From c73a2d9ad38ce3163a6674b370b285ce53826428 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Tue, 25 Jan 2022 15:36:53 -0500 Subject: [PATCH 001/188] init commit --- pyteal/ast/abirouter.py | 68 ++++++++++++++++++++++++++++++++++++ pyteal/ast/abirouter_test.py | 1 + pyteal/ast/return_.py | 5 +-- 3 files changed, 70 insertions(+), 4 deletions(-) create mode 100644 pyteal/ast/abirouter.py create mode 100644 pyteal/ast/abirouter_test.py diff --git a/pyteal/ast/abirouter.py b/pyteal/ast/abirouter.py new file mode 100644 index 000000000..775c097db --- /dev/null +++ b/pyteal/ast/abirouter.py @@ -0,0 +1,68 @@ +from typing import List, Union +from algosdk.future.transaction import OnComplete +from pyteal.ast import * + +""" +Implementing a Method +An ARC-4 app implementing a method: + +MUST check if txn NumAppArgs equals 0. If true, then this is a bare application call. If the Contract supports bare application calls for the current transaction parameters (it SHOULD check the OnCompletion action and whether the transaction is creating the application), it MUST handle the call appropriately and either approve or reject the transaction. The following steps MUST be ignored in this case. Otherwise, if the Contract does not support this bare application call, the Contract MUST reject the transaction. + +MUST examine txna ApplicationArgs 0 to identify the selector of the method being invoked. If the contract does not implement a method with that selector, the Contract MUST reject the transaction. + +MUST execute the actions required to implement the method being invoked. In general, this works by branching to the body of the method indicated by the selector. + +The code for that method MAY extract the arguments it needs, if any, from the application call arguments as described in the Encoding section. If the method has more than 15 arguments and the contract needs to extract an argument beyond the 14th, it MUST decode txna ApplicationArgs 15 as a tuple to access the arguments contained in it. + +If the method is non-void, the application MUST encode the return value as described in the Encoding section and then log it with the prefix 151f7c75. Other values MAY be logged before the return value, but other values MUST NOT be logged after the return value. +""" + + +""" +onBareAppCall can be used to register a bare app call (defined in the ABI as a call with no arguments or return value). The allowed on completion actions must be specified, as well as whether the bare call can be invoked during creation or not. + +onMethodCall can be used to register a method call. By default OnComplete.NoOp will be the only allowed on completion action, but others may be specified. Additionally, you can pass in a value for creation if this method call should be invoked during app creation or not. + +Ideally the router would also unpack the arguments and pass them to the handler function, as well as take any return value from the handler function and prefix and log it appropriately. Though this might require some more thought to implement properly. + +buildPrograms would construct ASTs for both the approval and clear programs based on the inputs to the router. If any routes can be accessed with OnComplete.ClearState, these routes will be added to the clear state program. +""" + + +class ABIRouter: + return_event_selector = Bytes("base16", "151f7c75") + + def __init__(self) -> None: + pass + + def onBareAppCall( + self, + bareAppCall: Subroutine, + onCompletes: Union[OnComplete, List[OnComplete]], + creation: bool, + ) -> None: + # TODO must check if txn NumAppArgs == 0 + pass + + def bareAppCall(self) -> None: + # Decorator form of onBareAppCall + pass + + def onMethodCall( + self, + methodAppCall: Subroutine, + onComplete: OnComplete, + creation: bool, + ) -> None: + # TODO unpack the arguments and pass them to handler function + # MethodSignature(methodAppCall.name) + # TODO take return value from handler and prefix + log + # Log(Concat(return_event_selector, ...)) + pass + + def methodCall(self) -> None: + # Decorator form of methodCall + pass + + def buildProgram(self) -> None: + pass diff --git a/pyteal/ast/abirouter_test.py b/pyteal/ast/abirouter_test.py new file mode 100644 index 000000000..464090415 --- /dev/null +++ b/pyteal/ast/abirouter_test.py @@ -0,0 +1 @@ +# TODO diff --git a/pyteal/ast/return_.py b/pyteal/ast/return_.py index 21d21f90c..9e0f4b566 100644 --- a/pyteal/ast/return_.py +++ b/pyteal/ast/return_.py @@ -73,10 +73,7 @@ def __teal__(self, options: "CompileOptions"): ) op = Op.return_ - args = [] - if self.value is not None: - args.append(self.value) - + args = [] if self.value is None else [self.value] return TealBlock.FromOp(options, TealOp(self, op), *args) def __str__(self): From 897ff6ad0f58747dfb9750402211d4293b1f1c17 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Tue, 25 Jan 2022 17:07:02 -0500 Subject: [PATCH 002/188] minor --- pyteal/ast/abirouter.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/pyteal/ast/abirouter.py b/pyteal/ast/abirouter.py index 775c097db..278a47f50 100644 --- a/pyteal/ast/abirouter.py +++ b/pyteal/ast/abirouter.py @@ -41,8 +41,18 @@ def onBareAppCall( onCompletes: Union[OnComplete, List[OnComplete]], creation: bool, ) -> None: - # TODO must check if txn NumAppArgs == 0 - pass + condList = [Txn.application_args.length() == Int(0)] + if creation: + condList.append(Txn.application_id() == Int(0)) + + if isinstance(onCompletes, list): + for i in onCompletes: + condList.append(Txn.on_completion() == i) + else: + condList.append(Txn.on_completion() == onCompletes) + + triggerCond = And(*condList) + def bareAppCall(self) -> None: # Decorator form of onBareAppCall @@ -54,6 +64,8 @@ def onMethodCall( onComplete: OnComplete, creation: bool, ) -> None: + # trigger condition: txna ApplicationArgs 0 == MethodSignature(methodAppCall.name) and onComplete == self.onComplete + # if create app id == 0 # TODO unpack the arguments and pass them to handler function # MethodSignature(methodAppCall.name) # TODO take return value from handler and prefix + log From 90fcfefbad01d5995240bfffbfc9b5dac4d6cd1b Mon Sep 17 00:00:00 2001 From: Hang Su Date: Tue, 25 Jan 2022 17:45:36 -0500 Subject: [PATCH 003/188] minor --- pyteal/ast/abirouter.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyteal/ast/abirouter.py b/pyteal/ast/abirouter.py index 278a47f50..aaa05fb67 100644 --- a/pyteal/ast/abirouter.py +++ b/pyteal/ast/abirouter.py @@ -53,7 +53,6 @@ def onBareAppCall( triggerCond = And(*condList) - def bareAppCall(self) -> None: # Decorator form of onBareAppCall pass From 01c114ed7ec74126c23fcd32ef552533c001ac67 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Wed, 26 Jan 2022 12:25:22 -0500 Subject: [PATCH 004/188] rm decorator --- pyteal/ast/abirouter.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/pyteal/ast/abirouter.py b/pyteal/ast/abirouter.py index aaa05fb67..7e2161b26 100644 --- a/pyteal/ast/abirouter.py +++ b/pyteal/ast/abirouter.py @@ -53,10 +53,6 @@ def onBareAppCall( triggerCond = And(*condList) - def bareAppCall(self) -> None: - # Decorator form of onBareAppCall - pass - def onMethodCall( self, methodAppCall: Subroutine, @@ -71,9 +67,5 @@ def onMethodCall( # Log(Concat(return_event_selector, ...)) pass - def methodCall(self) -> None: - # Decorator form of methodCall - pass - def buildProgram(self) -> None: pass From 46daf5cf8808fc86de2ef0e6b1c6984eb89003f8 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Wed, 26 Jan 2022 15:45:39 -0500 Subject: [PATCH 005/188] update ABI router design, seems we are still working on previous design --- pyteal/__init__.py | 3 +- pyteal/__init__.pyi | 4 +- pyteal/ast/__init__.py | 5 +- pyteal/ast/abirouter.py | 128 +++++++++++++++++++++++++++++----------- pyteal/config.py | 6 ++ pyteal/errors.py | 2 +- 6 files changed, 110 insertions(+), 38 deletions(-) diff --git a/pyteal/__init__.py b/pyteal/__init__.py index 85ba62b5f..58a4bd442 100644 --- a/pyteal/__init__.py +++ b/pyteal/__init__.py @@ -11,7 +11,7 @@ ) from .types import TealType from .errors import TealInternalError, TealTypeError, TealInputError, TealCompileError -from .config import MAX_GROUP_SIZE, NUM_SLOTS +from .config import MAX_GROUP_SIZE, NUM_SLOTS, RETURN_EVENT_SELECTOR # begin __all__ __all__ = ( @@ -30,6 +30,7 @@ "TealCompileError", "MAX_GROUP_SIZE", "NUM_SLOTS", + "RETURN_EVENT_SELECTOR", ] ) # end __all__ diff --git a/pyteal/__init__.pyi b/pyteal/__init__.pyi index 409e1aa53..da7d1a81a 100644 --- a/pyteal/__init__.pyi +++ b/pyteal/__init__.pyi @@ -14,7 +14,7 @@ from .compiler import ( ) from .types import TealType from .errors import TealInternalError, TealTypeError, TealInputError, TealCompileError -from .config import MAX_GROUP_SIZE, NUM_SLOTS +from .config import MAX_GROUP_SIZE, NUM_SLOTS, RETURN_EVENT_SELECTOR __all__ = [ "Expr", @@ -110,6 +110,7 @@ __all__ = [ "Return", "Approve", "Reject", + "ABIRouter", "Subroutine", "SubroutineDefinition", "SubroutineDeclaration", @@ -166,4 +167,5 @@ __all__ = [ "TealCompileError", "MAX_GROUP_SIZE", "NUM_SLOTS", + "RETURN_EVENT_SELECTOR", ] diff --git a/pyteal/ast/__init__.py b/pyteal/ast/__init__.py index 24b5240e0..3228109e6 100644 --- a/pyteal/ast/__init__.py +++ b/pyteal/ast/__init__.py @@ -6,7 +6,6 @@ from .addr import Addr from .bytes import Bytes from .int import Int, EnumInt -from .methodsig import MethodSignature # properties from .arg import Arg @@ -116,12 +115,13 @@ from .for_ import For from .break_ import Break from .continue_ import Continue - +from .abirouter import ABIRouter # misc from .scratch import ScratchSlot, ScratchLoad, ScratchStore, ScratchStackStore from .scratchvar import ScratchVar from .maybe import MaybeValue +from .methodsig import MethodSignature __all__ = [ "Expr", @@ -217,6 +217,7 @@ "Return", "Approve", "Reject", + "ABIRouter", "Subroutine", "SubroutineDefinition", "SubroutineDeclaration", diff --git a/pyteal/ast/abirouter.py b/pyteal/ast/abirouter.py index 7e2161b26..d0b3df952 100644 --- a/pyteal/ast/abirouter.py +++ b/pyteal/ast/abirouter.py @@ -1,10 +1,23 @@ -from typing import List, Union -from algosdk.future.transaction import OnComplete -from pyteal.ast import * +from typing import Callable, List, Tuple, Union, cast + +from pyteal.errors import TealInputError +from pyteal.config import RETURN_EVENT_SELECTOR + +from .app import OnComplete +from .bytes import Bytes +from .expr import Expr +from .int import EnumInt, Int +from .if_ import If +from .methodsig import MethodSignature +from .naryexpr import And, Or +from .return_ import Approve, Reject +from .seq import Seq +from .subroutine import Subroutine, SubroutineDefinition +from .txn import Txn """ Implementing a Method -An ARC-4 app implementing a method: +çAn ARC-4 app implementing a method: MUST check if txn NumAppArgs equals 0. If true, then this is a bare application call. If the Contract supports bare application calls for the current transaction parameters (it SHOULD check the OnCompletion action and whether the transaction is creating the application), it MUST handle the call appropriately and either approve or reject the transaction. The following steps MUST be ignored in this case. Otherwise, if the Contract does not support this bare application call, the Contract MUST reject the transaction. @@ -30,42 +43,91 @@ class ABIRouter: - return_event_selector = Bytes("base16", "151f7c75") - def __init__(self) -> None: - pass + self.approvalIfThen: List[Tuple[Expr, Expr]] = [] + self.clearStateIfThen: List[Tuple[Expr, Expr]] = [] + + @staticmethod + def approvalCondition( + methodName: Union[str, None], onCompletes: List[EnumInt], creation: bool + ) -> Union[Expr, None]: + condList: List[Expr] = [] if not creation else [Txn.application_id() == Int(0)] + if methodName != None: + condList.append( + Txn.application_args[0] == MethodSignature(cast(str, methodName)) + ) + ocCondList: List[Expr] = [] + for oc in onCompletes: + if oc != OnComplete.ClearState: + ocCondList.append(Txn.on_completion() == oc) + if len(ocCondList) == 0: + return None + condList.append(Or(*ocCondList)) + return And(*condList) + + @staticmethod + def clearStateCondition( + methodName: Union[str, None], onCompletes: List[EnumInt] + ) -> Union[Expr, None]: + if not any(map(lambda x: x == OnComplete.ClearState, onCompletes)): + return None + return ( + Txn.application_args[0] == MethodSignature(cast(str, methodName)) + if methodName != None + else Txn.application_args.length() == Int(0) + ) def onBareAppCall( self, - bareAppCall: Subroutine, - onCompletes: Union[OnComplete, List[OnComplete]], - creation: bool, + bareAppCall: Union[Callable[..., Expr], Expr], + onCompletes: Union[EnumInt, List[EnumInt]], + creation: bool = False, ) -> None: - condList = [Txn.application_args.length() == Int(0)] - if creation: - condList.append(Txn.application_id() == Int(0)) - - if isinstance(onCompletes, list): - for i in onCompletes: - condList.append(Txn.on_completion() == i) - else: - condList.append(Txn.on_completion() == onCompletes) - - triggerCond = And(*condList) + ocList: List[EnumInt] = ( + cast(List[EnumInt], onCompletes) + if isinstance(onCompletes, list) + else [cast(EnumInt, onCompletes)] + ) + approvalCond = ABIRouter.approvalCondition( + methodName=None, onCompletes=ocList, creation=creation + ) + clearStateCond = ABIRouter.clearStateCondition( + methodName=None, onCompletes=ocList + ) + # execBareAppCall: Expr = bareAppCall() if isinstance(bareAppCall, Subroutine) else cast(Expr, bareAppCall) + # TODO update then branch (either activate subroutine or run seq of expr), then approve + thenBranch = Seq([Approve()]) + # self.approvalIfThen.append((triggerCond, thenBranch)) def onMethodCall( self, - methodAppCall: Subroutine, - onComplete: OnComplete, - creation: bool, + methodName: str, + methodAppCall: Callable[..., Expr], + onComplete: EnumInt = OnComplete.NoOp, + creation: bool = False, ) -> None: - # trigger condition: txna ApplicationArgs 0 == MethodSignature(methodAppCall.name) and onComplete == self.onComplete - # if create app id == 0 + ocList: List[EnumInt] = [cast(EnumInt, onComplete)] + approvalCond = ABIRouter.approvalCondition( + methodName=methodName, onCompletes=ocList, creation=creation + ) + clearStateCond = ABIRouter.clearStateCondition( + methodName=methodName, onCompletes=ocList + ) # TODO unpack the arguments and pass them to handler function - # MethodSignature(methodAppCall.name) - # TODO take return value from handler and prefix + log - # Log(Concat(return_event_selector, ...)) - pass - - def buildProgram(self) -> None: - pass + # TODO take return value from handler and prefix + log: Log(Concat(return_event_selector, ...)) + # TODO update then branch (either activate subroutine or run seq of expr), then approve + thenBranch = Seq([Approve()]) + # self.approvalIfThen.append((triggerCond, thenBranch)) + + def buildProgram(self) -> Expr: + approvalProgram: If = If(self.approvalIfThen[0][0]).Then( + self.approvalIfThen[0][1] + ) + for i in range(1, len(self.approvalIfThen)): + approvalProgram.ElseIf(self.approvalIfThen[i][0]).Then( + self.approvalIfThen[i][1] + ) + approvalProgram.Else(Reject()) + + # TODO clear program + return approvalProgram diff --git a/pyteal/config.py b/pyteal/config.py index 54b9aee80..df4f1b5fd 100644 --- a/pyteal/config.py +++ b/pyteal/config.py @@ -1,5 +1,11 @@ +from .ast.bytes import Bytes + + # Maximum size of an atomic transaction group. MAX_GROUP_SIZE = 16 # Number of scratch space slots available. NUM_SLOTS = 256 + +# Bytes to prepend in log for ABI method return +RETURN_EVENT_SELECTOR = Bytes("base16", "151f7c75") diff --git a/pyteal/errors.py b/pyteal/errors.py index a32cb4d5a..4093819dd 100644 --- a/pyteal/errors.py +++ b/pyteal/errors.py @@ -1,4 +1,4 @@ -from typing import List, Optional, TYPE_CHECKING +from typing import Optional, TYPE_CHECKING if TYPE_CHECKING: from .ast import Expr From 8ea2bcb1acc18047a9e886c8aaf6b85fe1f9228a Mon Sep 17 00:00:00 2001 From: Hang Su Date: Wed, 26 Jan 2022 15:48:04 -0500 Subject: [PATCH 006/188] minor --- pyteal/ast/abirouter.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyteal/ast/abirouter.py b/pyteal/ast/abirouter.py index d0b3df952..f67a08eb5 100644 --- a/pyteal/ast/abirouter.py +++ b/pyteal/ast/abirouter.py @@ -101,17 +101,17 @@ def onBareAppCall( def onMethodCall( self, - methodName: str, + methodSig: str, methodAppCall: Callable[..., Expr], onComplete: EnumInt = OnComplete.NoOp, creation: bool = False, ) -> None: ocList: List[EnumInt] = [cast(EnumInt, onComplete)] approvalCond = ABIRouter.approvalCondition( - methodName=methodName, onCompletes=ocList, creation=creation + methodName=methodSig, onCompletes=ocList, creation=creation ) clearStateCond = ABIRouter.clearStateCondition( - methodName=methodName, onCompletes=ocList + methodName=methodSig, onCompletes=ocList ) # TODO unpack the arguments and pass them to handler function # TODO take return value from handler and prefix + log: Log(Concat(return_event_selector, ...)) From 0208800363c06a0840d29d425d4743da82c16f52 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Thu, 27 Jan 2022 10:20:40 -0500 Subject: [PATCH 007/188] update condition parser here --- pyteal/ast/abirouter.py | 73 +++++++++++++++++++++++------------------ 1 file changed, 41 insertions(+), 32 deletions(-) diff --git a/pyteal/ast/abirouter.py b/pyteal/ast/abirouter.py index f67a08eb5..eace878ed 100644 --- a/pyteal/ast/abirouter.py +++ b/pyteal/ast/abirouter.py @@ -17,7 +17,7 @@ """ Implementing a Method -çAn ARC-4 app implementing a method: +An ARC-4 app implementing a method: MUST check if txn NumAppArgs equals 0. If true, then this is a bare application call. If the Contract supports bare application calls for the current transaction parameters (it SHOULD check the OnCompletion action and whether the transaction is creating the application), it MUST handle the call appropriately and either approve or reject the transaction. The following steps MUST be ignored in this case. Otherwise, if the Contract does not support this bare application call, the Contract MUST reject the transaction. @@ -48,34 +48,49 @@ def __init__(self) -> None: self.clearStateIfThen: List[Tuple[Expr, Expr]] = [] @staticmethod - def approvalCondition( + def parseConditions( methodName: Union[str, None], onCompletes: List[EnumInt], creation: bool - ) -> Union[Expr, None]: - condList: List[Expr] = [] if not creation else [Txn.application_id() == Int(0)] - if methodName != None: - condList.append( - Txn.application_args[0] == MethodSignature(cast(str, methodName)) - ) - ocCondList: List[Expr] = [] - for oc in onCompletes: - if oc != OnComplete.ClearState: - ocCondList.append(Txn.on_completion() == oc) - if len(ocCondList) == 0: - return None - condList.append(Or(*ocCondList)) - return And(*condList) + ) -> Tuple[List[Expr], List[Expr]]: + # Check if it is a *CREATION* + approvalConds: List[Expr] = [Txn.application_id() == Int(0)] if creation else [] + clearStateConds: List[Expr] = [] - @staticmethod - def clearStateCondition( - methodName: Union[str, None], onCompletes: List[EnumInt] - ) -> Union[Expr, None]: - if not any(map(lambda x: x == OnComplete.ClearState, onCompletes)): - return None - return ( + # Check if current condition is for *ABI METHOD* (method selector) or *BARE APP CALL* (numAppArg == 0) + methodOrBareCondition = ( Txn.application_args[0] == MethodSignature(cast(str, methodName)) - if methodName != None + if methodName is not None else Txn.application_args.length() == Int(0) ) + approvalConds.append(methodOrBareCondition) + + # Check the existence of OC.CloseOut + closeOutExist = any(map(lambda x: x == OnComplete.CloseOut, onCompletes)) + # Check the existence of OC.ClearState (needed later) + clearStateExist = any(map(lambda x: x == OnComplete.ClearState, onCompletes)) + # Ill formed report if app create with existence of OC.CloseOut or OC.ClearState + if creation and (closeOutExist or clearStateExist): + raise TealInputError( + "OnComplete ClearState/CloseOut may be ill-formed with app creation" + ) + # if OC.ClearState exists, add method-or-bare-condition since it is only needed in ClearStateProgram + if clearStateExist: + clearStateConds.append(methodOrBareCondition) + + # Check onComplete conditions for approvalConds, filter out *ClearState* + approvalOcConds: List[Expr] = [ + Txn.on_completion() == oc + for oc in onCompletes + if oc != OnComplete.ClearState + ] + + # if approval OC condition is not empty, append Or to approvalConds + if len(approvalOcConds) > 0: + approvalConds.append(Or(*approvalOcConds)) + + # what we have here is: + # list of conds for approval program on one branch: creation?, method/bare, Or[OCs] + # list of conds for clearState program on one branch: method/bare + return approvalConds, clearStateConds def onBareAppCall( self, @@ -88,12 +103,9 @@ def onBareAppCall( if isinstance(onCompletes, list) else [cast(EnumInt, onCompletes)] ) - approvalCond = ABIRouter.approvalCondition( + approvalConds, clearStateConds = ABIRouter.parseConditions( methodName=None, onCompletes=ocList, creation=creation ) - clearStateCond = ABIRouter.clearStateCondition( - methodName=None, onCompletes=ocList - ) # execBareAppCall: Expr = bareAppCall() if isinstance(bareAppCall, Subroutine) else cast(Expr, bareAppCall) # TODO update then branch (either activate subroutine or run seq of expr), then approve thenBranch = Seq([Approve()]) @@ -107,12 +119,9 @@ def onMethodCall( creation: bool = False, ) -> None: ocList: List[EnumInt] = [cast(EnumInt, onComplete)] - approvalCond = ABIRouter.approvalCondition( + approvalConds, clearStateConds = ABIRouter.parseConditions( methodName=methodSig, onCompletes=ocList, creation=creation ) - clearStateCond = ABIRouter.clearStateCondition( - methodName=methodSig, onCompletes=ocList - ) # TODO unpack the arguments and pass them to handler function # TODO take return value from handler and prefix + log: Log(Concat(return_event_selector, ...)) # TODO update then branch (either activate subroutine or run seq of expr), then approve From 9adec413ef797c595b3c743b9936d137062e6093 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Thu, 27 Jan 2022 10:36:09 -0500 Subject: [PATCH 008/188] skeleton for handler wrapping, need to tweak on subroutine to expose more message --- pyteal/ast/abirouter.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/pyteal/ast/abirouter.py b/pyteal/ast/abirouter.py index eace878ed..601fc5b6e 100644 --- a/pyteal/ast/abirouter.py +++ b/pyteal/ast/abirouter.py @@ -92,6 +92,11 @@ def parseConditions( # list of conds for clearState program on one branch: method/bare return approvalConds, clearStateConds + @staticmethod + def wrapHandler(isMethod: bool, branch: Union[Callable[..., Expr], Expr]) -> Expr: + # TODO + return Seq([Approve()]) + def onBareAppCall( self, bareAppCall: Union[Callable[..., Expr], Expr], @@ -108,7 +113,7 @@ def onBareAppCall( ) # execBareAppCall: Expr = bareAppCall() if isinstance(bareAppCall, Subroutine) else cast(Expr, bareAppCall) # TODO update then branch (either activate subroutine or run seq of expr), then approve - thenBranch = Seq([Approve()]) + thenBranch = ABIRouter.wrapHandler(False, bareAppCall) # self.approvalIfThen.append((triggerCond, thenBranch)) def onMethodCall( @@ -125,7 +130,7 @@ def onMethodCall( # TODO unpack the arguments and pass them to handler function # TODO take return value from handler and prefix + log: Log(Concat(return_event_selector, ...)) # TODO update then branch (either activate subroutine or run seq of expr), then approve - thenBranch = Seq([Approve()]) + thenBranch = ABIRouter.wrapHandler(True, methodAppCall) # self.approvalIfThen.append((triggerCond, thenBranch)) def buildProgram(self) -> Expr: From 3953180160e1ef9217f67dc5a1a82a3ceaf6dc6f Mon Sep 17 00:00:00 2001 From: Hang Su Date: Thu, 27 Jan 2022 10:54:11 -0500 Subject: [PATCH 009/188] module --- pyteal/ast/abirouter.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyteal/ast/abirouter.py b/pyteal/ast/abirouter.py index 601fc5b6e..a811ffeb1 100644 --- a/pyteal/ast/abirouter.py +++ b/pyteal/ast/abirouter.py @@ -145,3 +145,6 @@ def buildProgram(self) -> Expr: # TODO clear program return approvalProgram + + +ABIRouter.__module__ = "pyteal" From 2bf75c38eace0a3798118f040693102890ab34d5 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Thu, 27 Jan 2022 14:06:48 -0500 Subject: [PATCH 010/188] use subroutine fn wrapper --- pyteal/ast/abirouter.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/pyteal/ast/abirouter.py b/pyteal/ast/abirouter.py index a811ffeb1..2c6010ba8 100644 --- a/pyteal/ast/abirouter.py +++ b/pyteal/ast/abirouter.py @@ -12,7 +12,7 @@ from .naryexpr import And, Or from .return_ import Approve, Reject from .seq import Seq -from .subroutine import Subroutine, SubroutineDefinition +from .subroutine import SubroutineFnWrapper from .txn import Txn """ @@ -93,13 +93,13 @@ def parseConditions( return approvalConds, clearStateConds @staticmethod - def wrapHandler(isMethod: bool, branch: Union[Callable[..., Expr], Expr]) -> Expr: + def wrapHandler(isMethod: bool, branch: Union[SubroutineFnWrapper, Expr]) -> Expr: # TODO return Seq([Approve()]) def onBareAppCall( self, - bareAppCall: Union[Callable[..., Expr], Expr], + bareAppCall: Union[SubroutineFnWrapper, Expr], onCompletes: Union[EnumInt, List[EnumInt]], creation: bool = False, ) -> None: @@ -118,14 +118,13 @@ def onBareAppCall( def onMethodCall( self, - methodSig: str, - methodAppCall: Callable[..., Expr], + methodAppCall: SubroutineFnWrapper, onComplete: EnumInt = OnComplete.NoOp, creation: bool = False, ) -> None: ocList: List[EnumInt] = [cast(EnumInt, onComplete)] approvalConds, clearStateConds = ABIRouter.parseConditions( - methodName=methodSig, onCompletes=ocList, creation=creation + methodName=methodAppCall.name(), onCompletes=ocList, creation=creation ) # TODO unpack the arguments and pass them to handler function # TODO take return value from handler and prefix + log: Log(Concat(return_event_selector, ...)) From 51153832a2a560ad875fee970df088e4fdf3ce5f Mon Sep 17 00:00:00 2001 From: Hang Su Date: Fri, 28 Jan 2022 15:46:31 -0500 Subject: [PATCH 011/188] update dummy method return class --- pyteal/ast/abirouter.py | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/pyteal/ast/abirouter.py b/pyteal/ast/abirouter.py index 2c6010ba8..42b723f3b 100644 --- a/pyteal/ast/abirouter.py +++ b/pyteal/ast/abirouter.py @@ -1,7 +1,6 @@ from typing import Callable, List, Tuple, Union, cast from pyteal.errors import TealInputError -from pyteal.config import RETURN_EVENT_SELECTOR from .app import OnComplete from .bytes import Bytes @@ -15,6 +14,11 @@ from .subroutine import SubroutineFnWrapper from .txn import Txn +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from ..compiler import CompileOptions + """ Implementing a Method An ARC-4 app implementing a method: @@ -113,8 +117,9 @@ def onBareAppCall( ) # execBareAppCall: Expr = bareAppCall() if isinstance(bareAppCall, Subroutine) else cast(Expr, bareAppCall) # TODO update then branch (either activate subroutine or run seq of expr), then approve - thenBranch = ABIRouter.wrapHandler(False, bareAppCall) + approvalBranch = ABIRouter.wrapHandler(False, bareAppCall) # self.approvalIfThen.append((triggerCond, thenBranch)) + clearStateBranch = Seq([]) def onMethodCall( self, @@ -129,7 +134,10 @@ def onMethodCall( # TODO unpack the arguments and pass them to handler function # TODO take return value from handler and prefix + log: Log(Concat(return_event_selector, ...)) # TODO update then branch (either activate subroutine or run seq of expr), then approve - thenBranch = ABIRouter.wrapHandler(True, methodAppCall) + approvalBranch = Seq( + [MethodReturn(ABIRouter.wrapHandler(True, methodAppCall)), Approve()] + ) + clearStateBranch = Seq([]) # self.approvalIfThen.append((triggerCond, thenBranch)) def buildProgram(self) -> Expr: @@ -147,3 +155,22 @@ def buildProgram(self) -> Expr: ABIRouter.__module__ = "pyteal" + + +class MethodReturn(Expr): + def __init__(self, value: Expr) -> None: + super().__init__() + """THIS IS A DUMMY CLASS, SHOULD WAIT ON ABI SIDE""" + self.value = value + + def has_return(self) -> bool: + return super().has_return() + + def __teal__(self, options: "CompileOptions"): + return super().__teal__(options) + + def type_of(self): + return super().type_of() + + def __str__(self) -> str: + return super().__str__() From 2d1c6e3281701066426364dd2a19cd037e8be2f8 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Fri, 28 Jan 2022 17:15:01 -0500 Subject: [PATCH 012/188] unify ast construct way --- pyteal/ast/abirouter.py | 42 +++++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/pyteal/ast/abirouter.py b/pyteal/ast/abirouter.py index 42b723f3b..197177d04 100644 --- a/pyteal/ast/abirouter.py +++ b/pyteal/ast/abirouter.py @@ -1,4 +1,5 @@ -from typing import Callable, List, Tuple, Union, cast +from typing import List, Tuple, Union, cast +from attr import dataclass from pyteal.errors import TealInputError @@ -117,9 +118,11 @@ def onBareAppCall( ) # execBareAppCall: Expr = bareAppCall() if isinstance(bareAppCall, Subroutine) else cast(Expr, bareAppCall) # TODO update then branch (either activate subroutine or run seq of expr), then approve - approvalBranch = ABIRouter.wrapHandler(False, bareAppCall) - # self.approvalIfThen.append((triggerCond, thenBranch)) - clearStateBranch = Seq([]) + branch = ABIRouter.wrapHandler(False, bareAppCall) + if len(approvalConds) > 0: + self.approvalIfThen.append((And(*approvalConds), branch)) + if len(clearStateConds) > 0: + self.approvalIfThen.append((And(*clearStateConds), branch)) def onMethodCall( self, @@ -134,24 +137,27 @@ def onMethodCall( # TODO unpack the arguments and pass them to handler function # TODO take return value from handler and prefix + log: Log(Concat(return_event_selector, ...)) # TODO update then branch (either activate subroutine or run seq of expr), then approve - approvalBranch = Seq( + branch = Seq( [MethodReturn(ABIRouter.wrapHandler(True, methodAppCall)), Approve()] ) - clearStateBranch = Seq([]) - # self.approvalIfThen.append((triggerCond, thenBranch)) + if len(approvalConds) > 0: + self.approvalIfThen.append((And(*approvalConds), branch)) + if len(clearStateConds) > 0: + self.approvalIfThen.append((And(*clearStateConds), branch)) - def buildProgram(self) -> Expr: - approvalProgram: If = If(self.approvalIfThen[0][0]).Then( - self.approvalIfThen[0][1] + @staticmethod + def astConstruct(astList: List[Tuple[Expr, Expr]]) -> Expr: + program: If = If(astList[0][0]).Then(astList[0][1]) + for i in range(1, len(astList)): + program.ElseIf(astList[i][0]).Then(astList[i][1]) + program.Else(Reject()) + return program + + def buildProgram(self) -> Tuple[Expr, Expr]: + return ( + ABIRouter.astConstruct(self.approvalIfThen), + ABIRouter.astConstruct(self.clearStateIfThen), ) - for i in range(1, len(self.approvalIfThen)): - approvalProgram.ElseIf(self.approvalIfThen[i][0]).Then( - self.approvalIfThen[i][1] - ) - approvalProgram.Else(Reject()) - - # TODO clear program - return approvalProgram ABIRouter.__module__ = "pyteal" From 0f8be9efb7478f4bd5a768cf40da8a37f2c062df Mon Sep 17 00:00:00 2001 From: Hang Su Date: Fri, 28 Jan 2022 17:16:30 -0500 Subject: [PATCH 013/188] minor --- pyteal/ast/abirouter.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/pyteal/ast/abirouter.py b/pyteal/ast/abirouter.py index 197177d04..dcf8053cb 100644 --- a/pyteal/ast/abirouter.py +++ b/pyteal/ast/abirouter.py @@ -1,10 +1,8 @@ from typing import List, Tuple, Union, cast -from attr import dataclass from pyteal.errors import TealInputError from .app import OnComplete -from .bytes import Bytes from .expr import Expr from .int import EnumInt, Int from .if_ import If From 92d3242ef9eaa19c92a4ed1d9098b7efcb1a4e7f Mon Sep 17 00:00:00 2001 From: Hang Su Date: Tue, 1 Feb 2022 13:03:07 -0500 Subject: [PATCH 014/188] update notes for tasks and todos --- pyteal/ast/abirouter.py | 40 ++++++++++++++++++---------------------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/pyteal/ast/abirouter.py b/pyteal/ast/abirouter.py index dcf8053cb..93720843b 100644 --- a/pyteal/ast/abirouter.py +++ b/pyteal/ast/abirouter.py @@ -18,30 +18,26 @@ if TYPE_CHECKING: from ..compiler import CompileOptions -""" -Implementing a Method -An ARC-4 app implementing a method: - -MUST check if txn NumAppArgs equals 0. If true, then this is a bare application call. If the Contract supports bare application calls for the current transaction parameters (it SHOULD check the OnCompletion action and whether the transaction is creating the application), it MUST handle the call appropriately and either approve or reject the transaction. The following steps MUST be ignored in this case. Otherwise, if the Contract does not support this bare application call, the Contract MUST reject the transaction. - -MUST examine txna ApplicationArgs 0 to identify the selector of the method being invoked. If the contract does not implement a method with that selector, the Contract MUST reject the transaction. -MUST execute the actions required to implement the method being invoked. In general, this works by branching to the body of the method indicated by the selector. - -The code for that method MAY extract the arguments it needs, if any, from the application call arguments as described in the Encoding section. If the method has more than 15 arguments and the contract needs to extract an argument beyond the 14th, it MUST decode txna ApplicationArgs 15 as a tuple to access the arguments contained in it. - -If the method is non-void, the application MUST encode the return value as described in the Encoding section and then log it with the prefix 151f7c75. Other values MAY be logged before the return value, but other values MUST NOT be logged after the return value. """ - - -""" -onBareAppCall can be used to register a bare app call (defined in the ABI as a call with no arguments or return value). The allowed on completion actions must be specified, as well as whether the bare call can be invoked during creation or not. - -onMethodCall can be used to register a method call. By default OnComplete.NoOp will be the only allowed on completion action, but others may be specified. Additionally, you can pass in a value for creation if this method call should be invoked during app creation or not. - -Ideally the router would also unpack the arguments and pass them to the handler function, as well as take any return value from the handler function and prefix and log it appropriately. Though this might require some more thought to implement properly. - -buildPrograms would construct ASTs for both the approval and clear programs based on the inputs to the router. If any routes can be accessed with OnComplete.ClearState, these routes will be added to the clear state program. +Notes: +- On a BareApp Call, check + - [x] txn NumAppArgs == 0 + - [x] On-Completion should match (can be a list of On-Completion here) + - [ ] Must execute actions required to invoke the method + +- On Method Call, check + - [x] txna ApplicationArgs 0 == method "method-signature" + - [x] On-Completion should match (only one On-Completion specified here?) + - [?] non void method call should log with 0x151f7c75 return-method-specifier (kinda done in another PR to ABI-Type) + - [ ] Must execute actions required to invoke the method + - [ ] extract arguments if needed (decode txna ApplicationArgs 15 if there exists, and extract arguments to feed method) + - [ ] redirect the method arguments and pass them to handler function + +Notes for OC: +- creation conflict with closeout and clearstate +- must check: txn ApplicationId == 0 for creation +- clearstate AST build should be separated with other OC AST build """ From bb204bf0a0205c93ccf6a18a294f127e141fdce0 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Tue, 1 Feb 2022 13:23:22 -0500 Subject: [PATCH 015/188] update handler wrapping for bare app call --- pyteal/ast/abirouter.py | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/pyteal/ast/abirouter.py b/pyteal/ast/abirouter.py index 93720843b..14fa1640a 100644 --- a/pyteal/ast/abirouter.py +++ b/pyteal/ast/abirouter.py @@ -1,6 +1,7 @@ from typing import List, Tuple, Union, cast from pyteal.errors import TealInputError +from pyteal.types import TealType from .app import OnComplete from .expr import Expr @@ -24,7 +25,7 @@ - On a BareApp Call, check - [x] txn NumAppArgs == 0 - [x] On-Completion should match (can be a list of On-Completion here) - - [ ] Must execute actions required to invoke the method + - [x] Must execute actions required to invoke the method - On Method Call, check - [x] txna ApplicationArgs 0 == method "method-signature" @@ -93,8 +94,28 @@ def parseConditions( @staticmethod def wrapHandler(isMethod: bool, branch: Union[SubroutineFnWrapper, Expr]) -> Expr: - # TODO - return Seq([Approve()]) + exprList: List[Expr] = [] + if not isMethod: + if ( + isinstance(branch, Seq) + and not branch.has_return() + and branch.type_of() == TealType.none + ): + exprList.append(branch) + elif ( + isinstance(branch, SubroutineFnWrapper) + and branch.has_return() + and branch.type_of() == TealType.none + ): + exprList.append(branch()) + else: + raise TealInputError( + "For bare app call: should only register Seq (with no ret) and Subroutine (with ret but none type)" + ) + else: + pass + exprList.append(Approve()) + return Seq(*exprList) def onBareAppCall( self, From ea66a6f14d3b34bc216468bc7d91962ab89c67f4 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Tue, 1 Feb 2022 13:24:04 -0500 Subject: [PATCH 016/188] minor --- pyteal/ast/abirouter.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/pyteal/ast/abirouter.py b/pyteal/ast/abirouter.py index 14fa1640a..3e8975918 100644 --- a/pyteal/ast/abirouter.py +++ b/pyteal/ast/abirouter.py @@ -131,8 +131,6 @@ def onBareAppCall( approvalConds, clearStateConds = ABIRouter.parseConditions( methodName=None, onCompletes=ocList, creation=creation ) - # execBareAppCall: Expr = bareAppCall() if isinstance(bareAppCall, Subroutine) else cast(Expr, bareAppCall) - # TODO update then branch (either activate subroutine or run seq of expr), then approve branch = ABIRouter.wrapHandler(False, bareAppCall) if len(approvalConds) > 0: self.approvalIfThen.append((And(*approvalConds), branch)) From 09bd24c0f1104ff53c2ea98038709cab5919075f Mon Sep 17 00:00:00 2001 From: Hang Su Date: Tue, 1 Feb 2022 15:14:08 -0500 Subject: [PATCH 017/188] update redirecting args to method registered --- pyteal/ast/abirouter.py | 72 ++++++++++++++++++++--------------------- 1 file changed, 35 insertions(+), 37 deletions(-) diff --git a/pyteal/ast/abirouter.py b/pyteal/ast/abirouter.py index 3e8975918..2db6f18e0 100644 --- a/pyteal/ast/abirouter.py +++ b/pyteal/ast/abirouter.py @@ -14,10 +14,10 @@ from .subroutine import SubroutineFnWrapper from .txn import Txn -from typing import TYPE_CHECKING +# from typing import TYPE_CHECKING -if TYPE_CHECKING: - from ..compiler import CompileOptions +# if TYPE_CHECKING: +# from ..compiler import CompileOptions """ @@ -31,9 +31,9 @@ - [x] txna ApplicationArgs 0 == method "method-signature" - [x] On-Completion should match (only one On-Completion specified here?) - [?] non void method call should log with 0x151f7c75 return-method-specifier (kinda done in another PR to ABI-Type) + - [?] redirect the method arguments and pass them to handler function (kinda done, but need to do with extraction and (en/de)-code) - [ ] Must execute actions required to invoke the method - [ ] extract arguments if needed (decode txna ApplicationArgs 15 if there exists, and extract arguments to feed method) - - [ ] redirect the method arguments and pass them to handler function Notes for OC: - creation conflict with closeout and clearstate @@ -49,16 +49,23 @@ def __init__(self) -> None: @staticmethod def parseConditions( - methodName: Union[str, None], onCompletes: List[EnumInt], creation: bool + mReg: Union[SubroutineFnWrapper, None], + onCompletes: List[EnumInt], + creation: bool, ) -> Tuple[List[Expr], List[Expr]]: # Check if it is a *CREATION* approvalConds: List[Expr] = [Txn.application_id() == Int(0)] if creation else [] clearStateConds: List[Expr] = [] - # Check if current condition is for *ABI METHOD* (method selector) or *BARE APP CALL* (numAppArg == 0) + # Check if current condition is for *ABI METHOD* (method selector && numAppArg == 1 + subroutineSyntaxArgNum) + # or *BARE APP CALL* (numAppArg == 0) methodOrBareCondition = ( - Txn.application_args[0] == MethodSignature(cast(str, methodName)) - if methodName is not None + And( + Txn.application_args.length() + == Int(1 + len(mReg.subroutine.implementationParams)), + Txn.application_args[0] == MethodSignature(cast(str, mReg.name)), + ) + if mReg is not None else Txn.application_args.length() == Int(0) ) approvalConds.append(methodOrBareCondition) @@ -110,10 +117,25 @@ def wrapHandler(isMethod: bool, branch: Union[SubroutineFnWrapper, Expr]) -> Exp exprList.append(branch()) else: raise TealInputError( - "For bare app call: should only register Seq (with no ret) and Subroutine (with ret but none type)" + "For bare app call: should only register Seq (with no ret) or Subroutine (with ret but none type)" ) else: - pass + if isinstance(branch, SubroutineFnWrapper) and branch.has_return(): + # TODO need to encode/decode things + exprList.append( + branch( + *[ + Txn.application_args[i + 1] + for i in range( + 0, len(branch.subroutine.implementationParams) + ) + ] + ) + ) + else: + raise TealInputError( + "For method call: should only register Subroutine with return" + ) exprList.append(Approve()) return Seq(*exprList) @@ -129,7 +151,7 @@ def onBareAppCall( else [cast(EnumInt, onCompletes)] ) approvalConds, clearStateConds = ABIRouter.parseConditions( - methodName=None, onCompletes=ocList, creation=creation + mReg=None, onCompletes=ocList, creation=creation ) branch = ABIRouter.wrapHandler(False, bareAppCall) if len(approvalConds) > 0: @@ -145,14 +167,9 @@ def onMethodCall( ) -> None: ocList: List[EnumInt] = [cast(EnumInt, onComplete)] approvalConds, clearStateConds = ABIRouter.parseConditions( - methodName=methodAppCall.name(), onCompletes=ocList, creation=creation - ) - # TODO unpack the arguments and pass them to handler function - # TODO take return value from handler and prefix + log: Log(Concat(return_event_selector, ...)) - # TODO update then branch (either activate subroutine or run seq of expr), then approve - branch = Seq( - [MethodReturn(ABIRouter.wrapHandler(True, methodAppCall)), Approve()] + mReg=methodAppCall, onCompletes=ocList, creation=creation ) + branch = Seq([ABIRouter.wrapHandler(True, methodAppCall), Approve()]) if len(approvalConds) > 0: self.approvalIfThen.append((And(*approvalConds), branch)) if len(clearStateConds) > 0: @@ -174,22 +191,3 @@ def buildProgram(self) -> Tuple[Expr, Expr]: ABIRouter.__module__ = "pyteal" - - -class MethodReturn(Expr): - def __init__(self, value: Expr) -> None: - super().__init__() - """THIS IS A DUMMY CLASS, SHOULD WAIT ON ABI SIDE""" - self.value = value - - def has_return(self) -> bool: - return super().has_return() - - def __teal__(self, options: "CompileOptions"): - return super().__teal__(options) - - def type_of(self): - return super().type_of() - - def __str__(self) -> str: - return super().__str__() From dfbec9b488bdbcbaadd701cf538ea19c9ebb1386 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Tue, 1 Feb 2022 15:17:33 -0500 Subject: [PATCH 018/188] minor --- pyteal/ast/abirouter.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyteal/ast/abirouter.py b/pyteal/ast/abirouter.py index 2db6f18e0..a77aad38d 100644 --- a/pyteal/ast/abirouter.py +++ b/pyteal/ast/abirouter.py @@ -1,7 +1,7 @@ from typing import List, Tuple, Union, cast -from pyteal.errors import TealInputError -from pyteal.types import TealType +from ..errors import TealInputError +from ..types import TealType from .app import OnComplete from .expr import Expr From 88a565f7d7a9b4889277e4d598d583e52b7bdcf7 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Tue, 1 Feb 2022 15:31:46 -0500 Subject: [PATCH 019/188] minor --- pyteal/ast/abirouter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyteal/ast/abirouter.py b/pyteal/ast/abirouter.py index a77aad38d..fd29be0dd 100644 --- a/pyteal/ast/abirouter.py +++ b/pyteal/ast/abirouter.py @@ -63,7 +63,7 @@ def parseConditions( And( Txn.application_args.length() == Int(1 + len(mReg.subroutine.implementationParams)), - Txn.application_args[0] == MethodSignature(cast(str, mReg.name)), + Txn.application_args[0] == MethodSignature(mReg.name()), ) if mReg is not None else Txn.application_args.length() == Int(0) From 8c54acc753c4bf0a8c1ac83cb6d662131838bd66 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Tue, 1 Feb 2022 17:13:26 -0500 Subject: [PATCH 020/188] update router src --- pyteal/ast/abirouter.py | 43 +++++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/pyteal/ast/abirouter.py b/pyteal/ast/abirouter.py index fd29be0dd..8afda141e 100644 --- a/pyteal/ast/abirouter.py +++ b/pyteal/ast/abirouter.py @@ -14,11 +14,6 @@ from .subroutine import SubroutineFnWrapper from .txn import Txn -# from typing import TYPE_CHECKING - -# if TYPE_CHECKING: -# from ..compiler import CompileOptions - """ Notes: @@ -126,9 +121,7 @@ def wrapHandler(isMethod: bool, branch: Union[SubroutineFnWrapper, Expr]) -> Exp branch( *[ Txn.application_args[i + 1] - for i in range( - 0, len(branch.subroutine.implementationParams) - ) + for i in range(len(branch.subroutine.implementationParams)) ] ) ) @@ -139,6 +132,24 @@ def wrapHandler(isMethod: bool, branch: Union[SubroutineFnWrapper, Expr]) -> Exp exprList.append(Approve()) return Seq(*exprList) + def __appendToAST( + self, approvalConds: List[Expr], clearConds: List[Expr], branch: Expr + ) -> None: + if len(approvalConds) > 0: + self.approvalIfThen.append( + ( + And(*approvalConds) if len(approvalConds) > 1 else approvalConds[0], + branch, + ) + ) + if len(clearConds) > 0: + self.clearStateIfThen.append( + ( + And(*clearConds) if len(approvalConds) > 1 else clearConds[0], + branch, + ) + ) + def onBareAppCall( self, bareAppCall: Union[SubroutineFnWrapper, Expr], @@ -150,14 +161,11 @@ def onBareAppCall( if isinstance(onCompletes, list) else [cast(EnumInt, onCompletes)] ) - approvalConds, clearStateConds = ABIRouter.parseConditions( + approvalConds, clearConds = ABIRouter.parseConditions( mReg=None, onCompletes=ocList, creation=creation ) branch = ABIRouter.wrapHandler(False, bareAppCall) - if len(approvalConds) > 0: - self.approvalIfThen.append((And(*approvalConds), branch)) - if len(clearStateConds) > 0: - self.approvalIfThen.append((And(*clearStateConds), branch)) + self.__appendToAST(approvalConds, clearConds, branch) def onMethodCall( self, @@ -166,14 +174,11 @@ def onMethodCall( creation: bool = False, ) -> None: ocList: List[EnumInt] = [cast(EnumInt, onComplete)] - approvalConds, clearStateConds = ABIRouter.parseConditions( + approvalConds, clearConds = ABIRouter.parseConditions( mReg=methodAppCall, onCompletes=ocList, creation=creation ) - branch = Seq([ABIRouter.wrapHandler(True, methodAppCall), Approve()]) - if len(approvalConds) > 0: - self.approvalIfThen.append((And(*approvalConds), branch)) - if len(clearStateConds) > 0: - self.approvalIfThen.append((And(*clearStateConds), branch)) + branch = ABIRouter.wrapHandler(True, methodAppCall) + self.__appendToAST(approvalConds, clearConds, branch) @staticmethod def astConstruct(astList: List[Tuple[Expr, Expr]]) -> Expr: From 1d6fa034629d5e1622331385bf573031aac128b8 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Tue, 1 Feb 2022 17:23:35 -0500 Subject: [PATCH 021/188] update program node --- pyteal/ast/abirouter.py | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/pyteal/ast/abirouter.py b/pyteal/ast/abirouter.py index 8afda141e..78885f64b 100644 --- a/pyteal/ast/abirouter.py +++ b/pyteal/ast/abirouter.py @@ -1,4 +1,4 @@ -from typing import List, Tuple, Union, cast +from typing import List, NamedTuple, Tuple, Union, cast from ..errors import TealInputError from ..types import TealType @@ -37,10 +37,15 @@ """ +class ProgramNode(NamedTuple): + condition: Expr + branch: Expr + + class ABIRouter: def __init__(self) -> None: - self.approvalIfThen: List[Tuple[Expr, Expr]] = [] - self.clearStateIfThen: List[Tuple[Expr, Expr]] = [] + self.approvalIfThen: List[ProgramNode] = [] + self.clearStateIfThen: List[ProgramNode] = [] @staticmethod def parseConditions( @@ -137,15 +142,15 @@ def __appendToAST( ) -> None: if len(approvalConds) > 0: self.approvalIfThen.append( - ( + ProgramNode( And(*approvalConds) if len(approvalConds) > 1 else approvalConds[0], branch, ) ) if len(clearConds) > 0: self.clearStateIfThen.append( - ( - And(*clearConds) if len(approvalConds) > 1 else clearConds[0], + ProgramNode( + And(*clearConds) if len(clearConds) > 1 else clearConds[0], branch, ) ) @@ -181,14 +186,15 @@ def onMethodCall( self.__appendToAST(approvalConds, clearConds, branch) @staticmethod - def astConstruct(astList: List[Tuple[Expr, Expr]]) -> Expr: - program: If = If(astList[0][0]).Then(astList[0][1]) + def astConstruct(astList: List[ProgramNode]) -> Expr: + program: If = If(astList[0].condition).Then(astList[0].branch) for i in range(1, len(astList)): - program.ElseIf(astList[i][0]).Then(astList[i][1]) + program.ElseIf(astList[i].condition).Then(astList[i].branch) program.Else(Reject()) return program def buildProgram(self) -> Tuple[Expr, Expr]: + # TODO need to recheck how the program is built return ( ABIRouter.astConstruct(self.approvalIfThen), ABIRouter.astConstruct(self.clearStateIfThen), From 7d683c35c558e3d89fa273c32487092191207e30 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Tue, 1 Feb 2022 17:31:17 -0500 Subject: [PATCH 022/188] add questions in build prog --- pyteal/ast/abirouter.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyteal/ast/abirouter.py b/pyteal/ast/abirouter.py index 78885f64b..4053435ac 100644 --- a/pyteal/ast/abirouter.py +++ b/pyteal/ast/abirouter.py @@ -195,6 +195,8 @@ def astConstruct(astList: List[ProgramNode]) -> Expr: def buildProgram(self) -> Tuple[Expr, Expr]: # TODO need to recheck how the program is built + # question: if there's no approval condition/branch registered, shall it reject/approve everything? + # similarly, if there's no clearstate condition/branch registered, shall it reject/approve everything? return ( ABIRouter.astConstruct(self.approvalIfThen), ABIRouter.astConstruct(self.clearStateIfThen), From 28c3a461ce819dab926ad2a06c942ae8e10d47dc Mon Sep 17 00:00:00 2001 From: Hang Su Date: Tue, 1 Feb 2022 17:49:39 -0500 Subject: [PATCH 023/188] update executing method branch --- pyteal/ast/abirouter.py | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/pyteal/ast/abirouter.py b/pyteal/ast/abirouter.py index 4053435ac..ab125ec0d 100644 --- a/pyteal/ast/abirouter.py +++ b/pyteal/ast/abirouter.py @@ -1,5 +1,8 @@ from typing import List, NamedTuple, Tuple, Union, cast +from pyteal.ast.unaryexpr import Log +from pyteal.config import RETURN_EVENT_SELECTOR + from ..errors import TealInputError from ..types import TealType @@ -8,7 +11,7 @@ from .int import EnumInt, Int from .if_ import If from .methodsig import MethodSignature -from .naryexpr import And, Or +from .naryexpr import And, Concat, Or from .return_ import Approve, Reject from .seq import Seq from .subroutine import SubroutineFnWrapper @@ -122,13 +125,18 @@ def wrapHandler(isMethod: bool, branch: Union[SubroutineFnWrapper, Expr]) -> Exp else: if isinstance(branch, SubroutineFnWrapper) and branch.has_return(): # TODO need to encode/decode things + + execBranch: Expr = branch( + *[ + Txn.application_args[i + 1] + for i in range(len(branch.subroutine.implementationParams)) + ] + ) + exprList.append( - branch( - *[ - Txn.application_args[i + 1] - for i in range(len(branch.subroutine.implementationParams)) - ] - ) + Log(Concat(RETURN_EVENT_SELECTOR, execBranch)) + if branch.type_of() != TealType.none + else execBranch ) else: raise TealInputError( From 0633c88e808b29cc63d266851546f0606320cb9e Mon Sep 17 00:00:00 2001 From: Hang Su Date: Tue, 1 Feb 2022 17:55:07 -0500 Subject: [PATCH 024/188] minor --- pyteal/ast/abirouter.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pyteal/ast/abirouter.py b/pyteal/ast/abirouter.py index ab125ec0d..113ce76dc 100644 --- a/pyteal/ast/abirouter.py +++ b/pyteal/ast/abirouter.py @@ -1,8 +1,6 @@ from typing import List, NamedTuple, Tuple, Union, cast -from pyteal.ast.unaryexpr import Log -from pyteal.config import RETURN_EVENT_SELECTOR - +from ..config import RETURN_EVENT_SELECTOR from ..errors import TealInputError from ..types import TealType @@ -11,6 +9,7 @@ from .int import EnumInt, Int from .if_ import If from .methodsig import MethodSignature +from .unaryexpr import Log from .naryexpr import And, Concat, Or from .return_ import Approve, Reject from .seq import Seq From e5cbdc7afdbda5a00be8f1de5b6b34879dd1eefc Mon Sep 17 00:00:00 2001 From: Hang Su Date: Wed, 2 Feb 2022 15:15:00 -0500 Subject: [PATCH 025/188] minor --- pyteal/ast/abirouter.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyteal/ast/abirouter.py b/pyteal/ast/abirouter.py index 113ce76dc..082581a9c 100644 --- a/pyteal/ast/abirouter.py +++ b/pyteal/ast/abirouter.py @@ -64,7 +64,7 @@ def parseConditions( methodOrBareCondition = ( And( Txn.application_args.length() - == Int(1 + len(mReg.subroutine.implementationParams)), + == Int(1 + mReg.subroutine.argumentCount()), Txn.application_args[0] == MethodSignature(mReg.name()), ) if mReg is not None @@ -128,7 +128,7 @@ def wrapHandler(isMethod: bool, branch: Union[SubroutineFnWrapper, Expr]) -> Exp execBranch: Expr = branch( *[ Txn.application_args[i + 1] - for i in range(len(branch.subroutine.implementationParams)) + for i in range(branch.subroutine.argumentCount()) ] ) From e8d54d842fc996aedb876aa7ab5e22e959a534d5 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Wed, 2 Feb 2022 17:30:31 -0500 Subject: [PATCH 026/188] minor --- pyteal/ast/abirouter.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pyteal/ast/abirouter.py b/pyteal/ast/abirouter.py index 082581a9c..fab6996fe 100644 --- a/pyteal/ast/abirouter.py +++ b/pyteal/ast/abirouter.py @@ -59,12 +59,12 @@ def parseConditions( approvalConds: List[Expr] = [Txn.application_id() == Int(0)] if creation else [] clearStateConds: List[Expr] = [] - # Check if current condition is for *ABI METHOD* (method selector && numAppArg == 1 + subroutineSyntaxArgNum) + # Check if current condition is for *ABI METHOD* (method selector && numAppArg == max(16, 1 + subroutineSyntaxArgNum)) # or *BARE APP CALL* (numAppArg == 0) methodOrBareCondition = ( And( Txn.application_args.length() - == Int(1 + mReg.subroutine.argumentCount()), + == Int(max(1 + mReg.subroutine.argumentCount(), 16)), Txn.application_args[0] == MethodSignature(mReg.name()), ) if mReg is not None @@ -194,6 +194,8 @@ def onMethodCall( @staticmethod def astConstruct(astList: List[ProgramNode]) -> Expr: + if len(astList) == 0: + raise TealInputError("ABIRouter: Cannot build program with an empty AST") program: If = If(astList[0].condition).Then(astList[0].branch) for i in range(1, len(astList)): program.ElseIf(astList[i].condition).Then(astList[i].branch) From 52cd87c01bb4eaa94a24a91506793ffd9cd38e26 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Thu, 3 Feb 2022 11:00:12 -0500 Subject: [PATCH 027/188] hide other methods, set MethodAppArgNumLimit, keyword args on exposed register func, skeleton for arg de-tuple --- pyteal/ast/abirouter.py | 59 ++++++++++++++++++++++------------------- pyteal/config.py | 3 +++ 2 files changed, 35 insertions(+), 27 deletions(-) diff --git a/pyteal/ast/abirouter.py b/pyteal/ast/abirouter.py index fab6996fe..dbec1a9a4 100644 --- a/pyteal/ast/abirouter.py +++ b/pyteal/ast/abirouter.py @@ -1,6 +1,6 @@ from typing import List, NamedTuple, Tuple, Union, cast -from ..config import RETURN_EVENT_SELECTOR +from ..config import METHOD_APP_ARG_NUM_LIMIT, RETURN_EVENT_SELECTOR from ..errors import TealInputError from ..types import TealType @@ -50,7 +50,7 @@ def __init__(self) -> None: self.clearStateIfThen: List[ProgramNode] = [] @staticmethod - def parseConditions( + def __parseConditions( mReg: Union[SubroutineFnWrapper, None], onCompletes: List[EnumInt], creation: bool, @@ -59,13 +59,17 @@ def parseConditions( approvalConds: List[Expr] = [Txn.application_id() == Int(0)] if creation else [] clearStateConds: List[Expr] = [] - # Check if current condition is for *ABI METHOD* (method selector && numAppArg == max(16, 1 + subroutineSyntaxArgNum)) - # or *BARE APP CALL* (numAppArg == 0) + # Check: + # - if current condition is for *ABI METHOD* + # (method selector && numAppArg == max(METHOD_APP_ARG_NUM_LIMIT, 1 + subroutineSyntaxArgNum)) + # - or *BARE APP CALL* (numAppArg == 0) methodOrBareCondition = ( And( - Txn.application_args.length() - == Int(max(1 + mReg.subroutine.argumentCount(), 16)), Txn.application_args[0] == MethodSignature(mReg.name()), + Txn.application_args.length() + == Int( + max(1 + mReg.subroutine.argumentCount(), METHOD_APP_ARG_NUM_LIMIT) + ), ) if mReg is not None else Txn.application_args.length() == Int(0) @@ -102,7 +106,7 @@ def parseConditions( return approvalConds, clearStateConds @staticmethod - def wrapHandler(isMethod: bool, branch: Union[SubroutineFnWrapper, Expr]) -> Expr: + def __wrapHandler(isMethod: bool, branch: Union[SubroutineFnWrapper, Expr]) -> Expr: exprList: List[Expr] = [] if not isMethod: if ( @@ -124,18 +128,18 @@ def wrapHandler(isMethod: bool, branch: Union[SubroutineFnWrapper, Expr]) -> Exp else: if isinstance(branch, SubroutineFnWrapper) and branch.has_return(): # TODO need to encode/decode things - - execBranch: Expr = branch( - *[ - Txn.application_args[i + 1] - for i in range(branch.subroutine.argumentCount()) - ] - ) + execBranchArgs: List[Expr] = [] + if branch.subroutine.argumentCount() >= METHOD_APP_ARG_NUM_LIMIT: + # decode (if arg num > 15 need to de-tuple 15th (last) argument) + pass + else: + pass exprList.append( - Log(Concat(RETURN_EVENT_SELECTOR, execBranch)) + # TODO this line can be changed to method-return in ABI side + Log(Concat(RETURN_EVENT_SELECTOR, branch(*execBranchArgs))) if branch.type_of() != TealType.none - else execBranch + else branch(*execBranchArgs) ) else: raise TealInputError( @@ -166,6 +170,7 @@ def onBareAppCall( self, bareAppCall: Union[SubroutineFnWrapper, Expr], onCompletes: Union[EnumInt, List[EnumInt]], + *, creation: bool = False, ) -> None: ocList: List[EnumInt] = ( @@ -173,42 +178,42 @@ def onBareAppCall( if isinstance(onCompletes, list) else [cast(EnumInt, onCompletes)] ) - approvalConds, clearConds = ABIRouter.parseConditions( + approvalConds, clearConds = ABIRouter.__parseConditions( mReg=None, onCompletes=ocList, creation=creation ) - branch = ABIRouter.wrapHandler(False, bareAppCall) + branch = ABIRouter.__wrapHandler(False, bareAppCall) self.__appendToAST(approvalConds, clearConds, branch) def onMethodCall( self, methodAppCall: SubroutineFnWrapper, + *, onComplete: EnumInt = OnComplete.NoOp, creation: bool = False, ) -> None: ocList: List[EnumInt] = [cast(EnumInt, onComplete)] - approvalConds, clearConds = ABIRouter.parseConditions( + approvalConds, clearConds = ABIRouter.__parseConditions( mReg=methodAppCall, onCompletes=ocList, creation=creation ) - branch = ABIRouter.wrapHandler(True, methodAppCall) + branch = ABIRouter.__wrapHandler(True, methodAppCall) self.__appendToAST(approvalConds, clearConds, branch) @staticmethod - def astConstruct(astList: List[ProgramNode]) -> Expr: + def __astConstruct( + astList: List[ProgramNode], *, astDefault: Expr = Reject() + ) -> Expr: if len(astList) == 0: raise TealInputError("ABIRouter: Cannot build program with an empty AST") program: If = If(astList[0].condition).Then(astList[0].branch) for i in range(1, len(astList)): program.ElseIf(astList[i].condition).Then(astList[i].branch) - program.Else(Reject()) + program.Else(astDefault) return program def buildProgram(self) -> Tuple[Expr, Expr]: - # TODO need to recheck how the program is built - # question: if there's no approval condition/branch registered, shall it reject/approve everything? - # similarly, if there's no clearstate condition/branch registered, shall it reject/approve everything? return ( - ABIRouter.astConstruct(self.approvalIfThen), - ABIRouter.astConstruct(self.clearStateIfThen), + ABIRouter.__astConstruct(self.approvalIfThen, astDefault=Reject()), + ABIRouter.__astConstruct(self.clearStateIfThen, astDefault=Approve()), ) diff --git a/pyteal/config.py b/pyteal/config.py index df4f1b5fd..8420b3356 100644 --- a/pyteal/config.py +++ b/pyteal/config.py @@ -9,3 +9,6 @@ # Bytes to prepend in log for ABI method return RETURN_EVENT_SELECTOR = Bytes("base16", "151f7c75") + +# ARC-0004 method application-arg num limit +METHOD_APP_ARG_NUM_LIMIT = 16 From fb544fed54c12581e0c06d2af9e2418533923d0b Mon Sep 17 00:00:00 2001 From: Hang Su Date: Thu, 3 Feb 2022 11:00:32 -0500 Subject: [PATCH 028/188] minor --- pyteal/ast/abirouter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyteal/ast/abirouter.py b/pyteal/ast/abirouter.py index dbec1a9a4..8a00d328a 100644 --- a/pyteal/ast/abirouter.py +++ b/pyteal/ast/abirouter.py @@ -130,7 +130,7 @@ def __wrapHandler(isMethod: bool, branch: Union[SubroutineFnWrapper, Expr]) -> E # TODO need to encode/decode things execBranchArgs: List[Expr] = [] if branch.subroutine.argumentCount() >= METHOD_APP_ARG_NUM_LIMIT: - # decode (if arg num > 15 need to de-tuple 15th (last) argument) + # NOTE decode (if arg num > 15 need to de-tuple 15th (last) argument) pass else: pass From 096d693e16c3ed6bbee1704d813791b85eb1a9e7 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Thu, 3 Feb 2022 13:01:23 -0500 Subject: [PATCH 029/188] update notes --- pyteal/ast/abirouter.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyteal/ast/abirouter.py b/pyteal/ast/abirouter.py index 8a00d328a..0728bfe1d 100644 --- a/pyteal/ast/abirouter.py +++ b/pyteal/ast/abirouter.py @@ -16,6 +16,7 @@ from .subroutine import SubroutineFnWrapper from .txn import Txn +# NOTE this should sit in `abi` directory, still waiting on abi to be merged in """ Notes: From 26c3ea047ecfc9ce0750ce469394b592f45a6be6 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Tue, 8 Feb 2022 12:31:48 -0500 Subject: [PATCH 030/188] update --- pyteal/ast/abirouter.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/pyteal/ast/abirouter.py b/pyteal/ast/abirouter.py index 0728bfe1d..7deec1cc2 100644 --- a/pyteal/ast/abirouter.py +++ b/pyteal/ast/abirouter.py @@ -28,10 +28,13 @@ - On Method Call, check - [x] txna ApplicationArgs 0 == method "method-signature" - [x] On-Completion should match (only one On-Completion specified here?) - - [?] non void method call should log with 0x151f7c75 return-method-specifier (kinda done in another PR to ABI-Type) - - [?] redirect the method arguments and pass them to handler function (kinda done, but need to do with extraction and (en/de)-code) + - [?] non void method call should log with 0x151f7c75 return-method-specifier + (kinda done in another PR to ABI-Type) + - [?] redirect the method arguments and pass them to handler function + (kinda done, but need to do with extraction and (en/de)-code) - [ ] Must execute actions required to invoke the method - - [ ] extract arguments if needed (decode txna ApplicationArgs 15 if there exists, and extract arguments to feed method) + - [ ] extract arguments if needed + (decode txna ApplicationArgs 15 if there exists, and extract arguments to feed method) Notes for OC: - creation conflict with closeout and clearstate @@ -124,7 +127,7 @@ def __wrapHandler(isMethod: bool, branch: Union[SubroutineFnWrapper, Expr]) -> E exprList.append(branch()) else: raise TealInputError( - "For bare app call: should only register Seq (with no ret) or Subroutine (with ret but none type)" + "bare appcall can only accept: none type + Seq (no ret) or Subroutine (with ret)" ) else: if isinstance(branch, SubroutineFnWrapper) and branch.has_return(): From 541b707e92c9a4aaa31c32e3997e9e0e5090b630 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Wed, 9 Feb 2022 11:59:19 -0500 Subject: [PATCH 031/188] add a default for no registered ast gen --- pyteal/ast/abirouter.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/pyteal/ast/abirouter.py b/pyteal/ast/abirouter.py index 7deec1cc2..39c7abff1 100644 --- a/pyteal/ast/abirouter.py +++ b/pyteal/ast/abirouter.py @@ -204,10 +204,18 @@ def onMethodCall( @staticmethod def __astConstruct( - astList: List[ProgramNode], *, astDefault: Expr = Reject() + astList: List[ProgramNode], + *, + astDefault: Expr = Reject(), + astNoRegDefault: Expr = Approve(), ) -> Expr: if len(astList) == 0: - raise TealInputError("ABIRouter: Cannot build program with an empty AST") + if astNoRegDefault is None: + raise TealInputError( + "ABIRouter: Cannot build program with an empty AST" + ) + else: + return astNoRegDefault program: If = If(astList[0].condition).Then(astList[0].branch) for i in range(1, len(astList)): program.ElseIf(astList[i].condition).Then(astList[i].branch) @@ -216,8 +224,12 @@ def __astConstruct( def buildProgram(self) -> Tuple[Expr, Expr]: return ( - ABIRouter.__astConstruct(self.approvalIfThen, astDefault=Reject()), - ABIRouter.__astConstruct(self.clearStateIfThen, astDefault=Approve()), + ABIRouter.__astConstruct( + self.approvalIfThen, astDefault=Reject(), astNoRegDefault=Approve() + ), + ABIRouter.__astConstruct( + self.clearStateIfThen, astDefault=Approve(), astNoRegDefault=Approve() + ), ) From f959cbaa6f4ebed27f1003681255589f1324ecee Mon Sep 17 00:00:00 2001 From: Hang Su Date: Tue, 1 Mar 2022 14:26:06 -0500 Subject: [PATCH 032/188] update ast construction to conds --- pyteal/ast/abirouter.py | 27 ++++++++------------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/pyteal/ast/abirouter.py b/pyteal/ast/abirouter.py index 39c7abff1..571ccf9dc 100644 --- a/pyteal/ast/abirouter.py +++ b/pyteal/ast/abirouter.py @@ -1,5 +1,7 @@ from typing import List, NamedTuple, Tuple, Union, cast +from pyteal.ast.cond import Cond + from ..config import METHOD_APP_ARG_NUM_LIMIT, RETURN_EVENT_SELECTOR from ..errors import TealInputError from ..types import TealType @@ -205,31 +207,18 @@ def onMethodCall( @staticmethod def __astConstruct( astList: List[ProgramNode], - *, - astDefault: Expr = Reject(), - astNoRegDefault: Expr = Approve(), ) -> Expr: if len(astList) == 0: - if astNoRegDefault is None: - raise TealInputError( - "ABIRouter: Cannot build program with an empty AST" - ) - else: - return astNoRegDefault - program: If = If(astList[0].condition).Then(astList[0].branch) - for i in range(1, len(astList)): - program.ElseIf(astList[i].condition).Then(astList[i].branch) - program.Else(astDefault) + raise TealInputError("ABIRouter: Cannot build program with an empty AST") + + program: Cond = Cond(*[[node.condition, node.branch] for node in astList]) + return program def buildProgram(self) -> Tuple[Expr, Expr]: return ( - ABIRouter.__astConstruct( - self.approvalIfThen, astDefault=Reject(), astNoRegDefault=Approve() - ), - ABIRouter.__astConstruct( - self.clearStateIfThen, astDefault=Approve(), astNoRegDefault=Approve() - ), + ABIRouter.__astConstruct(self.approvalIfThen), + ABIRouter.__astConstruct(self.clearStateIfThen), ) From 2794fa401d1699173666f307714afa4fabbfe5d2 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Tue, 1 Mar 2022 15:56:16 -0500 Subject: [PATCH 033/188] need some documentations --- pyteal/ast/abirouter.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/pyteal/ast/abirouter.py b/pyteal/ast/abirouter.py index 571ccf9dc..0701131c5 100644 --- a/pyteal/ast/abirouter.py +++ b/pyteal/ast/abirouter.py @@ -9,11 +9,10 @@ from .app import OnComplete from .expr import Expr from .int import EnumInt, Int -from .if_ import If from .methodsig import MethodSignature from .unaryexpr import Log from .naryexpr import And, Concat, Or -from .return_ import Approve, Reject +from .return_ import Approve from .seq import Seq from .subroutine import SubroutineFnWrapper from .txn import Txn @@ -46,11 +45,15 @@ class ProgramNode(NamedTuple): + """ """ + condition: Expr branch: Expr class ABIRouter: + """ """ + def __init__(self) -> None: self.approvalIfThen: List[ProgramNode] = [] self.clearStateIfThen: List[ProgramNode] = [] @@ -61,6 +64,7 @@ def __parseConditions( onCompletes: List[EnumInt], creation: bool, ) -> Tuple[List[Expr], List[Expr]]: + """ """ # Check if it is a *CREATION* approvalConds: List[Expr] = [Txn.application_id() == Int(0)] if creation else [] clearStateConds: List[Expr] = [] @@ -113,6 +117,7 @@ def __parseConditions( @staticmethod def __wrapHandler(isMethod: bool, branch: Union[SubroutineFnWrapper, Expr]) -> Expr: + """""" exprList: List[Expr] = [] if not isMethod: if ( @@ -157,6 +162,7 @@ def __wrapHandler(isMethod: bool, branch: Union[SubroutineFnWrapper, Expr]) -> E def __appendToAST( self, approvalConds: List[Expr], clearConds: List[Expr], branch: Expr ) -> None: + """ """ if len(approvalConds) > 0: self.approvalIfThen.append( ProgramNode( @@ -179,6 +185,7 @@ def onBareAppCall( *, creation: bool = False, ) -> None: + """ """ ocList: List[EnumInt] = ( cast(List[EnumInt], onCompletes) if isinstance(onCompletes, list) @@ -197,6 +204,7 @@ def onMethodCall( onComplete: EnumInt = OnComplete.NoOp, creation: bool = False, ) -> None: + """ """ ocList: List[EnumInt] = [cast(EnumInt, onComplete)] approvalConds, clearConds = ABIRouter.__parseConditions( mReg=methodAppCall, onCompletes=ocList, creation=creation @@ -208,6 +216,7 @@ def onMethodCall( def __astConstruct( astList: List[ProgramNode], ) -> Expr: + """ """ if len(astList) == 0: raise TealInputError("ABIRouter: Cannot build program with an empty AST") @@ -216,6 +225,7 @@ def __astConstruct( return program def buildProgram(self) -> Tuple[Expr, Expr]: + """ """ return ( ABIRouter.__astConstruct(self.approvalIfThen), ABIRouter.__astConstruct(self.clearStateIfThen), From 47e684cd6ccfdfe83ac69b7c3e35281b267d601b Mon Sep 17 00:00:00 2001 From: Hang Su Date: Wed, 2 Mar 2022 10:48:46 -0500 Subject: [PATCH 034/188] move abi-router in abi dir --- pyteal/ast/__init__.py | 2 - pyteal/ast/abi/__init__.py | 2 + pyteal/ast/{abirouter.py => abi/router.py} | 44 +++++++++---------- .../{abirouter_test.py => abi/router_test.py} | 0 4 files changed, 24 insertions(+), 24 deletions(-) rename pyteal/ast/{abirouter.py => abi/router.py} (89%) rename pyteal/ast/{abirouter_test.py => abi/router_test.py} (100%) diff --git a/pyteal/ast/__init__.py b/pyteal/ast/__init__.py index bd19141d5..e94606253 100644 --- a/pyteal/ast/__init__.py +++ b/pyteal/ast/__init__.py @@ -114,7 +114,6 @@ from .for_ import For from .break_ import Break from .continue_ import Continue -from .abirouter import ABIRouter # misc from .scratch import ( @@ -227,7 +226,6 @@ "Return", "Approve", "Reject", - "ABIRouter", "Subroutine", "SubroutineDefinition", "SubroutineDeclaration", diff --git a/pyteal/ast/abi/__init__.py b/pyteal/ast/abi/__init__.py index df4a814f4..426d4b4b3 100644 --- a/pyteal/ast/abi/__init__.py +++ b/pyteal/ast/abi/__init__.py @@ -3,6 +3,7 @@ from .uint import Uint, Byte, Uint8, Uint16, Uint32, Uint64 from .tuple import Tuple from .array import StaticArray, DynamicArray +from .router import Router __all__ = [ "Type", @@ -17,4 +18,5 @@ "Tuple", "StaticArray", "DynamicArray", + "Router", ] diff --git a/pyteal/ast/abirouter.py b/pyteal/ast/abi/router.py similarity index 89% rename from pyteal/ast/abirouter.py rename to pyteal/ast/abi/router.py index 0701131c5..49a9974d2 100644 --- a/pyteal/ast/abirouter.py +++ b/pyteal/ast/abi/router.py @@ -2,20 +2,20 @@ from pyteal.ast.cond import Cond -from ..config import METHOD_APP_ARG_NUM_LIMIT, RETURN_EVENT_SELECTOR -from ..errors import TealInputError -from ..types import TealType - -from .app import OnComplete -from .expr import Expr -from .int import EnumInt, Int -from .methodsig import MethodSignature -from .unaryexpr import Log -from .naryexpr import And, Concat, Or -from .return_ import Approve -from .seq import Seq -from .subroutine import SubroutineFnWrapper -from .txn import Txn +from ...config import METHOD_APP_ARG_NUM_LIMIT, RETURN_EVENT_SELECTOR +from ...errors import TealInputError +from ...types import TealType + +from ..app import OnComplete +from ..expr import Expr +from ..int import EnumInt, Int +from ..methodsig import MethodSignature +from ..unaryexpr import Log +from ..naryexpr import And, Concat, Or +from ..return_ import Approve +from ..seq import Seq +from ..subroutine import SubroutineFnWrapper +from ..txn import Txn # NOTE this should sit in `abi` directory, still waiting on abi to be merged in @@ -51,7 +51,7 @@ class ProgramNode(NamedTuple): branch: Expr -class ABIRouter: +class Router: """ """ def __init__(self) -> None: @@ -191,10 +191,10 @@ def onBareAppCall( if isinstance(onCompletes, list) else [cast(EnumInt, onCompletes)] ) - approvalConds, clearConds = ABIRouter.__parseConditions( + approvalConds, clearConds = Router.__parseConditions( mReg=None, onCompletes=ocList, creation=creation ) - branch = ABIRouter.__wrapHandler(False, bareAppCall) + branch = Router.__wrapHandler(False, bareAppCall) self.__appendToAST(approvalConds, clearConds, branch) def onMethodCall( @@ -206,10 +206,10 @@ def onMethodCall( ) -> None: """ """ ocList: List[EnumInt] = [cast(EnumInt, onComplete)] - approvalConds, clearConds = ABIRouter.__parseConditions( + approvalConds, clearConds = Router.__parseConditions( mReg=methodAppCall, onCompletes=ocList, creation=creation ) - branch = ABIRouter.__wrapHandler(True, methodAppCall) + branch = Router.__wrapHandler(True, methodAppCall) self.__appendToAST(approvalConds, clearConds, branch) @staticmethod @@ -227,9 +227,9 @@ def __astConstruct( def buildProgram(self) -> Tuple[Expr, Expr]: """ """ return ( - ABIRouter.__astConstruct(self.approvalIfThen), - ABIRouter.__astConstruct(self.clearStateIfThen), + Router.__astConstruct(self.approvalIfThen), + Router.__astConstruct(self.clearStateIfThen), ) -ABIRouter.__module__ = "pyteal" +Router.__module__ = "pyteal" diff --git a/pyteal/ast/abirouter_test.py b/pyteal/ast/abi/router_test.py similarity index 100% rename from pyteal/ast/abirouter_test.py rename to pyteal/ast/abi/router_test.py From 0d2dfcd946236bdce44bb51101b8ffc204b7c35a Mon Sep 17 00:00:00 2001 From: Hang Su Date: Thu, 3 Mar 2022 11:09:14 -0500 Subject: [PATCH 035/188] minor --- pyteal/__init__.pyi | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyteal/__init__.pyi b/pyteal/__init__.pyi index f0dcada43..862fa418d 100644 --- a/pyteal/__init__.pyi +++ b/pyteal/__init__.pyi @@ -124,9 +124,9 @@ __all__ = [ "Op", "Or", "Pop", + "RETURN_EVENT_SELECTOR", "Reject", "Return", - "RETURN_EVENT_SELECTOR", "ScratchIndex", "ScratchLoad", "ScratchSlot", @@ -171,5 +171,6 @@ __all__ = [ "UnaryExpr", "While", "WideRatio", + "abi", "compileTeal", ] From dd9abff546345e72a8ccd98cb439429904da47ba Mon Sep 17 00:00:00 2001 From: Hang Su Date: Wed, 30 Mar 2022 18:00:16 -0400 Subject: [PATCH 036/188] 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 037/188] 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 038/188] 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 039/188] 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 040/188] 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 041/188] 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 042/188] 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 043/188] 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 044/188] 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 045/188] 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 046/188] 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 047/188] 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 048/188] 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 049/188] 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 050/188] 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 051/188] 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 052/188] 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 aa938f967c20ad06465add4022ec4f36264fe530 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Thu, 7 Apr 2022 15:45:06 -0400 Subject: [PATCH 053/188] update with latest abi impl --- pyteal/ast/abi/router.py | 56 ++++++++++++++++++++-------------------- pyteal/config.py | 6 ++--- 2 files changed, 31 insertions(+), 31 deletions(-) diff --git a/pyteal/ast/abi/router.py b/pyteal/ast/abi/router.py index 49a9974d2..531c4a4b6 100644 --- a/pyteal/ast/abi/router.py +++ b/pyteal/ast/abi/router.py @@ -1,21 +1,24 @@ -from typing import List, NamedTuple, Tuple, Union, cast +from typing import List, Tuple, Union, cast +from dataclasses import dataclass -from pyteal.ast.cond import Cond - -from ...config import METHOD_APP_ARG_NUM_LIMIT, RETURN_EVENT_SELECTOR +from ...config import METHOD_ARG_NUM_LIMIT from ...errors import TealInputError from ...types import TealType - -from ..app import OnComplete -from ..expr import Expr -from ..int import EnumInt, Int -from ..methodsig import MethodSignature -from ..unaryexpr import Log -from ..naryexpr import And, Concat, Or -from ..return_ import Approve -from ..seq import Seq -from ..subroutine import SubroutineFnWrapper -from ..txn import Txn +from .. import ( + Cond, + OnComplete, + Expr, + EnumInt, + Int, + MethodSignature, + And, + Or, + Approve, + Seq, + SubroutineFnWrapper, + Txn, +) +from . import MethodReturn # NOTE this should sit in `abi` directory, still waiting on abi to be merged in @@ -44,9 +47,8 @@ """ -class ProgramNode(NamedTuple): - """ """ - +@dataclass +class ProgramNode: condition: Expr branch: Expr @@ -77,9 +79,7 @@ def __parseConditions( And( Txn.application_args[0] == MethodSignature(mReg.name()), Txn.application_args.length() - == Int( - max(1 + mReg.subroutine.argumentCount(), METHOD_APP_ARG_NUM_LIMIT) - ), + == Int(max(1 + mReg.subroutine.argumentCount(), METHOD_ARG_NUM_LIMIT)), ) if mReg is not None else Txn.application_args.length() == Int(0) @@ -140,18 +140,18 @@ def __wrapHandler(isMethod: bool, branch: Union[SubroutineFnWrapper, Expr]) -> E if isinstance(branch, SubroutineFnWrapper) and branch.has_return(): # TODO need to encode/decode things execBranchArgs: List[Expr] = [] - if branch.subroutine.argumentCount() >= METHOD_APP_ARG_NUM_LIMIT: + if branch.subroutine.argumentCount() >= METHOD_ARG_NUM_LIMIT: # NOTE decode (if arg num > 15 need to de-tuple 15th (last) argument) pass else: pass - exprList.append( - # TODO this line can be changed to method-return in ABI side - Log(Concat(RETURN_EVENT_SELECTOR, branch(*execBranchArgs))) - if branch.type_of() != TealType.none - else branch(*execBranchArgs) - ) + # exprList.append( + # TODO this line can be changed to method-return in ABI side + # MethodReturn(branch(*execBranchArgs)) + # if branch.type_of() != TealType.none + # else branch(*execBranchArgs) + # ) else: raise TealInputError( "For method call: should only register Subroutine with return" diff --git a/pyteal/config.py b/pyteal/config.py index 6c8d7a0ad..304993bb2 100644 --- a/pyteal/config.py +++ b/pyteal/config.py @@ -1,6 +1,3 @@ -from .ast.bytes import Bytes - - # Maximum size of an atomic transaction group. MAX_GROUP_SIZE = 16 @@ -9,3 +6,6 @@ # Method return selector in base16 RETURN_METHOD_SELECTOR = "0x151F7C75" + +# Method argument number limit +METHOD_ARG_NUM_LIMIT = 15 From 80c4d504c3fb7dbb6127d2439f00f91119ab5c9a Mon Sep 17 00:00:00 2001 From: Hang Su Date: Thu, 7 Apr 2022 15:53:37 -0400 Subject: [PATCH 054/188] minors --- pyteal/__init__.py | 10 ++++++++-- pyteal/__init__.pyi | 10 ++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/pyteal/__init__.py b/pyteal/__init__.py index 7e8231ed9..2c9af82c1 100644 --- a/pyteal/__init__.py +++ b/pyteal/__init__.py @@ -12,7 +12,12 @@ ) from .types import TealType from .errors import TealInternalError, TealTypeError, TealInputError, TealCompileError -from .config import MAX_GROUP_SIZE, NUM_SLOTS, RETURN_EVENT_SELECTOR +from .config import ( + MAX_GROUP_SIZE, + NUM_SLOTS, + RETURN_METHOD_SELECTOR, + METHOD_ARG_NUM_LIMIT, +) # begin __all__ __all__ = ( @@ -32,7 +37,8 @@ "TealCompileError", "MAX_GROUP_SIZE", "NUM_SLOTS", - "RETURN_EVENT_SELECTOR", + "RETURN_METHOD_SELECTOR", + "METHOD_ARG_NUM_LIMIT", ] ) # end __all__ diff --git a/pyteal/__init__.pyi b/pyteal/__init__.pyi index 12b223171..a17e5daf3 100644 --- a/pyteal/__init__.pyi +++ b/pyteal/__init__.pyi @@ -15,7 +15,12 @@ from .compiler import ( ) from .types import TealType from .errors import TealInternalError, TealTypeError, TealInputError, TealCompileError -from .config import MAX_GROUP_SIZE, NUM_SLOTS, RETURN_EVENT_SELECTOR +from .config import ( + MAX_GROUP_SIZE, + NUM_SLOTS, + RETURN_METHOD_SELECTOR, + METHOD_ARG_NUM_LIMIT, +) __all__ = [ "AccountParam", @@ -107,6 +112,7 @@ __all__ = [ "Lt", "MAX_GROUP_SIZE", "MAX_TEAL_VERSION", + "METHOD_ARG_NUM_LIMIT", "MIN_TEAL_VERSION", "MaybeValue", "MethodSignature", @@ -126,7 +132,7 @@ __all__ = [ "OptimizeOptions", "Or", "Pop", - "RETURN_EVENT_SELECTOR", + "RETURN_METHOD_SELECTOR", "Reject", "Return", "ScratchIndex", From 79492e72a9d14c8098d02655b053ce1182b52745 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Thu, 7 Apr 2022 16:17:37 -0400 Subject: [PATCH 055/188] minor --- pyteal/ast/abi/router.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyteal/ast/abi/router.py b/pyteal/ast/abi/router.py index 531c4a4b6..ff2071156 100644 --- a/pyteal/ast/abi/router.py +++ b/pyteal/ast/abi/router.py @@ -79,7 +79,7 @@ def __parseConditions( And( Txn.application_args[0] == MethodSignature(mReg.name()), Txn.application_args.length() - == Int(max(1 + mReg.subroutine.argumentCount(), METHOD_ARG_NUM_LIMIT)), + == Int(1 + max(mReg.subroutine.argumentCount(), METHOD_ARG_NUM_LIMIT)), ) if mReg is not None else Txn.application_args.length() == Int(0) From f0101d032824eff7aee06224d5c1342fc29716a7 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Tue, 12 Apr 2022 17:18:20 -0400 Subject: [PATCH 056/188] 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 057/188] 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 058/188] 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 059/188] 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 060/188] 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 061/188] 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 062/188] 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 063/188] 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 064/188] 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 065/188] 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 066/188] 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 067/188] 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 068/188] 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 069/188] 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 070/188] 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 071/188] 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 67831ffc3826667a3ecd397ce1f12b52da7bf2e0 Mon Sep 17 00:00:00 2001 From: Ben Guidarelli Date: Thu, 28 Apr 2022 09:54:00 -0400 Subject: [PATCH 072/188] merging feature/abi to abi-router (#305) * merging feature/abi * missed one * make router use absolute imports * linter happy? --- .flake8 | 31 + .github/workflows/build.yml | 34 +- .gitignore | 10 +- .readthedocs.yaml | 29 + .readthedocs.yml | 23 - CHANGELOG.md | 5 + CONTRIBUTING.md | 62 +- Makefile | 49 + README.md | 49 +- docs/conf.py | 2 +- docs/requirements.txt | 3 +- examples/application/vote_deploy.py | 1 - examples/signature/periodic_payment_deploy.py | 10 +- examples/signature/recurring_swap_deploy.py | 16 +- pyteal/__init__.py | 21 +- pyteal/__init__.pyi | 21 +- pyteal/ast/__init__.py | 92 +- pyteal/ast/abi/__init__.py | 25 +- pyteal/ast/abi/address.py | 33 + pyteal/ast/abi/address_test.py | 81 + pyteal/ast/abi/array_base.py | 30 +- pyteal/ast/abi/array_base_test.py | 69 +- pyteal/ast/abi/array_dynamic.py | 12 +- pyteal/ast/abi/array_dynamic_test.py | 119 +- pyteal/ast/abi/array_static.py | 12 +- pyteal/ast/abi/array_static_test.py | 111 +- pyteal/ast/abi/bool.py | 22 +- pyteal/ast/abi/bool_test.py | 168 +- pyteal/ast/abi/method_return.py | 14 +- pyteal/ast/abi/method_return_test.py | 28 +- pyteal/ast/abi/router.py | 35 +- pyteal/ast/abi/string.py | 37 + pyteal/ast/abi/string_test.py | 85 + pyteal/ast/abi/tuple.py | 32 +- pyteal/ast/abi/tuple_test.py | 265 +-- pyteal/ast/abi/type.py | 10 +- pyteal/ast/abi/type_test.py | 39 +- pyteal/ast/abi/uint.py | 26 +- pyteal/ast/abi/uint_test.py | 155 +- pyteal/ast/abi/util.py | 32 +- pyteal/ast/abi/util_test.py | 63 +- pyteal/ast/acct.py | 13 +- pyteal/ast/acct_test.py | 81 +- pyteal/ast/addr.py | 8 +- pyteal/ast/addr_test.py | 26 +- pyteal/ast/app.py | 16 +- pyteal/ast/app_test.py | 617 +++--- pyteal/ast/arg.py | 12 +- pyteal/ast/arg_test.py | 43 +- pyteal/ast/array.py | 2 +- pyteal/ast/assert_.py | 8 +- pyteal/ast/assert_test.py | 39 +- pyteal/ast/asset.py | 11 +- pyteal/ast/asset_test.py | 677 ++++--- pyteal/ast/binaryexpr.py | 10 +- pyteal/ast/binaryexpr_test.py | 1313 +++++++------ pyteal/ast/break_.py | 10 +- pyteal/ast/break_test.py | 27 +- pyteal/ast/bytes.py | 12 +- pyteal/ast/bytes_test.py | 147 +- pyteal/ast/cond.py | 12 +- pyteal/ast/cond_test.py | 106 +- pyteal/ast/continue_.py | 10 +- pyteal/ast/continue_test.py | 27 +- pyteal/ast/err.py | 8 +- pyteal/ast/err_test.py | 12 +- pyteal/ast/expr.py | 46 +- pyteal/ast/for_.py | 12 +- pyteal/ast/for_test.py | 139 +- pyteal/ast/gaid.py | 14 +- pyteal/ast/gaid_test.py | 47 +- pyteal/ast/gitxn.py | 11 +- pyteal/ast/gitxn_test.py | 52 +- pyteal/ast/gload.py | 14 +- pyteal/ast/gload_test.py | 89 +- pyteal/ast/global_.py | 10 +- pyteal/ast/global_test.py | 123 +- pyteal/ast/gtxn.py | 14 +- pyteal/ast/gtxn_test.py | 45 +- pyteal/ast/if_.py | 10 +- pyteal/ast/if_test.py | 163 +- pyteal/ast/int.py | 10 +- pyteal/ast/int_test.py | 35 +- pyteal/ast/itxn.py | 14 +- pyteal/ast/itxn_test.py | 99 +- pyteal/ast/leafexpr.py | 2 +- pyteal/ast/maybe.py | 13 +- pyteal/ast/maybe_test.py | 49 +- pyteal/ast/methodsig.py | 9 +- pyteal/ast/methodsig_test.py | 16 +- pyteal/ast/multi.py | 14 +- pyteal/ast/multi_test.py | 120 +- pyteal/ast/naryexpr.py | 10 +- pyteal/ast/naryexpr_test.py | 215 +-- pyteal/ast/nonce.py | 12 +- pyteal/ast/nonce_test.py | 149 +- pyteal/ast/return_.py | 12 +- pyteal/ast/return_test.py | 75 +- pyteal/ast/scratch.py | 18 +- pyteal/ast/scratch_test.py | 118 +- pyteal/ast/scratchvar.py | 8 +- pyteal/ast/scratchvar_test.py | 179 +- pyteal/ast/seq.py | 10 +- pyteal/ast/seq_test.py | 74 +- pyteal/ast/subroutine.py | 16 +- pyteal/ast/subroutine_test.py | 364 ++-- pyteal/ast/substring.py | 17 +- pyteal/ast/substring_test.py | 237 ++- pyteal/ast/ternaryexpr.py | 12 +- pyteal/ast/ternaryexpr_test.py | 163 +- pyteal/ast/tmpl.py | 9 +- pyteal/ast/tmpl_test.py | 37 +- pyteal/ast/txn.py | 16 +- pyteal/ast/txn_test.py | 232 +-- pyteal/ast/unaryexpr.py | 10 +- pyteal/ast/unaryexpr_test.py | 358 ++-- pyteal/ast/while_.py | 11 +- pyteal/ast/while_test.py | 107 +- pyteal/ast/widemath.py | 10 +- pyteal/compiler/__init__.py | 4 +- pyteal/compiler/compiler.py | 20 +- pyteal/compiler/compiler_test.py | 704 ++++--- pyteal/compiler/constants.py | 6 +- pyteal/compiler/constants_test.py | 755 ++++---- pyteal/compiler/flatten.py | 8 +- pyteal/compiler/flatten_test.py | 609 +++--- pyteal/compiler/optimizer/__init__.py | 5 +- pyteal/compiler/optimizer/optimizer.py | 6 +- pyteal/compiler/optimizer/optimizer_test.py | 234 +-- pyteal/compiler/scratchslots.py | 8 +- pyteal/compiler/scratchslots_test.py | 405 ++-- pyteal/compiler/sort.py | 4 +- pyteal/compiler/sort_test.py | 42 +- pyteal/compiler/subroutines.py | 8 +- pyteal/compiler/subroutines_test.py | 1702 ++++++++--------- pyteal/errors.py | 2 +- pyteal/ir/__init__.py | 16 +- pyteal/ir/tealblock.py | 15 +- pyteal/ir/tealblock_test.py | 219 ++- pyteal/ir/tealcomponent.py | 2 +- pyteal/ir/tealcomponent_test.py | 14 +- pyteal/ir/tealconditionalblock.py | 6 +- pyteal/ir/tealconditionalblock_test.py | 52 +- pyteal/ir/teallabel.py | 6 +- pyteal/ir/tealop.py | 18 +- pyteal/ir/tealsimpleblock.py | 6 +- pyteal/ir/tealsimpleblock_test.py | 22 +- pyteal/types.py | 2 +- pyteal/types_test.py | 9 +- pyteal/util.py | 2 +- requirements.txt | 5 - scripts/generate_init.py | 5 +- setup.py | 13 +- tests/compile_test.py | 60 +- tests/pass_by_ref_test.py | 506 ++--- tests/user_guide_test.py | 59 +- 156 files changed, 7543 insertions(+), 6873 deletions(-) create mode 100644 .flake8 create mode 100644 .readthedocs.yaml delete mode 100644 .readthedocs.yml create mode 100644 Makefile create mode 100644 pyteal/ast/abi/address.py create mode 100644 pyteal/ast/abi/address_test.py create mode 100644 pyteal/ast/abi/string.py create mode 100644 pyteal/ast/abi/string_test.py delete mode 100644 requirements.txt diff --git a/.flake8 b/.flake8 new file mode 100644 index 000000000..227d3d690 --- /dev/null +++ b/.flake8 @@ -0,0 +1,31 @@ +[flake8] +ignore = + E203, + E241, + W291, + E302, + E501, + W503, + E741, + +per-file-ignores = + pyteal/compiler/optimizer/__init__.py: F401 + examples/application/asset.py: F403, F405 + examples/application/security_token.py: F403, F405 + examples/application/vote.py: F403, F405 + examples/signature/atomic_swap.py: F403, F405 + examples/signature/basic.py: F403, F405 + examples/signature/dutch_auction.py: F403, F405 + examples/signature/periodic_payment_deploy.py: F403, F405 + examples/signature/recurring_swap.py: F403, F405 + examples/signature/split.py: F403, F405 + examples/signature/periodic_payment.py: F403, F405 + 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 + +# from flake8-tidy-imports +ban-relative-imports = true + diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 387a326ed..4eb3bbd35 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -6,10 +6,11 @@ on: - v** branches: - master + jobs: build-test: runs-on: ubuntu-20.04 - container: python:${{ matrix.python }}-slim + container: python:${{ matrix.python }} strategy: matrix: python: ['3.10'] @@ -19,16 +20,11 @@ jobs: uses: actions/checkout@v2 with: fetch-depth: 0 - - name: Install pip dependencies - run: | - pip install -r requirements.txt - pip install -e . + - name: Install python dependencies + run: make setup-development - name: Build and Test - run: | - python scripts/generate_init.py --check - black --check . - mypy pyteal - pytest + run: make build-and-test + build-docset: runs-on: ubuntu-20.04 container: python:3.10 # Needs `make`, can't be slim @@ -37,22 +33,16 @@ jobs: uses: actions/checkout@v2 with: fetch-depth: 0 - - name: Install pip dependencies - run: | - pip install -r requirements.txt - pip install -r docs/requirements.txt - pip install doc2dash + - name: Install python dependencies + run: make setup-docs - name: Make docs - run: | - cd docs - make html - 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 + run: make bundle-docs - name: Archive docset uses: actions/upload-artifact@v2 with: name: pyteal.docset path: docs/pyteal.docset.tar.gz + upload-to-pypi: runs-on: ubuntu-20.04 container: python:3.10 @@ -64,9 +54,9 @@ jobs: with: fetch-depth: 0 - name: Install dependencies - run: pip install wheel + run: make setup-wheel - name: Build package - run: python setup.py sdist bdist_wheel + run: make bdist-wheel - name: Release uses: pypa/gh-action-pypi-publish@release/v1 with: diff --git a/.gitignore b/.gitignore index 3ede576e5..b96db3ef2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,3 @@ -.DS_Store - # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] @@ -54,8 +52,8 @@ coverage.xml .pytest_cache/ # Tests generating TEAL output to compared against an expected example. -tests/teal/*.teal -!tests/teal/*_expected.teal +tests/**/*.teal +!tests/**/*_expected.teal # Translations *.mo @@ -76,6 +74,7 @@ instance/ # Sphinx documentation docs/_build/ +pyteal.docset* # PyBuilder target/ @@ -133,3 +132,6 @@ dmypy.json # IDE .idea .vscode + +# mac OS +.DS_Store diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 000000000..ff6059752 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,29 @@ +# .readthedocs.yaml +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +# Set the version of Python and other tools you might need +build: + os: ubuntu-20.04 + tools: + python: "3.10" + # You can also specify other tool versions: + # nodejs: "16" + # rust: "1.55" + # golang: "1.17" + +# Build documentation in the docs/ directory with Sphinx +sphinx: + configuration: docs/conf.py + +# If using Sphinx, optionally build your docs in additional formats such as PDF +# formats: +# - pdf + +# Optionally declare the Python requirements required to build your docs +python: + install: + - requirements: docs/requirements.txt diff --git a/.readthedocs.yml b/.readthedocs.yml deleted file mode 100644 index ae547809f..000000000 --- a/.readthedocs.yml +++ /dev/null @@ -1,23 +0,0 @@ -# readthedocs.yml -# Read the Docs configuration file -# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details - -# Required -version: 2 - -# Build documentation in the docs/ directory with Sphinx -sphinx: - configuration: docs/conf.py - -# Build documentation with MkDocs -#mkdocs: -# configuration: mkdocs.yml - -# Optionally build your docs in additional formats such as PDF and ePub -formats: all - -# Optionally set the version of Python and requirements required to build your docs -python: - version: 3.10 - install: - - requirements: docs/requirements.txt diff --git a/CHANGELOG.md b/CHANGELOG.md index 493aa5b84..3becbbf89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# 0.11.1 + +## Fixed +* Fix readthedocs build issue introduced in v0.11.0 ([#276](https://github.com/algorand/pyteal/pull/276), [#279](https://github.com/algorand/pyteal/pull/279)). + # 0.11.0 ## Added diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3ebd603e0..eabd337fb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,29 +2,27 @@ If you are interested in contributing to the project, we welcome and thank you. We want to make the best decentralized and effective blockchain platform available and we appreciate your willingness to help us. - - -# Filing Issues +## Filing Issues Did you discover a bug? Do you have a feature request? Filing issues is an easy way anyone can contribute and helps us improve PyTeal. We use GitHub Issues to track all known bugs and feature requests. -Before logging an issue be sure to check current issues, check the [Developer Frequently Asked Questions](https://developer.algorand.org/docs/developer-faq) and [GitHub issues][issues_url] to see if your issue is described there. +Before logging an issue be sure to check current issues, check the [open GitHub issues][issues_url] to see if your issue is described there. If you’d like to contribute to any of the repositories, please file a [GitHub issue][issues_url] using the issues menu item. Make sure to specify whether you are describing a bug or a new enhancement using the **Bug report** or **Feature request** button. See the GitHub help guide for more information on [filing an issue](https://help.github.com/en/articles/creating-an-issue). -# Contribution Model +## Contribution Model -For each of our repositories we use the same model for contributing code. Developers wanting to contribute must create pull requests. This process is described in the GitHub [Creating a pull request from a fork](https://help.github.com/en/articles/creating-a-pull-request-from-a-fork) documentation. Each pull request should be initiated against the master branch in the Algorand repository. After a pull request is submitted the core development team will review the submission and communicate with the developer using the comments sections of the PR. After the submission is reviewed and approved, it will be merged into the master branch of the source. These changes will be merged to our release branch on the next viable release date. +For each of our repositories we use the same model for contributing code. Developers wanting to contribute must create pull requests. This process is described in the GitHub [Creating a pull request from a fork](https://help.github.com/en/articles/creating-a-pull-request-from-a-fork) documentation. Each pull request should be initiated against the master branch in the Algorand repository. After a pull request is submitted the core development team will review the submission and communicate with the developer using the comments sections of the PR. After the submission is reviewed and approved, it will be merged into the master branch of the source. These changes will be merged to our release branch on the next viable release date. -# Code Guidelines +## Code Guidelines We make a best-effort attempt to adhere to [PEP 8 - Style Guide for Python Code](https://www.python.org/dev/peps/pep-0008/). Keep the following context in mind: * Our default stance is to run linter checks during the build process. * Notable exception: [naming conventions](https://peps.python.org/pep-0008/#naming-conventions). -## Naming Convention Guidelines +### Naming Convention Guidelines Since PyTeal aims for backwards compatibility, it's _not_ straightforward to change naming conventions in public APIs. Consequently, the repo contains some deviations from PEP 8 naming conventions. In order to retain a consistent style, we prefer to continue deviating from PEP 8 naming conventions in the following cases. We try to balance minimizing exceptions against providing a consistent style for existing software. @@ -32,3 +30,51 @@ In order to retain a consistent style, we prefer to continue deviating from PEP * Factory methods - Define following [class name](https://peps.python.org/pep-0008/#class-names) conventions. Example: https://github.com/algorand/pyteal/blob/7c953f600113abcb9a31df68165b61a2c897f591/pyteal/ast/ternaryexpr.py#L63 Since it's challenging to enforce these exceptions with a linter, we rely on PR creators and reviewers to make a best-effort attempt to enforce agreed upon naming conventions. + +### Module Guidelines + +Every directory containing source code should be a Python module, meaning it should have an `__init__.py` file. This `__init__.py` file is responsible for exporting all public objects (i.e. classes, functions, and constants) for use outside of that module. + +Modules may be created inside of other modules, in which case the deeper module is called a submodule or child module, and the module that contains it is called the parent module. For example, `pyteal` is the parent module to `pyteal.ast`. + +A sibling module is defined as a different child module of the parent module. For example, `pyteal.ast` and `pyteal.ir` are sibling modules. + +### Import Guidelines + +#### 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. + +With regard to modules, there are two ways to import an object from this codebase: + +* When importing an object from the same module or a parent module, you should import the full path of the source file that defines the object. For example: + * (Import from same module): If `pyteal/ast/seq.py` needs to import `Expr` defined in `pyteal/ast/expr.py`, it should use `from pyteal.ast.expr import Expr`. **DO NOT** use `from pyteal.ast import Expr` or `from pyteal import Expr`, as this will cause an unnecessary circular dependency. + * (Import from parent module): If `pyteal/ast/seq.py` needs to import `TealType` defined in `pyteal/types.py`, it should use `from pyteal.types import TealType`. **DO NOT** use `from pyteal import TealType`, as this will cause an unnecessary circular dependency. + +* When importing an object from a child module or sibling module, you should import the entire module folder and access the desired object from there. Do not directly import the file that defines the object. For example: + * (Import from child module): If `pyteal/compiler/compiler.py` needs to import `OptimizeOptions` from `pyteal/compiler/optimizer/optimizer.py`, it should use `from pyteal.compiler.optimizer import OptimizeOptions`. **DO NOT** use `from pyteal.compiler.optimizer.optimizer import OptimizeOptions`, as this will bypass the file `pyteal/compiler/optimizer/__init__.py`, which carefully defines the exports for the `pyteal.compiler.optimizer` module. + * (Import from sibling module): If `pyteal/compiler/compiler.py` needs to import `Expr` defined in `pyteal/ast/expr.py`, it should use `from pyteal.ast import Expr`. **DO NOT** use `from pyteal import Expr`, as this will cause an unnecessary circular dependency, nor `from pyteal.ast.expr import Expr`, as this will bypass the file `pyteal/ast/__init__.py`, which carefully defines the + exports for the `pyteal.ast` module. + +When this approach is followed properly, circular dependencies can happen in two ways: +1. **Intra-module circular dependencies**, e.g. `m1/a.py` imports `m1/b.py` which imports `m1/a.py`. + When this happens, normally one of the files only needs to import the other to implement something, + so the import statements can be moved from the top of the file to the body of the function that + needs them. This breaks the import cycle. + +2. **Inter-module circular dependencies**, e.g. `m1/a.py` imports module `m1/m2/__init__.py` which + imports `m1/m2/x.py` which imports `m1/a.py`. To avoid this, make sure that any objects/files in + `m1` that need to be exposed to deeper modules do not rely on any objects from that deeper module. + If that isn’t possible, then perhaps `m1/a.py` belongs in the `m1/m2/` module. + +#### In Test Code + +Test code is typically any file that ends with `_test.py` or is in the top-level `tests` folder. In +order to have a strong guarantee that we are testing in a similar environment to consumers of this +library, test code is encouraged to import _only_ the top-level PyTeal module, either with +`from pyteal import X, Y, Z` or `import pyteal as pt`. + +This way we can be sure all objects that should be publicly exported are in fact accessible from the top-level module. + +The only exception to this should be if the tests are for a non-exported object, in which case it is +necessary to import the object according to the runtime rules in the previous section. diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..8e77f54fd --- /dev/null +++ b/Makefile @@ -0,0 +1,49 @@ +setup-development: + pip install -e.[development] + +setup-docs: setup-development + pip install -r docs/requirements.txt + pip install doc2dash + +setup-wheel: + pip install wheel + +bdist-wheel: + python setup.py sdist bdist_wheel + +bundle-docs-clean: + rm -rf docs/pyteal.docset + +bundle-docs: bundle-docs-clean + cd docs && \ + make html && \ + 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 + +check-generate-init: + python -m scripts.generate_init --check + +ALLPY = docs examples pyteal scripts tests *.py +black: + black --check $(ALLPY) + +flake8: + flake8 $(ALLPY) + +MYPY = pyteal scripts +mypy: + mypy $(MYPY) + +lint: black flake8 mypy + +test-unit: + pytest + +build-and-test: check-generate-init lint test-unit + +# Extras: +coverage: + pytest --cov-report html --cov=pyteal diff --git a/README.md b/README.md index 0e9f57858..61db520c4 100644 --- a/README.md +++ b/README.md @@ -1,34 +1,35 @@ -![PyTeal logo](https://github.com/algorand/pyteal/blob/master/docs/pyteal.png?raw=true) + +![PyTeal logo](https://github.com/algorand/pyteal/blob/master/docs/pyteal.png?raw=true) # PyTeal: Algorand Smart Contracts in Python -[![Build Status](https://travis-ci.com/algorand/pyteal.svg?branch=master)](https://travis-ci.com/algorand/pyteal) +[![Build Status](https://github.com/algorand/pyteal/actions/workflows/build.yml/badge.svg)](https://github.com/algorand/pyteal/actions) [![PyPI version](https://badge.fury.io/py/pyteal.svg)](https://badge.fury.io/py/pyteal) [![Documentation Status](https://readthedocs.org/projects/pyteal/badge/?version=latest)](https://pyteal.readthedocs.io/en/latest/?badge=latest) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) -PyTeal is a Python language binding for [Algorand Smart Contracts (ASC1s)](https://developer.algorand.org/docs/features/asc1/). +PyTeal is a Python language binding for [Algorand Smart Contracts (ASC1s)](https://developer.algorand.org/docs/features/asc1/). -Algorand Smart Contracts are implemented using a new language that is stack-based, -called [Transaction Execution Approval Language (TEAL)](https://developer.algorand.org/docs/features/asc1/teal/). +Algorand Smart Contracts are implemented using a new language that is stack-based, +called [Transaction Execution Approval Language (TEAL)](https://developer.algorand.org/docs/features/asc1/teal/). -However, TEAL is essentially an assembly language. With PyTeal, developers can express smart contract logic purely using Python. +However, TEAL is essentially an assembly language. With PyTeal, developers can express smart contract logic purely using Python. PyTeal provides high level, functional programming style abstractions over TEAL and does type checking at construction time. -### Install +## Install PyTeal requires Python version >= 3.10. To manage multiple Python versions use tooling like [pyenv](https://github.com/pyenv/pyenv). -#### Recommended: Install from PyPi +### Recommended: Install from PyPi Install the latest official release from PyPi: * `pip install pyteal` -#### Install Latest Commit +### Install Latest Commit If needed, it's possible to install directly from the latest commit on master to use unreleased features: @@ -36,31 +37,41 @@ If needed, it's possible to install directly from the latest commit on master to * `pip install git+https://github.com/algorand/pyteal` -### Documentation +## Documentation * [PyTeal Docs](https://pyteal.readthedocs.io/) * `docs/` ([README](docs/README.md)) contains raw docs. -### Development Setup +## Development Setup Setup venv (one time): - * `python3 -m venv venv` + +* `python3 -m venv venv` Active venv: - * `. venv/bin/activate` (if your shell is bash/zsh) - * `. venv/bin/activate.fish` (if your shell is fish) -Pip install PyTeal in editable state - * `pip install -e .` +* `. venv/bin/activate` (if your shell is bash/zsh) +* `. venv/bin/activate.fish` (if your shell is fish) + +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\]` -Install dependencies: -* `pip install -r requirements.txt` - Type checking using mypy: + * `mypy pyteal` Run tests: + * `pytest` Format code: + * `black .` + +Lint using flake8: + +* `flake8 docs examples pyteal scripts tests *.py` diff --git a/docs/conf.py b/docs/conf.py index c32115eb2..ea5c005e6 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -19,7 +19,7 @@ # -- Project information ----------------------------------------------------- project = "PyTeal" -copyright = "2021, Algorand" +copyright = "2022, Algorand" author = "Algorand" diff --git a/docs/requirements.txt b/docs/requirements.txt index 957dc9c95..99582bbe8 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,2 +1,3 @@ -sphinx_rtd_theme +sphinx==4.2.0 +sphinx-rtd-theme==1.0.0 py-algorand-sdk diff --git a/examples/application/vote_deploy.py b/examples/application/vote_deploy.py index 09b9966b7..6dbec0a05 100644 --- a/examples/application/vote_deploy.py +++ b/examples/application/vote_deploy.py @@ -1,7 +1,6 @@ # based off https://github.com/algorand/docs/blob/cdf11d48a4b1168752e6ccaf77c8b9e8e599713a/examples/smart_contracts/v2/python/stateful_smart_contracts.py import base64 -import datetime from algosdk.future import transaction from algosdk import account, mnemonic diff --git a/examples/signature/periodic_payment_deploy.py b/examples/signature/periodic_payment_deploy.py index 6366e29a5..3882dc5ff 100644 --- a/examples/signature/periodic_payment_deploy.py +++ b/examples/signature/periodic_payment_deploy.py @@ -1,9 +1,13 @@ #!/usr/bin/env python3 -from pyteal import * -import uuid, params, base64 +import base64 +import params +import uuid + from algosdk import algod, transaction, account, mnemonic -from periodic_payment import periodic_payment, tmpl_amt +from periodic_payment import periodic_payment + +from pyteal import * # --------- compile & send transaction using Goal and Python SDK ---------- diff --git a/examples/signature/recurring_swap_deploy.py b/examples/signature/recurring_swap_deploy.py index 536237a52..1293f8b5c 100644 --- a/examples/signature/recurring_swap_deploy.py +++ b/examples/signature/recurring_swap_deploy.py @@ -1,11 +1,19 @@ #!/usr/bin/env python3 -from pyteal import * +import base64 from nacl import encoding, hash -from recurring_swap import recurring_swap, tmpl_provider -from algosdk import algod, account +import params +import re +import time +import uuid + +from algosdk import algod from algosdk.future import transaction -import uuid, params, re, base64, time + +from pyteal import * + +from recurring_swap import recurring_swap + # ------- generate provider's account ----------------------------------------------- key_fn = str(uuid.uuid4()) + ".key" diff --git a/pyteal/__init__.py b/pyteal/__init__.py index 2c9af82c1..cc4e7ca76 100644 --- a/pyteal/__init__.py +++ b/pyteal/__init__.py @@ -1,8 +1,8 @@ -from .ast import * -from .ast import __all__ as ast_all -from .ir import * -from .ir import __all__ as ir_all -from .compiler import ( +from pyteal.ast import * +from pyteal.ast import __all__ as ast_all +from pyteal.ir import * +from pyteal.ir import __all__ as ir_all +from pyteal.compiler import ( MAX_TEAL_VERSION, MIN_TEAL_VERSION, DEFAULT_TEAL_VERSION, @@ -10,9 +10,14 @@ compileTeal, OptimizeOptions, ) -from .types import TealType -from .errors import TealInternalError, TealTypeError, TealInputError, TealCompileError -from .config import ( +from pyteal.types import TealType +from pyteal.errors import ( + TealInternalError, + TealTypeError, + TealInputError, + TealCompileError, +) +from pyteal.config import ( MAX_GROUP_SIZE, NUM_SLOTS, RETURN_METHOD_SELECTOR, diff --git a/pyteal/__init__.pyi b/pyteal/__init__.pyi index a17e5daf3..1e1d18553 100644 --- a/pyteal/__init__.pyi +++ b/pyteal/__init__.pyi @@ -1,11 +1,11 @@ ## File generated from scripts/generate_init.py. ## DO NOT EDIT DIRECTLY -from .ast import * -from .ast import __all__ as ast_all -from .ir import * -from .ir import __all__ as ir_all -from .compiler import ( +from pyteal.ast import * +from pyteal.ast import __all__ as ast_all +from pyteal.ir import * +from pyteal.ir import __all__ as ir_all +from pyteal.compiler import ( MAX_TEAL_VERSION, MIN_TEAL_VERSION, DEFAULT_TEAL_VERSION, @@ -13,9 +13,14 @@ from .compiler import ( compileTeal, OptimizeOptions, ) -from .types import TealType -from .errors import TealInternalError, TealTypeError, TealInputError, TealCompileError -from .config import ( +from pyteal.types import TealType +from pyteal.errors import ( + TealInternalError, + TealTypeError, + TealInputError, + TealCompileError, +) +from pyteal.config import ( MAX_GROUP_SIZE, NUM_SLOTS, RETURN_METHOD_SELECTOR, diff --git a/pyteal/ast/__init__.py b/pyteal/ast/__init__.py index e94606253..43efc9004 100644 --- a/pyteal/ast/__init__.py +++ b/pyteal/ast/__init__.py @@ -1,34 +1,43 @@ # abstract types -from .expr import Expr +from pyteal.ast.expr import Expr # basic types -from .leafexpr import LeafExpr -from .addr import Addr -from .bytes import Bytes -from .int import Int, EnumInt +from pyteal.ast.leafexpr import LeafExpr +from pyteal.ast.addr import Addr +from pyteal.ast.bytes import Bytes +from pyteal.ast.int import Int, EnumInt +from pyteal.ast.methodsig import MethodSignature # properties -from .arg import Arg -from .txn import TxnType, TxnField, TxnExpr, TxnaExpr, TxnArray, TxnObject, Txn -from .gtxn import GtxnExpr, GtxnaExpr, TxnGroup, Gtxn -from .gaid import GeneratedID -from .gitxn import Gitxn, GitxnExpr, GitxnaExpr, InnerTxnGroup -from .gload import ImportScratchValue -from .global_ import Global, GlobalField -from .app import App, AppField, OnComplete, AppParam -from .asset import AssetHolding, AssetParam -from .acct import AccountParam +from pyteal.ast.arg import Arg +from pyteal.ast.txn import ( + TxnType, + TxnField, + TxnExpr, + TxnaExpr, + TxnArray, + TxnObject, + Txn, +) +from pyteal.ast.gtxn import GtxnExpr, GtxnaExpr, TxnGroup, Gtxn +from pyteal.ast.gaid import GeneratedID +from pyteal.ast.gitxn import Gitxn, GitxnExpr, GitxnaExpr, InnerTxnGroup +from pyteal.ast.gload import ImportScratchValue +from pyteal.ast.global_ import Global, GlobalField +from pyteal.ast.app import App, AppField, OnComplete, AppParam +from pyteal.ast.asset import AssetHolding, AssetParam +from pyteal.ast.acct import AccountParam # inner txns -from .itxn import InnerTxnBuilder, InnerTxn, InnerTxnAction +from pyteal.ast.itxn import InnerTxnBuilder, InnerTxn, InnerTxnAction # meta -from .array import Array -from .tmpl import Tmpl -from .nonce import Nonce +from pyteal.ast.array import Array +from pyteal.ast.tmpl import Tmpl +from pyteal.ast.nonce import Nonce # unary ops -from .unaryexpr import ( +from pyteal.ast.unaryexpr import ( UnaryExpr, Btoi, Itob, @@ -50,7 +59,7 @@ ) # binary ops -from .binaryexpr import ( +from pyteal.ast.binaryexpr import ( BinaryExpr, Minus, Div, @@ -89,47 +98,46 @@ ) # ternary ops -from .ternaryexpr import Divw, Ed25519Verify, SetBit, SetByte -from .substring import Substring, Extract, Suffix +from pyteal.ast.ternaryexpr import Divw, Ed25519Verify, SetBit, SetByte +from pyteal.ast.substring import Substring, Extract, Suffix # more ops -from .naryexpr import NaryExpr, Add, Mul, And, Or, Concat -from .widemath import WideRatio +from pyteal.ast.naryexpr import NaryExpr, Add, Mul, And, Or, Concat +from pyteal.ast.widemath import WideRatio # control flow -from .if_ import If -from .cond import Cond -from .seq import Seq -from .assert_ import Assert -from .err import Err -from .return_ import Return, Approve, Reject -from .subroutine import ( +from pyteal.ast.if_ import If +from pyteal.ast.cond import Cond +from pyteal.ast.seq import Seq +from pyteal.ast.assert_ import Assert +from pyteal.ast.err import Err +from pyteal.ast.return_ import Return, Approve, Reject +from pyteal.ast.subroutine import ( Subroutine, SubroutineDefinition, SubroutineDeclaration, SubroutineCall, SubroutineFnWrapper, ) -from .while_ import While -from .for_ import For -from .break_ import Break -from .continue_ import Continue +from pyteal.ast.while_ import While +from pyteal.ast.for_ import For +from pyteal.ast.break_ import Break +from pyteal.ast.continue_ import Continue # misc -from .scratch import ( +from pyteal.ast.scratch import ( ScratchIndex, ScratchLoad, ScratchSlot, ScratchStackStore, ScratchStore, ) -from .scratchvar import DynamicScratchVar, ScratchVar -from .maybe import MaybeValue -from .methodsig import MethodSignature -from .multi import MultiValue +from pyteal.ast.scratchvar import DynamicScratchVar, ScratchVar +from pyteal.ast.maybe import MaybeValue +from pyteal.ast.multi import MultiValue # abi -from . import abi +import pyteal.ast.abi as abi # noqa: I250 __all__ = [ "Expr", diff --git a/pyteal/ast/abi/__init__.py b/pyteal/ast/abi/__init__.py index e2d57bf29..ba495f0ac 100644 --- a/pyteal/ast/abi/__init__.py +++ b/pyteal/ast/abi/__init__.py @@ -1,6 +1,8 @@ -from .type import TypeSpec, BaseType, ComputedValue -from .bool import BoolTypeSpec, Bool -from .uint import ( +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.bool import BoolTypeSpec, Bool +from pyteal.ast.abi.uint import ( UintTypeSpec, Uint, ByteTypeSpec, @@ -14,7 +16,7 @@ Uint64TypeSpec, Uint64, ) -from .tuple import ( +from pyteal.ast.abi.tuple import ( TupleTypeSpec, Tuple, TupleElement, @@ -25,13 +27,18 @@ Tuple4, Tuple5, ) -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 .method_return import MethodReturn +from pyteal.ast.abi.array_base import ArrayTypeSpec, Array, ArrayElement +from pyteal.ast.abi.array_static import StaticArrayTypeSpec, StaticArray +from pyteal.ast.abi.array_dynamic import DynamicArrayTypeSpec, DynamicArray +from pyteal.ast.abi.util import type_spec_from_annotation +from pyteal.ast.abi.method_return import MethodReturn __all__ = [ + "String", + "StringTypeSpec", + "Address", + "AddressTypeSpec", + "ADDRESS_LENGTH", "TypeSpec", "BaseType", "ComputedValue", diff --git a/pyteal/ast/abi/address.py b/pyteal/ast/abi/address.py new file mode 100644 index 000000000..1e6a44095 --- /dev/null +++ b/pyteal/ast/abi/address.py @@ -0,0 +1,33 @@ +from pyteal.ast.abi.array_static import StaticArray, StaticArrayTypeSpec +from pyteal.ast.abi.uint import ByteTypeSpec +from pyteal.ast.expr import Expr + +ADDRESS_LENGTH = 32 + + +class AddressTypeSpec(StaticArrayTypeSpec): + def __init__(self) -> None: + super().__init__(ByteTypeSpec(), ADDRESS_LENGTH) + + def new_instance(self) -> "Address": + return Address() + + def __str__(self) -> str: + return "address" + + +AddressTypeSpec.__module__ = "pyteal" + + +class Address(StaticArray): + def __init__(self) -> None: + super().__init__(AddressTypeSpec(), ADDRESS_LENGTH) + + def type_spec(self) -> AddressTypeSpec: + return AddressTypeSpec() + + def get(self) -> Expr: + return self.stored_value.load() + + +Address.__module__ = "pyteal" diff --git a/pyteal/ast/abi/address_test.py b/pyteal/ast/abi/address_test.py new file mode 100644 index 000000000..b6d5bde39 --- /dev/null +++ b/pyteal/ast/abi/address_test.py @@ -0,0 +1,81 @@ +import pyteal as pt +from pyteal import abi + +options = pt.CompileOptions(version=5) + + +def test_AddressTypeSpec_str(): + assert str(abi.AddressTypeSpec()) == "address" + + +def test_AddressTypeSpec_is_dynamic(): + assert (abi.AddressTypeSpec()).is_dynamic() is False + + +def test_AddressTypeSpec_byte_length_static(): + assert (abi.AddressTypeSpec()).byte_length_static() == abi.ADDRESS_LENGTH + + +def test_AddressTypeSpec_new_instance(): + assert isinstance(abi.AddressTypeSpec().new_instance(), abi.Address) + + +def test_AddressTypeSpec_eq(): + assert abi.AddressTypeSpec() == abi.AddressTypeSpec() + + for otherType in ( + abi.ByteTypeSpec, + abi.StaticArrayTypeSpec(abi.ByteTypeSpec(), 31), + abi.DynamicArrayTypeSpec(abi.ByteTypeSpec()), + ): + assert abi.AddressTypeSpec() != otherType + + +def test_Address_encode(): + value = abi.Address() + expr = value.encode() + assert expr.type_of() == pt.TealType.bytes + assert expr.has_return() is False + + expected = pt.TealSimpleBlock( + [pt.TealOp(expr, pt.Op.load, value.stored_value.slot)] + ) + actual, _ = expr.__teal__(options) + assert actual == expected + + +def test_Address_decode(): + from os import urandom + + value = abi.Address() + for value_to_set in [urandom(abi.ADDRESS_LENGTH) for x in range(10)]: + expr = value.decode(pt.Bytes(value_to_set)) + + assert expr.type_of() == pt.TealType.none + assert expr.has_return() is False + + expected = pt.TealSimpleBlock( + [ + pt.TealOp(None, pt.Op.byte, f"0x{value_to_set.hex()}"), + pt.TealOp(None, pt.Op.store, value.stored_value.slot), + ] + ) + actual, _ = expr.__teal__(options) + actual.addIncoming() + actual = pt.TealBlock.NormalizeBlocks(actual) + + with pt.TealComponent.Context.ignoreExprEquality(): + assert actual == expected + + +def test_Address_get(): + value = abi.Address() + expr = value.get() + assert expr.type_of() == pt.TealType.bytes + assert expr.has_return() is False + + expected = pt.TealSimpleBlock( + [pt.TealOp(expr, pt.Op.load, value.stored_value.slot)] + ) + actual, _ = expr.__teal__(options) + assert actual == expected diff --git a/pyteal/ast/abi/array_base.py b/pyteal/ast/abi/array_base.py index 29f0f9fb2..3ed714574 100644 --- a/pyteal/ast/abi/array_base.py +++ b/pyteal/ast/abi/array_base.py @@ -8,21 +8,21 @@ ) from abc import abstractmethod -from ...types import TealType, require_type -from ...errors import TealInputError -from ..expr import Expr -from ..seq import Seq -from ..int import Int -from ..if_ import If -from ..unaryexpr import Len -from ..binaryexpr import ExtractUint16 -from ..naryexpr import Concat - -from .type import TypeSpec, BaseType, ComputedValue -from .tuple import encodeTuple -from .bool import Bool, BoolTypeSpec -from .uint import Uint16, Uint16TypeSpec -from .util import substringForDecoding +from pyteal.types import TealType, require_type +from pyteal.errors import TealInputError +from pyteal.ast.expr import Expr +from pyteal.ast.seq import Seq +from pyteal.ast.int import Int +from pyteal.ast.if_ import If +from pyteal.ast.unaryexpr import Len +from pyteal.ast.binaryexpr import ExtractUint16 +from pyteal.ast.naryexpr import Concat + +from pyteal.ast.abi.type import TypeSpec, BaseType, ComputedValue +from pyteal.ast.abi.tuple import encodeTuple +from pyteal.ast.abi.bool import Bool, BoolTypeSpec +from pyteal.ast.abi.uint import Uint16, Uint16TypeSpec +from pyteal.ast.abi.util import substringForDecoding T = TypeVar("T", bound=BaseType) diff --git a/pyteal/ast/abi/array_base_test.py b/pyteal/ast/abi/array_base_test.py index d1120e5e4..0c480183a 100644 --- a/pyteal/ast/abi/array_base_test.py +++ b/pyteal/ast/abi/array_base_test.py @@ -1,9 +1,10 @@ from typing import List, cast import pytest -from ... import * +import pyteal as pt +from pyteal import abi -options = CompileOptions(version=5) +options = pt.CompileOptions(version=5) STATIC_TYPES: List[abi.TypeSpec] = [ abi.BoolTypeSpec(), @@ -53,31 +54,31 @@ def test_ArrayElement_init(): dynamicArrayType = abi.DynamicArrayTypeSpec(abi.Uint64TypeSpec()) array = dynamicArrayType.new_instance() - index = Int(6) + index = pt.Int(6) element = abi.ArrayElement(array, index) assert element.array is array assert element.index is index - with pytest.raises(TealTypeError): - abi.ArrayElement(array, Bytes("abc")) + with pytest.raises(pt.TealTypeError): + abi.ArrayElement(array, pt.Bytes("abc")) - with pytest.raises(TealTypeError): - abi.ArrayElement(array, Assert(index)) + with pytest.raises(pt.TealTypeError): + abi.ArrayElement(array, pt.Assert(index)) def test_ArrayElement_store_into(): for elementType in STATIC_TYPES + DYNAMIC_TYPES: staticArrayType = abi.StaticArrayTypeSpec(elementType, 100) staticArray = staticArrayType.new_instance() - index = Int(9) + index = pt.Int(9) element = abi.ArrayElement(staticArray, index) output = elementType.new_instance() expr = element.store_into(output) encoded = staticArray.encode() - stride = Int(staticArray.type_spec()._stride()) + stride = pt.Int(staticArray.type_spec()._stride()) expectedLength = staticArray.length() if elementType == abi.BoolTypeSpec(): expectedExpr = cast(abi.Bool, output).decodeBit(encoded, index) @@ -88,71 +89,73 @@ def test_ArrayElement_store_into(): else: expectedExpr = output.decode( encoded, - startIndex=ExtractUint16(encoded, stride * index), - endIndex=If(index + Int(1) == expectedLength) - .Then(Len(encoded)) - .Else(ExtractUint16(encoded, stride * index + Int(2))), + startIndex=pt.ExtractUint16(encoded, stride * index), + endIndex=pt.If(index + pt.Int(1) == expectedLength) + .Then(pt.Len(encoded)) + .Else(pt.ExtractUint16(encoded, stride * index + pt.Int(2))), ) expected, _ = expectedExpr.__teal__(options) expected.addIncoming() - expected = TealBlock.NormalizeBlocks(expected) + expected = pt.TealBlock.NormalizeBlocks(expected) actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): element.store_into(abi.Tuple(elementType)) for elementType in STATIC_TYPES + DYNAMIC_TYPES: dynamicArrayType = abi.DynamicArrayTypeSpec(elementType) dynamicArray = dynamicArrayType.new_instance() - index = Int(9) + index = pt.Int(9) element = abi.ArrayElement(dynamicArray, index) output = elementType.new_instance() expr = element.store_into(output) encoded = dynamicArray.encode() - stride = Int(dynamicArray.type_spec()._stride()) + stride = pt.Int(dynamicArray.type_spec()._stride()) expectedLength = dynamicArray.length() if elementType == abi.BoolTypeSpec(): - expectedExpr = cast(abi.Bool, output).decodeBit(encoded, index + Int(16)) + expectedExpr = cast(abi.Bool, output).decodeBit(encoded, index + pt.Int(16)) elif not elementType.is_dynamic(): expectedExpr = output.decode( - encoded, startIndex=stride * index + Int(2), length=stride + encoded, startIndex=stride * index + pt.Int(2), length=stride ) else: expectedExpr = output.decode( encoded, - startIndex=ExtractUint16(encoded, stride * index + Int(2)) + Int(2), - endIndex=If(index + Int(1) == expectedLength) - .Then(Len(encoded)) + startIndex=pt.ExtractUint16(encoded, stride * index + pt.Int(2)) + + pt.Int(2), + endIndex=pt.If(index + pt.Int(1) == expectedLength) + .Then(pt.Len(encoded)) .Else( - ExtractUint16(encoded, stride * index + Int(2) + Int(2)) + Int(2) + pt.ExtractUint16(encoded, stride * index + pt.Int(2) + pt.Int(2)) + + pt.Int(2) ), ) expected, _ = expectedExpr.__teal__(options) expected.addIncoming() - expected = TealBlock.NormalizeBlocks(expected) + expected = pt.TealBlock.NormalizeBlocks(expected) actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): - with TealComponent.Context.ignoreScratchSlotEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreScratchSlotEquality(): assert actual == expected - assert TealBlock.MatchScratchSlotReferences( - TealBlock.GetReferencedScratchSlots(actual), - TealBlock.GetReferencedScratchSlots(expected), + assert pt.TealBlock.MatchScratchSlotReferences( + pt.TealBlock.GetReferencedScratchSlots(actual), + pt.TealBlock.GetReferencedScratchSlots(expected), ) - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): element.store_into(abi.Tuple(elementType)) diff --git a/pyteal/ast/abi/array_dynamic.py b/pyteal/ast/abi/array_dynamic.py index bc0526a86..f515b4ac8 100644 --- a/pyteal/ast/abi/array_dynamic.py +++ b/pyteal/ast/abi/array_dynamic.py @@ -6,13 +6,13 @@ ) -from ...errors import TealInputError -from ..expr import Expr -from ..seq import Seq +from pyteal.errors import TealInputError +from pyteal.ast.expr import Expr +from pyteal.ast.seq import Seq -from .type import ComputedValue, TypeSpec, BaseType -from .uint import Uint16 -from .array_base import ArrayTypeSpec, Array +from pyteal.ast.abi.type import ComputedValue, TypeSpec, BaseType +from pyteal.ast.abi.uint import Uint16 +from pyteal.ast.abi.array_base import ArrayTypeSpec, Array T = TypeVar("T", bound=BaseType) diff --git a/pyteal/ast/abi/array_dynamic_test.py b/pyteal/ast/abi/array_dynamic_test.py index c6211f1e1..6095fba3b 100644 --- a/pyteal/ast/abi/array_dynamic_test.py +++ b/pyteal/ast/abi/array_dynamic_test.py @@ -1,13 +1,14 @@ from typing import List import pytest -from ... import * -from .util import substringForDecoding -from .tuple import encodeTuple -from .array_base_test import STATIC_TYPES, DYNAMIC_TYPES -from .type_test import ContainerType +import pyteal as pt +from pyteal import abi +from pyteal.ast.abi.util import substringForDecoding +from pyteal.ast.abi.tuple import encodeTuple +from pyteal.ast.abi.array_base_test import STATIC_TYPES, DYNAMIC_TYPES +from pyteal.ast.abi.type_test import ContainerType -options = CompileOptions(version=5) +options = pt.CompileOptions(version=5) def test_DynamicArrayTypeSpec_init(): @@ -59,15 +60,15 @@ def test_DynamicArrayTypeSpec_byte_length_static(): def test_DynamicArray_decode(): - encoded = Bytes("encoded") + encoded = pt.Bytes("encoded") dynamicArrayType = abi.DynamicArrayTypeSpec(abi.Uint64TypeSpec()) - for startIndex in (None, Int(1)): - for endIndex in (None, Int(2)): - for length in (None, Int(3)): + for startIndex in (None, pt.Int(1)): + for endIndex in (None, pt.Int(2)): + for length in (None, pt.Int(3)): value = dynamicArrayType.new_instance() if endIndex is not None and length is not None: - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): value.decode( encoded, startIndex=startIndex, @@ -79,7 +80,7 @@ def test_DynamicArray_decode(): expr = value.decode( encoded, startIndex=startIndex, endIndex=endIndex, length=length ) - assert expr.type_of() == TealType.none + assert expr.type_of() == pt.TealType.none assert not expr.has_return() expectedExpr = value.stored_value.store( @@ -89,13 +90,13 @@ def test_DynamicArray_decode(): ) expected, _ = expectedExpr.__teal__(options) expected.addIncoming() - expected = TealBlock.NormalizeBlocks(expected) + expected = pt.TealBlock.NormalizeBlocks(expected) actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected @@ -110,31 +111,31 @@ def test_DynamicArray_set_values(): for values in valuesToSet: value = dynamicArrayType.new_instance() expr = value.set(values) - assert expr.type_of() == TealType.none + assert expr.type_of() == pt.TealType.none assert not expr.has_return() length_tmp = abi.Uint16() expectedExpr = value.stored_value.store( - Concat( - Seq(length_tmp.set(len(values)), length_tmp.encode()), + pt.Concat( + pt.Seq(length_tmp.set(len(values)), length_tmp.encode()), encodeTuple(values), ) ) expected, _ = expectedExpr.__teal__(options) expected.addIncoming() - expected = TealBlock.NormalizeBlocks(expected) + expected = pt.TealBlock.NormalizeBlocks(expected) actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): - with TealComponent.Context.ignoreScratchSlotEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreScratchSlotEquality(): assert actual == expected - assert TealBlock.MatchScratchSlotReferences( - TealBlock.GetReferencedScratchSlots(actual), - TealBlock.GetReferencedScratchSlots(expected), + assert pt.TealBlock.MatchScratchSlotReferences( + pt.TealBlock.GetReferencedScratchSlots(actual), + pt.TealBlock.GetReferencedScratchSlots(expected), ) @@ -143,56 +144,58 @@ def test_DynamicArray_set_copy(): value = dynamicArrayType.new_instance() otherArray = dynamicArrayType.new_instance() - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): value.set(abi.DynamicArray(abi.DynamicArrayTypeSpec(abi.Uint8TypeSpec()))) - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): value.set(abi.Uint64()) expr = value.set(otherArray) - assert expr.type_of() == TealType.none + assert expr.type_of() == pt.TealType.none assert not expr.has_return() - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(None, Op.load, otherArray.stored_value.slot), - TealOp(None, Op.store, value.stored_value.slot), + pt.TealOp(None, pt.Op.load, otherArray.stored_value.slot), + pt.TealOp(None, pt.Op.store, value.stored_value.slot), ] ) actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected def test_DynamicArray_set_computed(): value = abi.DynamicArray(abi.ByteTypeSpec()) - computed = ContainerType(value.type_spec(), Bytes("this should be a dynamic array")) + computed = ContainerType( + value.type_spec(), pt.Bytes("this should be a dynamic array") + ) expr = value.set(computed) - assert expr.type_of() == TealType.none + assert expr.type_of() == pt.TealType.none assert not expr.has_return() - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(None, Op.byte, '"this should be a dynamic array"'), - TealOp(None, Op.store, value.stored_value.slot), + pt.TealOp(None, pt.Op.byte, '"this should be a dynamic array"'), + pt.TealOp(None, pt.Op.store, value.stored_value.slot), ] ) actual, _ = expr.__teal__(options) actual.addIncoming() actual = actual.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): value.set( ContainerType( abi.DynamicArrayTypeSpec(abi.Uint16TypeSpec()), - Bytes("well i am trolling again"), + pt.Bytes("well i am trolling again"), ) ) @@ -201,16 +204,18 @@ def test_DynamicArray_encode(): dynamicArrayType = abi.DynamicArrayTypeSpec(abi.Uint64TypeSpec()) value = dynamicArrayType.new_instance() expr = value.encode() - assert expr.type_of() == TealType.bytes + assert expr.type_of() == pt.TealType.bytes assert not expr.has_return() - expected = TealSimpleBlock([TealOp(None, Op.load, value.stored_value.slot)]) + expected = pt.TealSimpleBlock( + [pt.TealOp(None, pt.Op.load, value.stored_value.slot)] + ) actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected @@ -218,26 +223,26 @@ def test_DynamicArray_length(): dynamicArrayType = abi.DynamicArrayTypeSpec(abi.Uint64TypeSpec()) value = dynamicArrayType.new_instance() expr = value.length() - assert expr.type_of() == TealType.uint64 + assert expr.type_of() == pt.TealType.uint64 assert not expr.has_return() length_tmp = abi.Uint16() - expectedExpr = Seq(length_tmp.decode(value.encode()), length_tmp.get()) + expectedExpr = pt.Seq(length_tmp.decode(value.encode()), length_tmp.get()) expected, _ = expectedExpr.__teal__(options) expected.addIncoming() - expected = TealBlock.NormalizeBlocks(expected) + expected = pt.TealBlock.NormalizeBlocks(expected) actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): - with TealComponent.Context.ignoreScratchSlotEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreScratchSlotEquality(): assert actual == expected - assert TealBlock.MatchScratchSlotReferences( - TealBlock.GetReferencedScratchSlots(actual), - TealBlock.GetReferencedScratchSlots(expected), + assert pt.TealBlock.MatchScratchSlotReferences( + pt.TealBlock.GetReferencedScratchSlots(actual), + pt.TealBlock.GetReferencedScratchSlots(expected), ) @@ -247,7 +252,7 @@ def test_DynamicArray_getitem(): for index in (0, 1, 2, 3, 1000): # dynamic indexes - indexExpr = Int(index) + indexExpr = pt.Int(index) element = value[indexExpr] assert type(element) is abi.ArrayElement assert element.array is value @@ -258,8 +263,8 @@ def test_DynamicArray_getitem(): element = value[index] assert type(element) is abi.ArrayElement assert element.array is value - assert type(element.index) is Int + assert type(element.index) is pt.Int assert element.index.value == index - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): value[-1] diff --git a/pyteal/ast/abi/array_static.py b/pyteal/ast/abi/array_static.py index 3632933a9..f9e8f834e 100644 --- a/pyteal/ast/abi/array_static.py +++ b/pyteal/ast/abi/array_static.py @@ -7,13 +7,13 @@ cast, ) -from ...errors import TealInputError -from ..expr import Expr -from ..int import Int +from pyteal.errors import TealInputError +from pyteal.ast.expr import Expr +from pyteal.ast.int import Int -from .type import ComputedValue, TypeSpec, BaseType -from .bool import BoolTypeSpec, boolSequenceLength -from .array_base import ArrayTypeSpec, Array, ArrayElement +from pyteal.ast.abi.type import ComputedValue, TypeSpec, BaseType +from pyteal.ast.abi.bool import BoolTypeSpec, boolSequenceLength +from pyteal.ast.abi.array_base import ArrayTypeSpec, Array, ArrayElement T = TypeVar("T", bound=BaseType) diff --git a/pyteal/ast/abi/array_static_test.py b/pyteal/ast/abi/array_static_test.py index f12995766..70cc817fc 100644 --- a/pyteal/ast/abi/array_static_test.py +++ b/pyteal/ast/abi/array_static_test.py @@ -1,13 +1,14 @@ import pytest -from ... import * -from .util import substringForDecoding -from .tuple import encodeTuple -from .bool import boolSequenceLength -from .type_test import ContainerType -from .array_base_test import STATIC_TYPES, DYNAMIC_TYPES +import pyteal as pt +from pyteal import abi +from pyteal.ast.abi.util import substringForDecoding +from pyteal.ast.abi.tuple import encodeTuple +from pyteal.ast.abi.bool import boolSequenceLength +from pyteal.ast.abi.type_test import ContainerType +from pyteal.ast.abi.array_base_test import STATIC_TYPES, DYNAMIC_TYPES -options = CompileOptions(version=5) +options = pt.CompileOptions(version=5) def test_StaticArrayTypeSpec_init(): @@ -99,14 +100,14 @@ def test_StaticArrayTypeSpec_byte_length_static(): def test_StaticArray_decode(): - encoded = Bytes("encoded") - for startIndex in (None, Int(1)): - for endIndex in (None, Int(2)): - for length in (None, Int(3)): + encoded = pt.Bytes("encoded") + for startIndex in (None, pt.Int(1)): + for endIndex in (None, pt.Int(2)): + for length in (None, pt.Int(3)): value = abi.StaticArray(abi.Uint64TypeSpec(), 10) if endIndex is not None and length is not None: - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): value.decode( encoded, startIndex=startIndex, @@ -118,7 +119,7 @@ def test_StaticArray_decode(): expr = value.decode( encoded, startIndex=startIndex, endIndex=endIndex, length=length ) - assert expr.type_of() == TealType.none + assert expr.type_of() == pt.TealType.none assert not expr.has_return() expectedExpr = value.stored_value.store( @@ -128,49 +129,49 @@ def test_StaticArray_decode(): ) expected, _ = expectedExpr.__teal__(options) expected.addIncoming() - expected = TealBlock.NormalizeBlocks(expected) + expected = pt.TealBlock.NormalizeBlocks(expected) actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected def test_StaticArray_set_values(): value = abi.StaticArray(abi.Uint64TypeSpec(), 10) - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): value.set([]) - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): value.set([abi.Uint64()] * 9) - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): value.set([abi.Uint64()] * 11) - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): value.set([abi.Uint16()] * 10) - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): value.set([abi.Uint64()] * 9 + [abi.Uint16()]) values = [abi.Uint64() for _ in range(10)] expr = value.set(values) - assert expr.type_of() == TealType.none + assert expr.type_of() == pt.TealType.none assert not expr.has_return() expectedExpr = value.stored_value.store(encodeTuple(values)) expected, _ = expectedExpr.__teal__(options) expected.addIncoming() - expected = TealBlock.NormalizeBlocks(expected) + expected = pt.TealBlock.NormalizeBlocks(expected) actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected @@ -178,61 +179,61 @@ def test_StaticArray_set_copy(): value = abi.StaticArray(abi.Uint64TypeSpec(), 10) otherArray = abi.StaticArray(abi.Uint64TypeSpec(), 10) - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): value.set(abi.StaticArray(abi.Uint64TypeSpec(), 11)) - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): value.set(abi.StaticArray(abi.Uint8TypeSpec(), 10)) - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): value.set(abi.Uint64()) expr = value.set(otherArray) - assert expr.type_of() == TealType.none + assert expr.type_of() == pt.TealType.none assert not expr.has_return() - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(None, Op.load, otherArray.stored_value.slot), - TealOp(None, Op.store, value.stored_value.slot), + pt.TealOp(None, pt.Op.load, otherArray.stored_value.slot), + pt.TealOp(None, pt.Op.store, value.stored_value.slot), ] ) actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected def test_StaticArray_set_computed(): value = abi.StaticArray(abi.Uint64TypeSpec(), 10) computed = ContainerType( - value.type_spec(), Bytes("indeed this is hard to simulate") + value.type_spec(), pt.Bytes("indeed this is hard to simulate") ) expr = value.set(computed) - assert expr.type_of() == TealType.none + assert expr.type_of() == pt.TealType.none assert not expr.has_return() - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(None, Op.byte, '"indeed this is hard to simulate"'), - TealOp(None, Op.store, value.stored_value.slot), + pt.TealOp(None, pt.Op.byte, '"indeed this is hard to simulate"'), + pt.TealOp(None, pt.Op.store, value.stored_value.slot), ] ) actual, _ = expr.__teal__(options) actual.addIncoming() actual = actual.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): value.set( ContainerType( abi.StaticArrayTypeSpec(abi.Uint16TypeSpec(), 40), - Bytes("well i am trolling"), + pt.Bytes("well i am trolling"), ) ) @@ -240,16 +241,18 @@ def test_StaticArray_set_computed(): def test_StaticArray_encode(): value = abi.StaticArray(abi.Uint64TypeSpec(), 10) expr = value.encode() - assert expr.type_of() == TealType.bytes + assert expr.type_of() == pt.TealType.bytes assert not expr.has_return() - expected = TealSimpleBlock([TealOp(None, Op.load, value.stored_value.slot)]) + expected = pt.TealSimpleBlock( + [pt.TealOp(None, pt.Op.load, value.stored_value.slot)] + ) actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected @@ -257,16 +260,16 @@ def test_StaticArray_length(): for length in (0, 1, 2, 3, 1000): value = abi.StaticArray(abi.Uint64TypeSpec(), length) expr = value.length() - assert expr.type_of() == TealType.uint64 + assert expr.type_of() == pt.TealType.uint64 assert not expr.has_return() - expected = TealSimpleBlock([TealOp(None, Op.int, length)]) + expected = pt.TealSimpleBlock([pt.TealOp(None, pt.Op.int, length)]) actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected @@ -276,7 +279,7 @@ def test_StaticArray_getitem(): for index in range(length): # dynamic indexes - indexExpr = Int(index) + indexExpr = pt.Int(index) element = value[indexExpr] assert type(element) is abi.ArrayElement assert element.array is value @@ -287,11 +290,11 @@ def test_StaticArray_getitem(): element = value[index] assert type(element) is abi.ArrayElement assert element.array is value - assert type(element.index) is Int + assert type(element.index) is pt.Int assert element.index.value == index - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): value[-1] - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): value[length] diff --git a/pyteal/ast/abi/bool.py b/pyteal/ast/abi/bool.py index 8bae5724e..f1b8a6065 100644 --- a/pyteal/ast/abi/bool.py +++ b/pyteal/ast/abi/bool.py @@ -1,16 +1,16 @@ from typing import TypeVar, Union, cast, Sequence, Callable -from ...types import TealType -from ...errors import TealInputError -from ..expr import Expr -from ..seq import Seq -from ..assert_ import Assert -from ..int import Int -from ..bytes import Bytes -from ..binaryexpr import GetBit -from ..ternaryexpr import SetBit -from .type import ComputedValue, TypeSpec, BaseType -from .uint import NUM_BITS_IN_BYTE +from pyteal.types import TealType +from pyteal.errors import TealInputError +from pyteal.ast.expr import Expr +from pyteal.ast.seq import Seq +from pyteal.ast.assert_ import Assert +from pyteal.ast.int import Int +from pyteal.ast.bytes import Bytes +from pyteal.ast.binaryexpr import GetBit +from pyteal.ast.ternaryexpr import SetBit +from pyteal.ast.abi.type import ComputedValue, TypeSpec, BaseType +from pyteal.ast.abi.uint import NUM_BITS_IN_BYTE class BoolTypeSpec(TypeSpec): diff --git a/pyteal/ast/abi/bool_test.py b/pyteal/ast/abi/bool_test.py index ec28031ab..b5ab9eec9 100644 --- a/pyteal/ast/abi/bool_test.py +++ b/pyteal/ast/abi/bool_test.py @@ -1,9 +1,10 @@ from typing import NamedTuple, List import pytest -from ... import * -from .type_test import ContainerType -from .bool import ( +import pyteal as pt +from pyteal import abi +from pyteal.ast.abi.type_test import ContainerType +from pyteal.ast.abi.bool import ( boolAwareStaticByteLength, consecutiveBoolInstanceNum, consecutiveBoolTypeSpecNum, @@ -11,10 +12,7 @@ encodeBoolSequence, ) -# this is not necessary but mypy complains if it's not included -from ... import CompileOptions - -options = CompileOptions(version=5) +options = pt.CompileOptions(version=5) def test_BoolTypeSpec_str(): @@ -49,48 +47,48 @@ def test_Bool_set_static(): value = abi.Bool() for value_to_set in (True, False): expr = value.set(value_to_set) - assert expr.type_of() == TealType.none + assert expr.type_of() == pt.TealType.none assert not expr.has_return() - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(None, Op.int, 1 if value_to_set else 0), - TealOp(None, Op.store, value.stored_value.slot), + pt.TealOp(None, pt.Op.int, 1 if value_to_set else 0), + pt.TealOp(None, pt.Op.store, value.stored_value.slot), ] ) actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected def test_Bool_set_expr(): value = abi.Bool() - expr = value.set(Int(0).Or(Int(1))) - assert expr.type_of() == TealType.none + expr = value.set(pt.Int(0).Or(pt.Int(1))) + assert expr.type_of() == pt.TealType.none assert not expr.has_return() - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(None, Op.int, 0), - TealOp(None, Op.int, 1), - TealOp(None, Op.logic_or), - TealOp(None, Op.store, value.stored_value.slot), - TealOp(None, Op.load, value.stored_value.slot), - TealOp(None, Op.int, 2), - TealOp(None, Op.lt), - TealOp(None, Op.assert_), + pt.TealOp(None, pt.Op.int, 0), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.logic_or), + pt.TealOp(None, pt.Op.store, value.stored_value.slot), + pt.TealOp(None, pt.Op.load, value.stored_value.slot), + pt.TealOp(None, pt.Op.int, 2), + pt.TealOp(None, pt.Op.lt), + pt.TealOp(None, pt.Op.assert_), ] ) actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected @@ -98,59 +96,61 @@ def test_Bool_set_copy(): other = abi.Bool() value = abi.Bool() expr = value.set(other) - assert expr.type_of() == TealType.none + assert expr.type_of() == pt.TealType.none assert not expr.has_return() - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(None, Op.load, other.stored_value.slot), - TealOp(None, Op.store, value.stored_value.slot), + pt.TealOp(None, pt.Op.load, other.stored_value.slot), + pt.TealOp(None, pt.Op.store, value.stored_value.slot), ] ) actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): value.set(abi.Uint16()) def test_Bool_set_computed(): value = abi.Bool() - computed = ContainerType(abi.BoolTypeSpec(), Int(0x80)) + computed = ContainerType(abi.BoolTypeSpec(), pt.Int(0x80)) expr = value.set(computed) - assert expr.type_of() == TealType.none + assert expr.type_of() == pt.TealType.none assert not expr.has_return() - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(None, Op.int, 0x80), - TealOp(None, Op.store, value.stored_value.slot), + pt.TealOp(None, pt.Op.int, 0x80), + pt.TealOp(None, pt.Op.store, value.stored_value.slot), ] ) actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected - with pytest.raises(TealInputError): - value.set(ContainerType(abi.Uint32TypeSpec(), Int(65537))) + with pytest.raises(pt.TealInputError): + value.set(ContainerType(abi.Uint32TypeSpec(), pt.Int(65537))) def test_Bool_get(): value = abi.Bool() expr = value.get() - assert expr.type_of() == TealType.uint64 + assert expr.type_of() == pt.TealType.uint64 assert not expr.has_return() - expected = TealSimpleBlock([TealOp(expr, Op.load, value.stored_value.slot)]) + expected = pt.TealSimpleBlock( + [pt.TealOp(expr, pt.Op.load, value.stored_value.slot)] + ) actual, _ = expr.__teal__(options) @@ -159,80 +159,80 @@ def test_Bool_get(): def test_Bool_decode(): value = abi.Bool() - encoded = Bytes("encoded") - for startIndex in (None, Int(1)): - for endIndex in (None, Int(2)): - for length in (None, Int(3)): + encoded = pt.Bytes("encoded") + for startIndex in (None, pt.Int(1)): + for endIndex in (None, pt.Int(2)): + for length in (None, pt.Int(3)): expr = value.decode( encoded, startIndex=startIndex, endIndex=endIndex, length=length ) - assert expr.type_of() == TealType.none + assert expr.type_of() == pt.TealType.none assert not expr.has_return() - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(None, Op.byte, '"encoded"'), - TealOp(None, Op.int, 0 if startIndex is None else 1), - TealOp(None, Op.int, 8), - TealOp(None, Op.mul), - TealOp(None, Op.getbit), - TealOp(None, Op.store, value.stored_value.slot), + pt.TealOp(None, pt.Op.byte, '"encoded"'), + pt.TealOp(None, pt.Op.int, 0 if startIndex is None else 1), + pt.TealOp(None, pt.Op.int, 8), + pt.TealOp(None, pt.Op.mul), + pt.TealOp(None, pt.Op.getbit), + pt.TealOp(None, pt.Op.store, value.stored_value.slot), ] ) actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected def test_Bool_decodeBit(): value = abi.Bool() - bitIndex = Int(17) - encoded = Bytes("encoded") + bitIndex = pt.Int(17) + encoded = pt.Bytes("encoded") expr = value.decodeBit(encoded, bitIndex) - assert expr.type_of() == TealType.none + assert expr.type_of() == pt.TealType.none assert not expr.has_return() - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(None, Op.byte, '"encoded"'), - TealOp(None, Op.int, 17), - TealOp(None, Op.getbit), - TealOp(None, Op.store, value.stored_value.slot), + pt.TealOp(None, pt.Op.byte, '"encoded"'), + pt.TealOp(None, pt.Op.int, 17), + pt.TealOp(None, pt.Op.getbit), + pt.TealOp(None, pt.Op.store, value.stored_value.slot), ] ) actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected def test_Bool_encode(): value = abi.Bool() expr = value.encode() - assert expr.type_of() == TealType.bytes + assert expr.type_of() == pt.TealType.bytes assert not expr.has_return() - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(None, Op.byte, "0x00"), - TealOp(None, Op.int, 0), - TealOp(None, Op.load, value.stored_value.slot), - TealOp(None, Op.setbit), + pt.TealOp(None, pt.Op.byte, "0x00"), + pt.TealOp(None, pt.Op.int, 0), + pt.TealOp(None, pt.Op.load, value.stored_value.slot), + pt.TealOp(None, pt.Op.setbit), ] ) actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected @@ -397,28 +397,28 @@ class EncodeSeqTest(NamedTuple): for i, test in enumerate(tests): expr = encodeBoolSequence(test.types) - assert expr.type_of() == TealType.bytes + assert expr.type_of() == pt.TealType.bytes assert not expr.has_return() setBits = [ [ - TealOp(None, Op.int, j), - TealOp(None, Op.load, testType.stored_value.slot), - TealOp(None, Op.setbit), + pt.TealOp(None, pt.Op.int, j), + pt.TealOp(None, pt.Op.load, testType.stored_value.slot), + pt.TealOp(None, pt.Op.setbit), ] for j, testType in enumerate(test.types) ] - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(None, Op.byte, "0x" + ("00" * test.expectedLength)), + pt.TealOp(None, pt.Op.byte, "0x" + ("00" * test.expectedLength)), ] + [expr for setBit in setBits for expr in setBit] ) actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected, "Test at index {} failed".format(i) diff --git a/pyteal/ast/abi/method_return.py b/pyteal/ast/abi/method_return.py index d9a61f9da..7d7af0ed7 100644 --- a/pyteal/ast/abi/method_return.py +++ b/pyteal/ast/abi/method_return.py @@ -1,13 +1,13 @@ from typing import TYPE_CHECKING, Tuple -from . import BaseType -from ...types import TealType -from ...errors import TealInputError -from .. import Expr, Log, Concat, Bytes -from ...ir import TealBlock, TealSimpleBlock, Op -from ...config import RETURN_METHOD_SELECTOR +from pyteal.ast.abi import BaseType +from pyteal.types import TealType +from pyteal.errors import TealInputError +from pyteal.ast import Expr, Log, Concat, Bytes +from pyteal.ir import TealBlock, TealSimpleBlock, Op +from pyteal.config import RETURN_METHOD_SELECTOR if TYPE_CHECKING: - from ...compiler import CompileOptions + from pyteal.compiler import CompileOptions class MethodReturn(Expr): diff --git a/pyteal/ast/abi/method_return_test.py b/pyteal/ast/abi/method_return_test.py index d9da210c6..d834ddf2a 100644 --- a/pyteal/ast/abi/method_return_test.py +++ b/pyteal/ast/abi/method_return_test.py @@ -1,35 +1,33 @@ import pytest -from . import * -from .. import Int, Bytes -from ...types import TealType -from ...errors import TealInputError +import pyteal as pt +from pyteal import abi POSITIVE_CASES = [ - Uint16(), - Uint32(), - StaticArray(BoolTypeSpec(), 12), + abi.Uint16(), + abi.Uint32(), + abi.StaticArray(abi.BoolTypeSpec(), 12), ] @pytest.mark.parametrize("case", POSITIVE_CASES) def test_method_return(case): - m_ret = MethodReturn(case) - assert m_ret.type_of() == TealType.none + m_ret = abi.MethodReturn(case) + assert m_ret.type_of() == pt.TealType.none assert not m_ret.has_return() assert str(m_ret) == f"(MethodReturn {case.type_spec()})" NEGATIVE_CASES = [ - Int(0), - Bytes("aaaaaaa"), - Uint16, - Uint32, + pt.Int(0), + pt.Bytes("aaaaaaa"), + abi.Uint16, + abi.Uint32, ] @pytest.mark.parametrize("case", NEGATIVE_CASES) def test_method_return_error(case): - with pytest.raises(TealInputError): - MethodReturn(case) + with pytest.raises(pt.TealInputError): + abi.MethodReturn(case) diff --git a/pyteal/ast/abi/router.py b/pyteal/ast/abi/router.py index ff2071156..375820eca 100644 --- a/pyteal/ast/abi/router.py +++ b/pyteal/ast/abi/router.py @@ -1,24 +1,21 @@ from typing import List, Tuple, Union, cast from dataclasses import dataclass -from ...config import METHOD_ARG_NUM_LIMIT -from ...errors import TealInputError -from ...types import TealType -from .. import ( - Cond, - OnComplete, - Expr, - EnumInt, - Int, - MethodSignature, - And, - Or, - Approve, - Seq, - SubroutineFnWrapper, - Txn, -) -from . import MethodReturn +from pyteal.config import METHOD_ARG_NUM_LIMIT +from pyteal.errors import TealInputError +from pyteal.types import TealType +from pyteal.ast.cond import Cond +from pyteal.ast.expr import Expr +from pyteal.ast.app import OnComplete, EnumInt +from pyteal.ast.int import Int +from pyteal.ast.seq import Seq +from pyteal.ast.subroutine import SubroutineFnWrapper +from pyteal.ast.methodsig import MethodSignature +from pyteal.ast.naryexpr import And, Or +from pyteal.ast.txn import Txn +from pyteal.ast.return_ import Approve + +# from pyteal.ast.abi.method_return import MethodReturn # NOTE this should sit in `abi` directory, still waiting on abi to be merged in @@ -139,7 +136,7 @@ def __wrapHandler(isMethod: bool, branch: Union[SubroutineFnWrapper, Expr]) -> E else: if isinstance(branch, SubroutineFnWrapper) and branch.has_return(): # TODO need to encode/decode things - execBranchArgs: List[Expr] = [] + # execBranchArgs: List[Expr] = [] if branch.subroutine.argumentCount() >= METHOD_ARG_NUM_LIMIT: # NOTE decode (if arg num > 15 need to de-tuple 15th (last) argument) pass diff --git a/pyteal/ast/abi/string.py b/pyteal/ast/abi/string.py new file mode 100644 index 000000000..ca32024a1 --- /dev/null +++ b/pyteal/ast/abi/string.py @@ -0,0 +1,37 @@ +from pyteal.ast.abi.array_dynamic import DynamicArray, DynamicArrayTypeSpec +from pyteal.ast.abi.uint import ByteTypeSpec, Uint16TypeSpec +from pyteal.ast.abi.util import substringForDecoding + +from pyteal.ast.int import Int +from pyteal.ast.expr import Expr + + +class StringTypeSpec(DynamicArrayTypeSpec): + def __init__(self) -> None: + super().__init__(ByteTypeSpec()) + + def new_instance(self) -> "String": + return String() + + def __str__(self) -> str: + return "string" + + +StringTypeSpec.__module__ = "pyteal" + + +class String(DynamicArray): + def __init__(self) -> None: + super().__init__(StringTypeSpec()) + + def type_spec(self) -> StringTypeSpec: + return StringTypeSpec() + + def get(self) -> Expr: + return substringForDecoding( + self.stored_value.load(), + startIndex=Int(Uint16TypeSpec().byte_length_static()), + ) + + +String.__module__ = "pyteal" diff --git a/pyteal/ast/abi/string_test.py b/pyteal/ast/abi/string_test.py new file mode 100644 index 000000000..c722c0d7a --- /dev/null +++ b/pyteal/ast/abi/string_test.py @@ -0,0 +1,85 @@ +import pyteal as pt +from pyteal import abi + +options = pt.CompileOptions(version=5) + + +def test_StringTypeSpec_str(): + assert str(abi.StringTypeSpec()) == "string" + + +def test_StringTypeSpec_is_dynamic(): + assert (abi.StringTypeSpec()).is_dynamic() + + +def test_StringTypeSpec_new_instance(): + assert isinstance(abi.StringTypeSpec().new_instance(), abi.String) + + +def test_StringTypeSpec_eq(): + assert abi.StringTypeSpec() == abi.StringTypeSpec() + + for otherType in ( + abi.ByteTypeSpec, + abi.StaticArrayTypeSpec(abi.ByteTypeSpec(), 1), + abi.DynamicArrayTypeSpec(abi.Uint8TypeSpec()), + ): + assert abi.StringTypeSpec() != otherType + + +def test_String_encode(): + value = abi.String() + expr = value.encode() + assert expr.type_of() == pt.TealType.bytes + assert expr.has_return() is False + + expected = pt.TealSimpleBlock( + [pt.TealOp(expr, pt.Op.load, value.stored_value.slot)] + ) + actual, _ = expr.__teal__(options) + assert actual == expected + + +def test_String_decode(): + import random + from os import urandom + + value = abi.String() + for value_to_set in [urandom(random.randint(0, 50)) for x in range(10)]: + expr = value.decode(pt.Bytes(value_to_set)) + + assert expr.type_of() == pt.TealType.none + assert expr.has_return() is False + + expected = pt.TealSimpleBlock( + [ + pt.TealOp(None, pt.Op.byte, f"0x{value_to_set.hex()}"), + pt.TealOp(None, pt.Op.store, value.stored_value.slot), + ] + ) + actual, _ = expr.__teal__(options) + actual.addIncoming() + actual = pt.TealBlock.NormalizeBlocks(actual) + + with pt.TealComponent.Context.ignoreExprEquality(): + assert actual == expected + + +def test_String_get(): + value = abi.String() + expr = value.get() + assert expr.type_of() == pt.TealType.bytes + assert expr.has_return() is False + + expected = pt.TealSimpleBlock( + [ + pt.TealOp(expr, pt.Op.load, value.stored_value.slot), + pt.TealOp(None, pt.Op.extract, 2, 0), + ] + ) + actual, _ = expr.__teal__(options) + actual.addIncoming() + actual = pt.TealBlock.NormalizeBlocks(actual) + + with pt.TealComponent.Context.ignoreExprEquality(): + assert actual == expected diff --git a/pyteal/ast/abi/tuple.py b/pyteal/ast/abi/tuple.py index 08b81aabb..6d5792829 100644 --- a/pyteal/ast/abi/tuple.py +++ b/pyteal/ast/abi/tuple.py @@ -4,25 +4,23 @@ Dict, Generic, TypeVar, - Union, cast, - Tuple as TypingTuple, overload, ) -from ...types import TealType -from ...errors import TealInputError -from ..expr import Expr -from ..seq import Seq -from ..int import Int -from ..bytes import Bytes -from ..unaryexpr import Len -from ..binaryexpr import ExtractUint16 -from ..naryexpr import Concat -from ..scratchvar import ScratchVar - -from .type import TypeSpec, BaseType, ComputedValue -from .bool import ( +from pyteal.types import TealType +from pyteal.errors import TealInputError +from pyteal.ast.expr import Expr +from pyteal.ast.seq import Seq +from pyteal.ast.int import Int +from pyteal.ast.bytes import Bytes +from pyteal.ast.unaryexpr import Len +from pyteal.ast.binaryexpr import ExtractUint16 +from pyteal.ast.naryexpr import Concat +from pyteal.ast.scratchvar import ScratchVar + +from pyteal.ast.abi.type import TypeSpec, BaseType, ComputedValue +from pyteal.ast.abi.bool import ( Bool, BoolTypeSpec, consecutiveBoolInstanceNum, @@ -31,8 +29,8 @@ encodeBoolSequence, boolAwareStaticByteLength, ) -from .uint import NUM_BITS_IN_BYTE, Uint16 -from .util import substringForDecoding +from pyteal.ast.abi.uint import NUM_BITS_IN_BYTE, Uint16 +from pyteal.ast.abi.util import substringForDecoding def encodeTuple(values: Sequence[BaseType]) -> Expr: diff --git a/pyteal/ast/abi/tuple_test.py b/pyteal/ast/abi/tuple_test.py index 7d5385f26..6fb9401e4 100644 --- a/pyteal/ast/abi/tuple_test.py +++ b/pyteal/ast/abi/tuple_test.py @@ -1,19 +1,20 @@ from typing import NamedTuple, List, Callable import pytest -from ... import * -from .tuple import encodeTuple, indexTuple, TupleElement -from .bool import encodeBoolSequence -from .util import substringForDecoding -from .type_test import ContainerType +import pyteal as pt +from pyteal import abi +from pyteal.ast.abi.tuple import encodeTuple, indexTuple, TupleElement +from pyteal.ast.abi.bool import encodeBoolSequence +from pyteal.ast.abi.util import substringForDecoding +from pyteal.ast.abi.type_test import ContainerType -options = CompileOptions(version=5) +options = pt.CompileOptions(version=5) def test_encodeTuple(): class EncodeTest(NamedTuple): types: List[abi.BaseType] - expected: Expr + expected: pt.Expr # variables used to construct the tests uint64_a = abi.Uint64() @@ -26,15 +27,15 @@ class EncodeTest(NamedTuple): dynamic_array_a = abi.DynamicArray(abi.Uint64TypeSpec()) dynamic_array_b = abi.DynamicArray(abi.Uint16TypeSpec()) dynamic_array_c = abi.DynamicArray(abi.BoolTypeSpec()) - tail_holder = ScratchVar() - encoded_tail = ScratchVar() + tail_holder = pt.ScratchVar() + encoded_tail = pt.ScratchVar() tests: List[EncodeTest] = [ - EncodeTest(types=[], expected=Bytes("")), + EncodeTest(types=[], expected=pt.Bytes("")), EncodeTest(types=[uint64_a], expected=uint64_a.encode()), EncodeTest( types=[uint64_a, uint64_b], - expected=Concat(uint64_a.encode(), uint64_b.encode()), + expected=pt.Concat(uint64_a.encode(), uint64_b.encode()), ), EncodeTest(types=[bool_a], expected=bool_a.encode()), EncodeTest( @@ -42,15 +43,15 @@ class EncodeTest(NamedTuple): ), EncodeTest( types=[bool_a, bool_b, uint64_a], - expected=Concat(encodeBoolSequence([bool_a, bool_b]), uint64_a.encode()), + expected=pt.Concat(encodeBoolSequence([bool_a, bool_b]), uint64_a.encode()), ), EncodeTest( types=[uint64_a, bool_a, bool_b], - expected=Concat(uint64_a.encode(), encodeBoolSequence([bool_a, bool_b])), + expected=pt.Concat(uint64_a.encode(), encodeBoolSequence([bool_a, bool_b])), ), EncodeTest( types=[uint64_a, bool_a, bool_b, uint64_b], - expected=Concat( + expected=pt.Concat( uint64_a.encode(), encodeBoolSequence([bool_a, bool_b]), uint64_b.encode(), @@ -58,14 +59,14 @@ class EncodeTest(NamedTuple): ), EncodeTest( types=[uint64_a, bool_a, uint64_b, bool_b], - expected=Concat( + expected=pt.Concat( uint64_a.encode(), bool_a.encode(), uint64_b.encode(), bool_b.encode() ), ), EncodeTest(types=[tuple_a], expected=tuple_a.encode()), EncodeTest( types=[uint64_a, tuple_a, bool_a, bool_b], - expected=Concat( + expected=pt.Concat( uint64_a.encode(), tuple_a.encode(), encodeBoolSequence([bool_a, bool_b]), @@ -73,8 +74,8 @@ class EncodeTest(NamedTuple): ), EncodeTest( types=[dynamic_array_a], - expected=Concat( - Seq( + expected=pt.Concat( + pt.Seq( encoded_tail.store(dynamic_array_a.encode()), tail_holder.store(encoded_tail.load()), uint16_a.set(2), @@ -85,9 +86,9 @@ class EncodeTest(NamedTuple): ), EncodeTest( types=[uint64_a, dynamic_array_a], - expected=Concat( + expected=pt.Concat( uint64_a.encode(), - Seq( + pt.Seq( encoded_tail.store(dynamic_array_a.encode()), tail_holder.store(encoded_tail.load()), uint16_a.set(8 + 2), @@ -98,9 +99,9 @@ class EncodeTest(NamedTuple): ), EncodeTest( types=[uint64_a, dynamic_array_a, uint64_b], - expected=Concat( + expected=pt.Concat( uint64_a.encode(), - Seq( + pt.Seq( encoded_tail.store(dynamic_array_a.encode()), tail_holder.store(encoded_tail.load()), uint16_a.set(8 + 2 + 8), @@ -112,9 +113,9 @@ class EncodeTest(NamedTuple): ), EncodeTest( types=[uint64_a, dynamic_array_a, bool_a, bool_b], - expected=Concat( + expected=pt.Concat( uint64_a.encode(), - Seq( + pt.Seq( encoded_tail.store(dynamic_array_a.encode()), tail_holder.store(encoded_tail.load()), uint16_a.set(8 + 2 + 1), @@ -126,19 +127,21 @@ class EncodeTest(NamedTuple): ), EncodeTest( types=[uint64_a, dynamic_array_a, uint64_b, dynamic_array_b], - expected=Concat( + expected=pt.Concat( uint64_a.encode(), - Seq( + pt.Seq( encoded_tail.store(dynamic_array_a.encode()), tail_holder.store(encoded_tail.load()), uint16_a.set(8 + 2 + 8 + 2), - uint16_b.set(uint16_a.get() + Len(encoded_tail.load())), + uint16_b.set(uint16_a.get() + pt.Len(encoded_tail.load())), uint16_a.encode(), ), uint64_b.encode(), - Seq( + pt.Seq( encoded_tail.store(dynamic_array_b.encode()), - tail_holder.store(Concat(tail_holder.load(), encoded_tail.load())), + tail_holder.store( + pt.Concat(tail_holder.load(), encoded_tail.load()) + ), uint16_a.set(uint16_b), uint16_a.encode(), ), @@ -155,27 +158,31 @@ class EncodeTest(NamedTuple): bool_b, dynamic_array_c, ], - expected=Concat( + expected=pt.Concat( uint64_a.encode(), - Seq( + pt.Seq( encoded_tail.store(dynamic_array_a.encode()), tail_holder.store(encoded_tail.load()), uint16_a.set(8 + 2 + 8 + 2 + 1 + 2), - uint16_b.set(uint16_a.get() + Len(encoded_tail.load())), + uint16_b.set(uint16_a.get() + pt.Len(encoded_tail.load())), uint16_a.encode(), ), uint64_b.encode(), - Seq( + pt.Seq( encoded_tail.store(dynamic_array_b.encode()), - tail_holder.store(Concat(tail_holder.load(), encoded_tail.load())), + tail_holder.store( + pt.Concat(tail_holder.load(), encoded_tail.load()) + ), uint16_a.set(uint16_b), - uint16_b.set(uint16_a.get() + Len(encoded_tail.load())), + uint16_b.set(uint16_a.get() + pt.Len(encoded_tail.load())), uint16_a.encode(), ), encodeBoolSequence([bool_a, bool_b]), - Seq( + pt.Seq( encoded_tail.store(dynamic_array_c.encode()), - tail_holder.store(Concat(tail_holder.load(), encoded_tail.load())), + tail_holder.store( + pt.Concat(tail_holder.load(), encoded_tail.load()) + ), uint16_a.set(uint16_b), uint16_a.encode(), ), @@ -186,29 +193,29 @@ class EncodeTest(NamedTuple): for i, test in enumerate(tests): expr = encodeTuple(test.types) - assert expr.type_of() == TealType.bytes + assert expr.type_of() == pt.TealType.bytes assert not expr.has_return() expected, _ = test.expected.__teal__(options) expected.addIncoming() - expected = TealBlock.NormalizeBlocks(expected) + expected = pt.TealBlock.NormalizeBlocks(expected) actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) if any(t.type_spec().is_dynamic() for t in test.types): - with TealComponent.Context.ignoreExprEquality(): - with TealComponent.Context.ignoreScratchSlotEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreScratchSlotEquality(): assert actual == expected, "Test at index {} failed".format(i) - assert TealBlock.MatchScratchSlotReferences( - TealBlock.GetReferencedScratchSlots(actual), - TealBlock.GetReferencedScratchSlots(expected), + assert pt.TealBlock.MatchScratchSlotReferences( + pt.TealBlock.GetReferencedScratchSlots(actual), + pt.TealBlock.GetReferencedScratchSlots(expected), ) continue - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected, "Test at index {} failed".format(i) @@ -216,7 +223,7 @@ def test_indexTuple(): class IndexTest(NamedTuple): types: List[abi.TypeSpec] typeIndex: int - expected: Callable[[abi.BaseType], Expr] + expected: Callable[[abi.BaseType], pt.Expr] # variables used to construct the tests uint64_t = abi.Uint64TypeSpec() @@ -226,7 +233,7 @@ class IndexTest(NamedTuple): dynamic_array_t1 = abi.DynamicArrayTypeSpec(abi.Uint64TypeSpec()) dynamic_array_t2 = abi.DynamicArrayTypeSpec(abi.Uint16TypeSpec()) - encoded = Bytes("encoded") + encoded = pt.Bytes("encoded") tests: List[IndexTest] = [ IndexTest( @@ -237,81 +244,81 @@ class IndexTest(NamedTuple): IndexTest( types=[uint64_t, uint64_t], typeIndex=0, - expected=lambda output: output.decode(encoded, length=Int(8)), + expected=lambda output: output.decode(encoded, length=pt.Int(8)), ), IndexTest( types=[uint64_t, uint64_t], typeIndex=1, - expected=lambda output: output.decode(encoded, startIndex=Int(8)), + expected=lambda output: output.decode(encoded, startIndex=pt.Int(8)), ), IndexTest( types=[uint64_t, byte_t, uint64_t], typeIndex=1, expected=lambda output: output.decode( - encoded, startIndex=Int(8), length=Int(1) + encoded, startIndex=pt.Int(8), length=pt.Int(1) ), ), IndexTest( types=[uint64_t, byte_t, uint64_t], typeIndex=2, expected=lambda output: output.decode( - encoded, startIndex=Int(9), length=Int(8) + encoded, startIndex=pt.Int(9), length=pt.Int(8) ), ), IndexTest( types=[bool_t], typeIndex=0, - expected=lambda output: output.decodeBit(encoded, Int(0)), + expected=lambda output: output.decodeBit(encoded, pt.Int(0)), ), IndexTest( types=[bool_t, bool_t], typeIndex=0, - expected=lambda output: output.decodeBit(encoded, Int(0)), + expected=lambda output: output.decodeBit(encoded, pt.Int(0)), ), IndexTest( types=[bool_t, bool_t], typeIndex=1, - expected=lambda output: output.decodeBit(encoded, Int(1)), + expected=lambda output: output.decodeBit(encoded, pt.Int(1)), ), IndexTest( types=[uint64_t, bool_t], typeIndex=1, - expected=lambda output: output.decodeBit(encoded, Int(8 * 8)), + expected=lambda output: output.decodeBit(encoded, pt.Int(8 * 8)), ), IndexTest( types=[uint64_t, bool_t, bool_t], typeIndex=1, - expected=lambda output: output.decodeBit(encoded, Int(8 * 8)), + expected=lambda output: output.decodeBit(encoded, pt.Int(8 * 8)), ), IndexTest( types=[uint64_t, bool_t, bool_t], typeIndex=2, - expected=lambda output: output.decodeBit(encoded, Int(8 * 8 + 1)), + expected=lambda output: output.decodeBit(encoded, pt.Int(8 * 8 + 1)), ), IndexTest( types=[bool_t, uint64_t], typeIndex=0, - expected=lambda output: output.decodeBit(encoded, Int(0)), + expected=lambda output: output.decodeBit(encoded, pt.Int(0)), ), IndexTest( types=[bool_t, uint64_t], typeIndex=1, - expected=lambda output: output.decode(encoded, startIndex=Int(1)), + expected=lambda output: output.decode(encoded, startIndex=pt.Int(1)), ), IndexTest( types=[bool_t, bool_t, uint64_t], typeIndex=0, - expected=lambda output: output.decodeBit(encoded, Int(0)), + expected=lambda output: output.decodeBit(encoded, pt.Int(0)), ), IndexTest( types=[bool_t, bool_t, uint64_t], typeIndex=1, - expected=lambda output: output.decodeBit(encoded, Int(1)), + expected=lambda output: output.decodeBit(encoded, pt.Int(1)), ), IndexTest( types=[bool_t, bool_t, uint64_t], typeIndex=2, - expected=lambda output: output.decode(encoded, startIndex=Int(1)), + expected=lambda output: output.decode(encoded, startIndex=pt.Int(1)), ), IndexTest( types=[tuple_t], typeIndex=0, expected=lambda output: output.decode(encoded) @@ -319,48 +326,52 @@ class IndexTest(NamedTuple): IndexTest( types=[byte_t, tuple_t], typeIndex=1, - expected=lambda output: output.decode(encoded, startIndex=Int(1)), + expected=lambda output: output.decode(encoded, startIndex=pt.Int(1)), ), IndexTest( types=[tuple_t, byte_t], typeIndex=0, expected=lambda output: output.decode( - encoded, startIndex=Int(0), length=Int(tuple_t.byte_length_static()) + encoded, + startIndex=pt.Int(0), + length=pt.Int(tuple_t.byte_length_static()), ), ), IndexTest( types=[byte_t, tuple_t, byte_t], typeIndex=1, expected=lambda output: output.decode( - encoded, startIndex=Int(1), length=Int(tuple_t.byte_length_static()) + encoded, + startIndex=pt.Int(1), + length=pt.Int(tuple_t.byte_length_static()), ), ), IndexTest( types=[dynamic_array_t1], typeIndex=0, expected=lambda output: output.decode( - encoded, startIndex=ExtractUint16(encoded, Int(0)) + encoded, startIndex=pt.ExtractUint16(encoded, pt.Int(0)) ), ), IndexTest( types=[byte_t, dynamic_array_t1], typeIndex=1, expected=lambda output: output.decode( - encoded, startIndex=ExtractUint16(encoded, Int(1)) + encoded, startIndex=pt.ExtractUint16(encoded, pt.Int(1)) ), ), IndexTest( types=[dynamic_array_t1, byte_t], typeIndex=0, expected=lambda output: output.decode( - encoded, startIndex=ExtractUint16(encoded, Int(0)) + encoded, startIndex=pt.ExtractUint16(encoded, pt.Int(0)) ), ), IndexTest( types=[byte_t, dynamic_array_t1, byte_t], typeIndex=1, expected=lambda output: output.decode( - encoded, startIndex=ExtractUint16(encoded, Int(1)) + encoded, startIndex=pt.ExtractUint16(encoded, pt.Int(1)) ), ), IndexTest( @@ -368,15 +379,15 @@ class IndexTest(NamedTuple): typeIndex=1, expected=lambda output: output.decode( encoded, - startIndex=ExtractUint16(encoded, Int(1)), - endIndex=ExtractUint16(encoded, Int(4)), + startIndex=pt.ExtractUint16(encoded, pt.Int(1)), + endIndex=pt.ExtractUint16(encoded, pt.Int(4)), ), ), IndexTest( types=[byte_t, dynamic_array_t1, byte_t, dynamic_array_t2], typeIndex=3, expected=lambda output: output.decode( - encoded, startIndex=ExtractUint16(encoded, Int(4)) + encoded, startIndex=pt.ExtractUint16(encoded, pt.Int(4)) ), ), IndexTest( @@ -384,15 +395,15 @@ class IndexTest(NamedTuple): typeIndex=1, expected=lambda output: output.decode( encoded, - startIndex=ExtractUint16(encoded, Int(1)), - endIndex=ExtractUint16(encoded, Int(4)), + startIndex=pt.ExtractUint16(encoded, pt.Int(1)), + endIndex=pt.ExtractUint16(encoded, pt.Int(4)), ), ), IndexTest( types=[byte_t, dynamic_array_t1, tuple_t, dynamic_array_t2], typeIndex=3, expected=lambda output: output.decode( - encoded, startIndex=ExtractUint16(encoded, Int(4)) + encoded, startIndex=pt.ExtractUint16(encoded, pt.Int(4)) ), ), IndexTest( @@ -400,15 +411,15 @@ class IndexTest(NamedTuple): typeIndex=1, expected=lambda output: output.decode( encoded, - startIndex=ExtractUint16(encoded, Int(1)), - endIndex=ExtractUint16(encoded, Int(4)), + startIndex=pt.ExtractUint16(encoded, pt.Int(1)), + endIndex=pt.ExtractUint16(encoded, pt.Int(4)), ), ), IndexTest( types=[byte_t, dynamic_array_t1, bool_t, bool_t, dynamic_array_t2], typeIndex=4, expected=lambda output: output.decode( - encoded, startIndex=ExtractUint16(encoded, Int(4)) + encoded, startIndex=pt.ExtractUint16(encoded, pt.Int(4)) ), ), ] @@ -416,18 +427,18 @@ class IndexTest(NamedTuple): for i, test in enumerate(tests): output = test.types[test.typeIndex].new_instance() expr = indexTuple(test.types, encoded, test.typeIndex, output) - assert expr.type_of() == TealType.none + assert expr.type_of() == pt.TealType.none assert not expr.has_return() expected, _ = test.expected(output).__teal__(options) expected.addIncoming() - expected = TealBlock.NormalizeBlocks(expected) + expected = pt.TealBlock.NormalizeBlocks(expected) actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected, "Test at index {} failed".format(i) with pytest.raises(ValueError): @@ -587,13 +598,13 @@ def test_TupleTypeSpec_byte_length_static(): def test_Tuple_decode(): - encoded = Bytes("encoded") + encoded = pt.Bytes("encoded") tupleValue = abi.Tuple(abi.Uint64TypeSpec()) - for startIndex in (None, Int(1)): - for endIndex in (None, Int(2)): - for length in (None, Int(3)): + for startIndex in (None, pt.Int(1)): + for endIndex in (None, pt.Int(2)): + for length in (None, pt.Int(3)): if endIndex is not None and length is not None: - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): tupleValue.decode( encoded, startIndex=startIndex, @@ -605,7 +616,7 @@ def test_Tuple_decode(): expr = tupleValue.decode( encoded, startIndex=startIndex, endIndex=endIndex, length=length ) - assert expr.type_of() == TealType.none + assert expr.type_of() == pt.TealType.none assert not expr.has_return() expectedExpr = tupleValue.stored_value.store( @@ -615,13 +626,13 @@ def test_Tuple_decode(): ) expected, _ = expectedExpr.__teal__(options) expected.addIncoming() - expected = TealBlock.NormalizeBlocks(expected) + expected = pt.TealBlock.NormalizeBlocks(expected) actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected @@ -633,35 +644,35 @@ def test_Tuple_set(): uint16 = abi.Uint16() uint32 = abi.Uint32() - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): tupleValue.set() - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): tupleValue.set(uint8, uint16) - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): tupleValue.set(uint8, uint16, uint32, uint32) - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): tupleValue.set(uint8, uint32, uint16) - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): tupleValue.set(uint8, uint16, uint16) expr = tupleValue.set(uint8, uint16, uint32) - assert expr.type_of() == TealType.none + assert expr.type_of() == pt.TealType.none assert not expr.has_return() expectedExpr = tupleValue.stored_value.store(encodeTuple([uint8, uint16, uint32])) expected, _ = expectedExpr.__teal__(options) expected.addIncoming() - expected = TealBlock.NormalizeBlocks(expected) + expected = pt.TealBlock.NormalizeBlocks(expected) actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected @@ -669,46 +680,50 @@ def test_Tuple_set_Computed(): tupleValue = abi.Tuple( abi.Uint8TypeSpec(), abi.Uint16TypeSpec(), abi.Uint32TypeSpec() ) - computed = ContainerType(tupleValue.type_spec(), Bytes("internal representation")) + computed = ContainerType( + tupleValue.type_spec(), pt.Bytes("internal representation") + ) expr = tupleValue.set(computed) - assert expr.type_of() == TealType.none + assert expr.type_of() == pt.TealType.none assert not expr.has_return() - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(None, Op.byte, '"internal representation"'), - TealOp(None, Op.store, tupleValue.stored_value.slot), + pt.TealOp(None, pt.Op.byte, '"internal representation"'), + pt.TealOp(None, pt.Op.store, tupleValue.stored_value.slot), ] ) actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): tupleValue.set(computed, computed) - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): tupleValue.set( - ContainerType(abi.TupleTypeSpec(abi.ByteTypeSpec()), Bytes(b"a")) + ContainerType(abi.TupleTypeSpec(abi.ByteTypeSpec()), pt.Bytes(b"a")) ) def test_Tuple_encode(): tupleValue = abi.Tuple(abi.Uint64TypeSpec()) expr = tupleValue.encode() - assert expr.type_of() == TealType.bytes + assert expr.type_of() == pt.TealType.bytes assert not expr.has_return() - expected = TealSimpleBlock([TealOp(None, Op.load, tupleValue.stored_value.slot)]) + expected = pt.TealSimpleBlock( + [pt.TealOp(None, pt.Op.load, tupleValue.stored_value.slot)] + ) actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected @@ -726,17 +741,17 @@ def test_Tuple_length(): for i, test in enumerate(tests): tupleValue = abi.Tuple(*test) expr = tupleValue.length() - assert expr.type_of() == TealType.uint64 + assert expr.type_of() == pt.TealType.uint64 assert not expr.has_return() expectedLength = len(test) - expected = TealSimpleBlock([TealOp(None, Op.int, expectedLength)]) + expected = pt.TealSimpleBlock([pt.TealOp(None, pt.Op.int, expectedLength)]) actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected, "Test at index {} failed".format(i) @@ -759,10 +774,10 @@ def test_Tuple_getitem(): assert element.tuple is tupleValue, "Test at index {} failed".format(i) assert element.index == j, "Test at index {} failed".format(i) - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): tupleValue[-1] - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): tupleValue[len(test)] @@ -784,17 +799,17 @@ def test_TupleElement_store_into(): output = test[j].new_instance() expr = element.store_into(output) - assert expr.type_of() == TealType.none + assert expr.type_of() == pt.TealType.none assert not expr.has_return() expectedExpr = indexTuple(test, tupleValue.encode(), j, output) expected, _ = expectedExpr.__teal__(options) expected.addIncoming() - expected = TealBlock.NormalizeBlocks(expected) + expected = pt.TealBlock.NormalizeBlocks(expected) actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected, "Test at index {} failed".format(i) diff --git a/pyteal/ast/abi/type.py b/pyteal/ast/abi/type.py index 86b36f595..97304062f 100644 --- a/pyteal/ast/abi/type.py +++ b/pyteal/ast/abi/type.py @@ -1,11 +1,11 @@ from typing import TypeVar, Generic, Callable, Final, cast from abc import ABC, abstractmethod -from ...errors import TealInputError -from ...types import TealType -from ..expr import Expr -from ..scratchvar import ScratchVar -from ..seq import Seq +from pyteal.errors import TealInputError +from pyteal.types import TealType +from pyteal.ast.expr import Expr +from pyteal.ast.scratchvar import ScratchVar +from pyteal.ast.seq import Seq class TypeSpec(ABC): diff --git a/pyteal/ast/abi/type_test.py b/pyteal/ast/abi/type_test.py index b556671b3..56a9e6c76 100644 --- a/pyteal/ast/abi/type_test.py +++ b/pyteal/ast/abi/type_test.py @@ -1,19 +1,20 @@ -from ... import * +import pyteal as pt +from pyteal import abi -options = CompileOptions(version=5) +options = pt.CompileOptions(version=5) class ContainerType(abi.ComputedValue): - def __init__(self, type_spec: abi.TypeSpec, encodings: Expr): + def __init__(self, type_spec: abi.TypeSpec, encodings: pt.Expr): self.type_spec = type_spec self.encodings = encodings def produced_type_spec(self) -> abi.TypeSpec: return self.type_spec - def store_into(self, output: abi.BaseType) -> Expr: + def store_into(self, output: abi.BaseType) -> pt.Expr: if output.type_spec() != self.type_spec: - raise TealInputError( + raise pt.TealInputError( f"expected type_spec {self.type_spec} but get {output.type_spec()}" ) return output.stored_value.store(self.encodings) @@ -21,29 +22,29 @@ def store_into(self, output: abi.BaseType) -> Expr: def test_ComputedType_use(): for value in (0, 1, 2, 3, 12345): - dummyComputedType = ContainerType(abi.Uint64TypeSpec(), Int(value)) - expr = dummyComputedType.use(lambda output: Int(2) * output.get()) - assert expr.type_of() == TealType.uint64 + dummyComputedType = ContainerType(abi.Uint64TypeSpec(), pt.Int(value)) + expr = dummyComputedType.use(lambda output: pt.Int(2) * output.get()) + assert expr.type_of() == pt.TealType.uint64 assert not expr.has_return() actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - assert type(actual) is TealSimpleBlock - assert actual.ops[1].op == Op.store - assert type(actual.ops[1].args[0]) is ScratchSlot + assert type(actual) is pt.TealSimpleBlock + assert actual.ops[1].op == pt.Op.store + assert type(actual.ops[1].args[0]) is pt.ScratchSlot actualSlot = actual.ops[1].args[0] - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(None, Op.int, value), - TealOp(None, Op.store, actualSlot), - TealOp(None, Op.int, 2), - TealOp(None, Op.load, actualSlot), - TealOp(None, Op.mul), + pt.TealOp(None, pt.Op.int, value), + pt.TealOp(None, pt.Op.store, actualSlot), + pt.TealOp(None, pt.Op.int, 2), + pt.TealOp(None, pt.Op.load, actualSlot), + pt.TealOp(None, pt.Op.mul), ] ) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected diff --git a/pyteal/ast/abi/uint.py b/pyteal/ast/abi/uint.py index 6c745a902..ea8e8eded 100644 --- a/pyteal/ast/abi/uint.py +++ b/pyteal/ast/abi/uint.py @@ -7,19 +7,19 @@ ) from abc import abstractmethod -from ...types import TealType -from ...errors import TealInputError -from ..scratchvar import ScratchVar -from ..expr import Expr -from ..seq import Seq -from ..assert_ import Assert -from ..substring import Suffix -from ..int import Int -from ..bytes import Bytes -from ..unaryexpr import Itob, Btoi -from ..binaryexpr import GetByte, ExtractUint16, ExtractUint32, ExtractUint64 -from ..ternaryexpr import SetByte -from .type import ComputedValue, TypeSpec, BaseType +from pyteal.types import TealType +from pyteal.errors import TealInputError +from pyteal.ast.scratchvar import ScratchVar +from pyteal.ast.expr import Expr +from pyteal.ast.seq import Seq +from pyteal.ast.assert_ import Assert +from pyteal.ast.substring import Suffix +from pyteal.ast.int import Int +from pyteal.ast.bytes import Bytes +from pyteal.ast.unaryexpr import Itob, Btoi +from pyteal.ast.binaryexpr import GetByte, ExtractUint16, ExtractUint32, ExtractUint64 +from pyteal.ast.ternaryexpr import SetByte +from pyteal.ast.abi.type import ComputedValue, TypeSpec, BaseType NUM_BITS_IN_BYTE = 8 diff --git a/pyteal/ast/abi/uint_test.py b/pyteal/ast/abi/uint_test.py index 55d081dbb..7e9638454 100644 --- a/pyteal/ast/abi/uint_test.py +++ b/pyteal/ast/abi/uint_test.py @@ -1,10 +1,11 @@ from typing import List, Tuple, NamedTuple, Callable, Union, Optional -from .type_test import ContainerType -from ... import * +from pyteal.ast.abi.type_test import ContainerType +import pyteal as pt +from pyteal import abi import pytest -options = CompileOptions(version=5) +options = pt.CompileOptions(version=5) class UintTestData(NamedTuple): @@ -14,14 +15,14 @@ class UintTestData(NamedTuple): maxValue: int checkUpperBound: bool expectedDecoding: Callable[ - [Expr, Optional[Expr], Optional[Expr], Optional[Expr]], Expr + [pt.Expr, Optional[pt.Expr], Optional[pt.Expr], Optional[pt.Expr]], pt.Expr ] - expectedEncoding: Callable[[abi.Uint], Expr] + expectedEncoding: Callable[[abi.Uint], pt.Expr] -def noneToInt0(value: Union[None, Expr]): +def noneToInt0(value: Union[None, pt.Expr]): if value is None: - return Int(0) + return pt.Int(0) return value @@ -32,11 +33,11 @@ def noneToInt0(value: Union[None, Expr]): expectedBits=8, maxValue=2**8 - 1, checkUpperBound=True, - expectedDecoding=lambda encoded, startIndex, endIndex, length: GetByte( + expectedDecoding=lambda encoded, startIndex, endIndex, length: pt.GetByte( encoded, noneToInt0(startIndex) ), - expectedEncoding=lambda uintType: SetByte( - Bytes(b"\x00"), Int(0), uintType.get() + expectedEncoding=lambda uintType: pt.SetByte( + pt.Bytes(b"\x00"), pt.Int(0), uintType.get() ), ), UintTestData( @@ -45,10 +46,10 @@ def noneToInt0(value: Union[None, Expr]): expectedBits=16, maxValue=2**16 - 1, checkUpperBound=True, - expectedDecoding=lambda encoded, startIndex, endIndex, length: ExtractUint16( + expectedDecoding=lambda encoded, startIndex, endIndex, length: pt.ExtractUint16( encoded, noneToInt0(startIndex) ), - expectedEncoding=lambda uintType: Suffix(Itob(uintType.get()), Int(6)), + expectedEncoding=lambda uintType: pt.Suffix(pt.Itob(uintType.get()), pt.Int(6)), ), UintTestData( uintType=abi.Uint32TypeSpec(), @@ -56,10 +57,10 @@ def noneToInt0(value: Union[None, Expr]): expectedBits=32, maxValue=2**32 - 1, checkUpperBound=True, - expectedDecoding=lambda encoded, startIndex, endIndex, length: ExtractUint32( + expectedDecoding=lambda encoded, startIndex, endIndex, length: pt.ExtractUint32( encoded, noneToInt0(startIndex) ), - expectedEncoding=lambda uintType: Suffix(Itob(uintType.get()), Int(4)), + expectedEncoding=lambda uintType: pt.Suffix(pt.Itob(uintType.get()), pt.Int(4)), ), UintTestData( uintType=abi.Uint64TypeSpec(), @@ -67,10 +68,10 @@ def noneToInt0(value: Union[None, Expr]): expectedBits=64, maxValue=2**64 - 1, checkUpperBound=False, - expectedDecoding=lambda encoded, startIndex, endIndex, length: Btoi(encoded) + expectedDecoding=lambda encoded, startIndex, endIndex, length: pt.Btoi(encoded) if startIndex is None and endIndex is None and length is None - else ExtractUint64(encoded, noneToInt0(startIndex)), - expectedEncoding=lambda uintType: Itob(uintType.get()), + else pt.ExtractUint64(encoded, noneToInt0(startIndex)), + expectedEncoding=lambda uintType: pt.Itob(uintType.get()), ), ] @@ -115,8 +116,8 @@ def test_UintTypeSpec_eq(): def test_UintTypeSpec_storage_type(): for test in testData: - assert test.uintType.storage_type() == TealType.uint64 - assert abi.BoolTypeSpec().storage_type() == TealType.uint64 + assert test.uintType.storage_type() == pt.TealType.uint64 + assert abi.BoolTypeSpec().storage_type() == pt.TealType.uint64 def test_UintTypeSpec_new_instance(): @@ -130,61 +131,61 @@ def test_Uint_set_static(): for value_to_set in (0, 1, 100, test.maxValue): value = test.uintType.new_instance() expr = value.set(value_to_set) - assert expr.type_of() == TealType.none + assert expr.type_of() == pt.TealType.none assert not expr.has_return() - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(None, Op.int, value_to_set), - TealOp(None, Op.store, value.stored_value.slot), + pt.TealOp(None, pt.Op.int, value_to_set), + pt.TealOp(None, pt.Op.store, value.stored_value.slot), ] ) actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): value.set(test.maxValue + 1) - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): value.set(-1) def test_Uint_set_expr(): for test in testData: value = test.uintType.new_instance() - expr = value.set(Int(10) + Int(1)) - assert expr.type_of() == TealType.none + expr = value.set(pt.Int(10) + pt.Int(1)) + assert expr.type_of() == pt.TealType.none assert not expr.has_return() upperBoundCheck = [] if test.checkUpperBound: upperBoundCheck = [ - TealOp(None, Op.load, value.stored_value.slot), - TealOp(None, Op.int, test.maxValue + 1), - TealOp(None, Op.lt), - TealOp(None, Op.assert_), + pt.TealOp(None, pt.Op.load, value.stored_value.slot), + pt.TealOp(None, pt.Op.int, test.maxValue + 1), + pt.TealOp(None, pt.Op.lt), + pt.TealOp(None, pt.Op.assert_), ] - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(None, Op.int, 10), - TealOp(None, Op.int, 1), - TealOp(None, Op.add), - TealOp(None, Op.store, value.stored_value.slot), + pt.TealOp(None, pt.Op.int, 10), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.add), + pt.TealOp(None, pt.Op.store, value.stored_value.slot), ] + upperBoundCheck ) actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected @@ -193,52 +194,52 @@ def test_Uint_set_copy(): value = test.uintType.new_instance() other = test.uintType.new_instance() expr = value.set(other) - assert expr.type_of() == TealType.none + assert expr.type_of() == pt.TealType.none assert not expr.has_return() - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(None, Op.load, other.stored_value.slot), - TealOp(None, Op.store, value.stored_value.slot), + pt.TealOp(None, pt.Op.load, other.stored_value.slot), + pt.TealOp(None, pt.Op.store, value.stored_value.slot), ] ) actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): value.set(abi.Bool()) def test_Uint_set_computed(): - byte_computed_value = ContainerType(abi.ByteTypeSpec(), Int(0x22)) + byte_computed_value = ContainerType(abi.ByteTypeSpec(), pt.Int(0x22)) for test in testData: - computed_value = ContainerType(test.uintType, Int(0x44)) + computed_value = ContainerType(test.uintType, pt.Int(0x44)) value = test.uintType.new_instance() expr = value.set(computed_value) - assert expr.type_of() == TealType.none + assert expr.type_of() == pt.TealType.none assert not expr.has_return() - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(None, Op.int, 0x44), - TealOp(None, Op.store, value.stored_value.slot), + pt.TealOp(None, pt.Op.int, 0x44), + pt.TealOp(None, pt.Op.store, value.stored_value.slot), ] ) actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): value.set(byte_computed_value) @@ -246,10 +247,12 @@ def test_Uint_get(): for test in testData: value = test.uintType.new_instance() expr = value.get() - assert expr.type_of() == TealType.uint64 + assert expr.type_of() == pt.TealType.uint64 assert not expr.has_return() - expected = TealSimpleBlock([TealOp(expr, Op.load, value.stored_value.slot)]) + expected = pt.TealSimpleBlock( + [pt.TealOp(expr, pt.Op.load, value.stored_value.slot)] + ) actual, _ = expr.__teal__(options) @@ -257,16 +260,16 @@ def test_Uint_get(): def test_Uint_decode(): - encoded = Bytes("encoded") + encoded = pt.Bytes("encoded") for test in testData: - for startIndex in (None, Int(1)): - for endIndex in (None, Int(2)): - for length in (None, Int(3)): + for startIndex in (None, pt.Int(1)): + for endIndex in (None, pt.Int(2)): + for length in (None, pt.Int(3)): value = test.uintType.new_instance() expr = value.decode( encoded, startIndex=startIndex, endIndex=endIndex, length=length ) - assert expr.type_of() == TealType.none + assert expr.type_of() == pt.TealType.none assert not expr.has_return() expectedDecoding = value.stored_value.store( @@ -274,13 +277,13 @@ def test_Uint_decode(): ) expected, _ = expectedDecoding.__teal__(options) expected.addIncoming() - expected = TealBlock.NormalizeBlocks(expected) + expected = pt.TealBlock.NormalizeBlocks(expected) actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected @@ -288,18 +291,18 @@ def test_Uint_encode(): for test in testData: value = test.uintType.new_instance() expr = value.encode() - assert expr.type_of() == TealType.bytes + assert expr.type_of() == pt.TealType.bytes assert not expr.has_return() expected, _ = test.expectedEncoding(value).__teal__(options) expected.addIncoming() - expected = TealBlock.NormalizeBlocks(expected) + expected = pt.TealBlock.NormalizeBlocks(expected) actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected @@ -313,19 +316,19 @@ def test_ByteUint8_mutual_conversion(): other = type_a.new_instance() expr = type_b_instance.set(other) - assert expr.type_of() == TealType.none + assert expr.type_of() == pt.TealType.none assert not expr.has_return() - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(None, Op.load, other.stored_value.slot), - TealOp(None, Op.store, type_b_instance.stored_value.slot), + pt.TealOp(None, pt.Op.load, other.stored_value.slot), + pt.TealOp(None, pt.Op.store, type_b_instance.stored_value.slot), ] ) actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected diff --git a/pyteal/ast/abi/util.py b/pyteal/ast/abi/util.py index e9077ded6..7a638ca0f 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 ...errors import TealInputError -from ..expr import Expr -from ..int import Int -from ..substring import Extract, Substring, Suffix -from .type import TypeSpec +from pyteal.errors import TealInputError +from pyteal.ast.expr import Expr +from pyteal.ast.int import Int +from pyteal.ast.substring import Extract, Substring, Suffix +from pyteal.ast.abi.type import TypeSpec def substringForDecoding( @@ -82,8 +82,8 @@ def type_spec_from_annotation(annotation: Any) -> TypeSpec: Returns: The TypeSpec that corresponds to the input annotation. """ - from .bool import BoolTypeSpec, Bool - from .uint import ( + from pyteal.ast.abi.bool import BoolTypeSpec, Bool + from pyteal.ast.abi.uint import ( ByteTypeSpec, Byte, Uint8TypeSpec, @@ -95,9 +95,9 @@ def type_spec_from_annotation(annotation: Any) -> TypeSpec: Uint64TypeSpec, Uint64, ) - from .array_dynamic import DynamicArrayTypeSpec, DynamicArray - from .array_static import StaticArrayTypeSpec, StaticArray - from .tuple import ( + from pyteal.ast.abi.array_dynamic import DynamicArrayTypeSpec, DynamicArray + from pyteal.ast.abi.array_static import StaticArrayTypeSpec, StaticArray + from pyteal.ast.abi.tuple import ( TupleTypeSpec, Tuple, Tuple0, @@ -107,6 +107,8 @@ def type_spec_from_annotation(annotation: Any) -> TypeSpec: Tuple4, Tuple5, ) + from pyteal.ast.abi.string import StringTypeSpec, String + from pyteal.ast.abi.address import AddressTypeSpec, Address origin = get_origin(annotation) if origin is None: @@ -144,6 +146,16 @@ def type_spec_from_annotation(annotation: Any) -> TypeSpec: raise TypeError("Uint64 expects 0 type arguments. Got: {}".format(args)) return Uint64TypeSpec() + if origin is String: + if len(args) != 0: + raise TypeError("String expects 0 arguments. Got: {}".format(args)) + return StringTypeSpec() + + if origin is Address: + if len(args) != 0: + raise TypeError("Address expects 0 arguments. Got: {}".format(args)) + return AddressTypeSpec() + if origin is DynamicArray: if len(args) != 1: raise TypeError( diff --git a/pyteal/ast/abi/util_test.py b/pyteal/ast/abi/util_test.py index b4026ee0a..954d4ac9d 100644 --- a/pyteal/ast/abi/util_test.py +++ b/pyteal/ast/abi/util_test.py @@ -2,67 +2,74 @@ from inspect import isabstract import pytest -from ... import * -from .util import ( +import pyteal as pt +from pyteal import abi +from pyteal.ast.abi.util import ( substringForDecoding, int_literal_from_annotation, type_spec_from_annotation, ) -options = CompileOptions(version=5) +options = pt.CompileOptions(version=5) def test_substringForDecoding(): class SubstringTest(NamedTuple): - startIndex: Optional[Expr] - endIndex: Optional[Expr] - length: Optional[Expr] - expected: Union[Expr, Any] + startIndex: Optional[pt.Expr] + endIndex: Optional[pt.Expr] + length: Optional[pt.Expr] + expected: Union[pt.Expr, Any] - encoded = Bytes("encoded") + encoded = pt.Bytes("encoded") tests: List[SubstringTest] = [ SubstringTest(startIndex=None, endIndex=None, length=None, expected=encoded), SubstringTest( startIndex=None, endIndex=None, - length=Int(4), - expected=Extract(encoded, Int(0), Int(4)), + length=pt.Int(4), + expected=pt.Extract(encoded, pt.Int(0), pt.Int(4)), ), SubstringTest( startIndex=None, - endIndex=Int(4), + endIndex=pt.Int(4), length=None, - expected=Substring(encoded, Int(0), Int(4)), + expected=pt.Substring(encoded, pt.Int(0), pt.Int(4)), ), SubstringTest( - startIndex=None, endIndex=Int(4), length=Int(5), expected=TealInputError + startIndex=None, + endIndex=pt.Int(4), + length=pt.Int(5), + expected=pt.TealInputError, ), SubstringTest( - startIndex=Int(4), + startIndex=pt.Int(4), endIndex=None, length=None, - expected=Suffix(encoded, Int(4)), + expected=pt.Suffix(encoded, pt.Int(4)), ), SubstringTest( - startIndex=Int(4), + startIndex=pt.Int(4), endIndex=None, - length=Int(5), - expected=Extract(encoded, Int(4), Int(5)), + length=pt.Int(5), + expected=pt.Extract(encoded, pt.Int(4), pt.Int(5)), ), SubstringTest( - startIndex=Int(4), - endIndex=Int(5), + startIndex=pt.Int(4), + endIndex=pt.Int(5), length=None, - expected=Substring(encoded, Int(4), Int(5)), + expected=pt.Substring(encoded, pt.Int(4), pt.Int(5)), ), SubstringTest( - startIndex=Int(4), endIndex=Int(5), length=Int(6), expected=TealInputError + startIndex=pt.Int(4), + endIndex=pt.Int(5), + length=pt.Int(6), + expected=pt.TealInputError, ), ] for i, test in enumerate(tests): - if not isinstance(test.expected, Expr): + if not isinstance(test.expected, pt.Expr): with pytest.raises(test.expected): substringForDecoding( encoded, @@ -78,18 +85,18 @@ class SubstringTest(NamedTuple): endIndex=test.endIndex, length=test.length, ) - assert expr.type_of() == TealType.bytes + assert expr.type_of() == pt.TealType.bytes assert not expr.has_return() - expected, _ = cast(Expr, test.expected).__teal__(options) + expected, _ = cast(pt.Expr, test.expected).__teal__(options) expected.addIncoming() - expected = TealBlock.NormalizeBlocks(expected) + expected = pt.TealBlock.NormalizeBlocks(expected) actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected, "Test at index {} failed".format(i) diff --git a/pyteal/ast/acct.py b/pyteal/ast/acct.py index d9c59a8d8..c08f2760b 100644 --- a/pyteal/ast/acct.py +++ b/pyteal/ast/acct.py @@ -1,12 +1,7 @@ -from typing import TYPE_CHECKING - -from ..types import TealType, require_type -from ..ir import Op -from .expr import Expr -from .maybe import MaybeValue - -if TYPE_CHECKING: - from ..compiler import CompileOptions +from pyteal.types import TealType, require_type +from pyteal.ir import Op +from pyteal.ast.expr import Expr +from pyteal.ast.maybe import MaybeValue class AccountParam: diff --git a/pyteal/ast/acct_test.py b/pyteal/ast/acct_test.py index 84af5cf07..52fc89132 100644 --- a/pyteal/ast/acct_test.py +++ b/pyteal/ast/acct_test.py @@ -1,80 +1,75 @@ -import pytest +import pyteal as pt -from .. import * - -# this is not necessary but mypy complains if it's not included -from .. import CompileOptions - -options = CompileOptions() -teal4Options = CompileOptions(version=4) -teal5Options = CompileOptions(version=5) -teal6Options = CompileOptions(version=6) +options = pt.CompileOptions() +teal4Options = pt.CompileOptions(version=4) +teal5Options = pt.CompileOptions(version=5) +teal6Options = pt.CompileOptions(version=6) def test_acct_param_balance_valid(): - arg = Int(1) - expr = AccountParam.balance(arg) - assert expr.type_of() == TealType.none - assert expr.value().type_of() == TealType.uint64 + arg = pt.Int(1) + expr = pt.AccountParam.balance(arg) + assert expr.type_of() == pt.TealType.none + assert expr.value().type_of() == pt.TealType.uint64 - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(arg, Op.int, 1), - TealOp(expr, Op.acct_params_get, "AcctBalance"), - TealOp(None, Op.store, expr.slotOk), - TealOp(None, Op.store, expr.slotValue), + pt.TealOp(arg, pt.Op.int, 1), + pt.TealOp(expr, pt.Op.acct_params_get, "AcctBalance"), + pt.TealOp(None, pt.Op.store, expr.slotOk), + pt.TealOp(None, pt.Op.store, expr.slotValue), ] ) actual, _ = expr.__teal__(teal6Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected def test_acct_param_min_balance_valid(): - arg = Int(0) - expr = AccountParam.minBalance(arg) - assert expr.type_of() == TealType.none - assert expr.value().type_of() == TealType.uint64 + arg = pt.Int(0) + expr = pt.AccountParam.minBalance(arg) + assert expr.type_of() == pt.TealType.none + assert expr.value().type_of() == pt.TealType.uint64 - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(arg, Op.int, 0), - TealOp(expr, Op.acct_params_get, "AcctMinBalance"), - TealOp(None, Op.store, expr.slotOk), - TealOp(None, Op.store, expr.slotValue), + pt.TealOp(arg, pt.Op.int, 0), + pt.TealOp(expr, pt.Op.acct_params_get, "AcctMinBalance"), + pt.TealOp(None, pt.Op.store, expr.slotOk), + pt.TealOp(None, pt.Op.store, expr.slotValue), ] ) actual, _ = expr.__teal__(teal6Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected def test_acct_param_auth_addr_valid(): - arg = Int(1) - expr = AccountParam.authAddr(arg) - assert expr.type_of() == TealType.none - assert expr.value().type_of() == TealType.bytes + arg = pt.Int(1) + expr = pt.AccountParam.authAddr(arg) + assert expr.type_of() == pt.TealType.none + assert expr.value().type_of() == pt.TealType.bytes - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(arg, Op.int, 1), - TealOp(expr, Op.acct_params_get, "AcctAuthAddr"), - TealOp(None, Op.store, expr.slotOk), - TealOp(None, Op.store, expr.slotValue), + pt.TealOp(arg, pt.Op.int, 1), + pt.TealOp(expr, pt.Op.acct_params_get, "AcctAuthAddr"), + pt.TealOp(None, pt.Op.store, expr.slotOk), + pt.TealOp(None, pt.Op.store, expr.slotValue), ] ) actual, _ = expr.__teal__(teal6Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected diff --git a/pyteal/ast/addr.py b/pyteal/ast/addr.py index 4c6b6bbe1..fb6687777 100644 --- a/pyteal/ast/addr.py +++ b/pyteal/ast/addr.py @@ -1,11 +1,11 @@ from typing import TYPE_CHECKING -from ..types import TealType, valid_address -from ..ir import TealOp, Op, TealBlock -from .leafexpr import LeafExpr +from pyteal.types import TealType, valid_address +from pyteal.ir import TealOp, Op, TealBlock +from pyteal.ast.leafexpr import LeafExpr if TYPE_CHECKING: - from ..compiler import CompileOptions + from pyteal.compiler import CompileOptions class Addr(LeafExpr): diff --git a/pyteal/ast/addr_test.py b/pyteal/ast/addr_test.py index 66d8b302f..9187d69c2 100644 --- a/pyteal/ast/addr_test.py +++ b/pyteal/ast/addr_test.py @@ -1,30 +1,30 @@ import pytest -from .. import * +import pyteal as pt def test_addr(): - expr = Addr("NJUWK3DJNZTWU2LFNRUW4Z3KNFSWY2LOM5VGSZLMNFXGO2TJMVWGS3THMF") - assert expr.type_of() == TealType.bytes - expected = TealSimpleBlock( + expr = pt.Addr("NJUWK3DJNZTWU2LFNRUW4Z3KNFSWY2LOM5VGSZLMNFXGO2TJMVWGS3THMF") + assert expr.type_of() == pt.TealType.bytes + expected = pt.TealSimpleBlock( [ - TealOp( + pt.TealOp( expr, - Op.addr, + pt.Op.addr, "NJUWK3DJNZTWU2LFNRUW4Z3KNFSWY2LOM5VGSZLMNFXGO2TJMVWGS3THMF", ) ] ) - actual, _ = expr.__teal__(CompileOptions()) + actual, _ = expr.__teal__(pt.CompileOptions()) assert actual == expected def test_addr_invalid(): - with pytest.raises(TealInputError): - Addr("NJUWK3DJNZTWU2LFNRUW4Z3KNFSWY2LOM5VGSZLMNFXGO2TJMVWGS3TH") + with pytest.raises(pt.TealInputError): + pt.Addr("NJUWK3DJNZTWU2LFNRUW4Z3KNFSWY2LOM5VGSZLMNFXGO2TJMVWGS3TH") - with pytest.raises(TealInputError): - Addr("000000000000000000000000000000000000000000000000000000000") + with pytest.raises(pt.TealInputError): + pt.Addr("000000000000000000000000000000000000000000000000000000000") - with pytest.raises(TealInputError): - Addr(2) + with pytest.raises(pt.TealInputError): + pt.Addr(2) diff --git a/pyteal/ast/app.py b/pyteal/ast/app.py index 23e265094..adf18f6a7 100644 --- a/pyteal/ast/app.py +++ b/pyteal/ast/app.py @@ -1,16 +1,16 @@ from typing import TYPE_CHECKING from enum import Enum -from ..types import TealType, require_type -from ..ir import TealOp, Op, TealBlock -from .leafexpr import LeafExpr -from .expr import Expr -from .maybe import MaybeValue -from .int import EnumInt -from .global_ import Global +from pyteal.types import TealType, require_type +from pyteal.ir import TealOp, Op, TealBlock +from pyteal.ast.leafexpr import LeafExpr +from pyteal.ast.expr import Expr +from pyteal.ast.maybe import MaybeValue +from pyteal.ast.int import EnumInt +from pyteal.ast.global_ import Global if TYPE_CHECKING: - from ..compiler import CompileOptions + from pyteal.compiler import CompileOptions class OnComplete: diff --git a/pyteal/ast/app_test.py b/pyteal/ast/app_test.py index 95f6b4ac2..5d16ab876 100644 --- a/pyteal/ast/app_test.py +++ b/pyteal/ast/app_test.py @@ -1,679 +1,676 @@ import pytest -from .. import * +import pyteal as pt -# this is not necessary but mypy complains if it's not included -from .. import CompileOptions - -options = CompileOptions() -teal4Options = CompileOptions(version=4) -teal5Options = CompileOptions(version=5) +options = pt.CompileOptions() +teal4Options = pt.CompileOptions(version=4) +teal5Options = pt.CompileOptions(version=5) def test_on_complete(): - assert OnComplete.NoOp.__teal__(options)[0] == TealSimpleBlock( - [TealOp(OnComplete.NoOp, Op.int, "NoOp")] + assert pt.OnComplete.NoOp.__teal__(options)[0] == pt.TealSimpleBlock( + [pt.TealOp(pt.OnComplete.NoOp, pt.Op.int, "NoOp")] ) - assert OnComplete.OptIn.__teal__(options)[0] == TealSimpleBlock( - [TealOp(OnComplete.OptIn, Op.int, "OptIn")] + assert pt.OnComplete.OptIn.__teal__(options)[0] == pt.TealSimpleBlock( + [pt.TealOp(pt.OnComplete.OptIn, pt.Op.int, "OptIn")] ) - assert OnComplete.CloseOut.__teal__(options)[0] == TealSimpleBlock( - [TealOp(OnComplete.CloseOut, Op.int, "CloseOut")] + assert pt.OnComplete.CloseOut.__teal__(options)[0] == pt.TealSimpleBlock( + [pt.TealOp(pt.OnComplete.CloseOut, pt.Op.int, "CloseOut")] ) - assert OnComplete.ClearState.__teal__(options)[0] == TealSimpleBlock( - [TealOp(OnComplete.ClearState, Op.int, "ClearState")] + assert pt.OnComplete.ClearState.__teal__(options)[0] == pt.TealSimpleBlock( + [pt.TealOp(pt.OnComplete.ClearState, pt.Op.int, "ClearState")] ) - assert OnComplete.UpdateApplication.__teal__(options)[0] == TealSimpleBlock( - [TealOp(OnComplete.UpdateApplication, Op.int, "UpdateApplication")] + assert pt.OnComplete.UpdateApplication.__teal__(options)[0] == pt.TealSimpleBlock( + [pt.TealOp(pt.OnComplete.UpdateApplication, pt.Op.int, "UpdateApplication")] ) - assert OnComplete.DeleteApplication.__teal__(options)[0] == TealSimpleBlock( - [TealOp(OnComplete.DeleteApplication, Op.int, "DeleteApplication")] + assert pt.OnComplete.DeleteApplication.__teal__(options)[0] == pt.TealSimpleBlock( + [pt.TealOp(pt.OnComplete.DeleteApplication, pt.Op.int, "DeleteApplication")] ) def test_app_id(): - expr = App.id() - assert expr.type_of() == TealType.uint64 - with TealComponent.Context.ignoreExprEquality(): + expr = pt.App.id() + assert expr.type_of() == pt.TealType.uint64 + with pt.TealComponent.Context.ignoreExprEquality(): assert ( expr.__teal__(options)[0] - == Global.current_application_id().__teal__(options)[0] + == pt.Global.current_application_id().__teal__(options)[0] ) def test_opted_in(): - args = [Int(1), Int(12)] - expr = App.optedIn(args[0], args[1]) - assert expr.type_of() == TealType.uint64 + args = [pt.Int(1), pt.Int(12)] + expr = pt.App.optedIn(args[0], args[1]) + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(args[0], Op.int, 1), - TealOp(args[1], Op.int, 12), - TealOp(expr, Op.app_opted_in), + pt.TealOp(args[0], pt.Op.int, 1), + pt.TealOp(args[1], pt.Op.int, 12), + pt.TealOp(expr, pt.Op.app_opted_in), ] ) actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected def test_opted_in_direct_ref(): - args = [Bytes("sender address"), Int(100)] - expr = App.optedIn(args[0], args[1]) - assert expr.type_of() == TealType.uint64 + args = [pt.Bytes("sender address"), pt.Int(100)] + expr = pt.App.optedIn(args[0], args[1]) + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(args[0], Op.byte, '"sender address"'), - TealOp(args[1], Op.int, 100), - TealOp(expr, Op.app_opted_in), + pt.TealOp(args[0], pt.Op.byte, '"sender address"'), + pt.TealOp(args[1], pt.Op.int, 100), + pt.TealOp(expr, pt.Op.app_opted_in), ] ) actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected def test_opted_in_invalid(): - with pytest.raises(TealTypeError): - App.optedIn(Bytes("sender"), Bytes("100")) + with pytest.raises(pt.TealTypeError): + pt.App.optedIn(pt.Bytes("sender"), pt.Bytes("100")) - with pytest.raises(TealTypeError): - App.optedIn(Int(123456), Bytes("364")) + with pytest.raises(pt.TealTypeError): + pt.App.optedIn(pt.Int(123456), pt.Bytes("364")) def test_local_get(): - args = [Int(0), Bytes("key")] - expr = App.localGet(args[0], args[1]) - assert expr.type_of() == TealType.anytype + args = [pt.Int(0), pt.Bytes("key")] + expr = pt.App.localGet(args[0], args[1]) + assert expr.type_of() == pt.TealType.anytype - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(args[0], Op.int, 0), - TealOp(args[1], Op.byte, '"key"'), - TealOp(expr, Op.app_local_get), + pt.TealOp(args[0], pt.Op.int, 0), + pt.TealOp(args[1], pt.Op.byte, '"key"'), + pt.TealOp(expr, pt.Op.app_local_get), ] ) actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected def test_local_get_direct_ref(): - args = [Txn.sender(), Bytes("key")] - expr = App.localGet(args[0], args[1]) - assert expr.type_of() == TealType.anytype + args = [pt.Txn.sender(), pt.Bytes("key")] + expr = pt.App.localGet(args[0], args[1]) + assert expr.type_of() == pt.TealType.anytype - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(args[0], Op.txn, "Sender"), - TealOp(args[1], Op.byte, '"key"'), - TealOp(expr, Op.app_local_get), + pt.TealOp(args[0], pt.Op.txn, "Sender"), + pt.TealOp(args[1], pt.Op.byte, '"key"'), + pt.TealOp(expr, pt.Op.app_local_get), ] ) actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected def test_local_get_invalid(): - with pytest.raises(TealTypeError): - App.localGet(Txn.sender(), Int(1337)) + with pytest.raises(pt.TealTypeError): + pt.App.localGet(pt.Txn.sender(), pt.Int(1337)) - with pytest.raises(TealTypeError): - App.localGet(Int(0), Int(1)) + with pytest.raises(pt.TealTypeError): + pt.App.localGet(pt.Int(0), pt.Int(1)) def test_local_get_ex(): - args = [Int(0), Int(6), Bytes("key")] - expr = App.localGetEx(args[0], args[1], args[2]) - assert expr.type_of() == TealType.none - assert expr.value().type_of() == TealType.anytype + args = [pt.Int(0), pt.Int(6), pt.Bytes("key")] + expr = pt.App.localGetEx(args[0], args[1], args[2]) + assert expr.type_of() == pt.TealType.none + assert expr.value().type_of() == pt.TealType.anytype - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(args[0], Op.int, 0), - TealOp(args[1], Op.int, 6), - TealOp(args[2], Op.byte, '"key"'), - TealOp(expr, Op.app_local_get_ex), - TealOp(None, Op.store, expr.slotOk), - TealOp(None, Op.store, expr.slotValue), + pt.TealOp(args[0], pt.Op.int, 0), + pt.TealOp(args[1], pt.Op.int, 6), + pt.TealOp(args[2], pt.Op.byte, '"key"'), + pt.TealOp(expr, pt.Op.app_local_get_ex), + pt.TealOp(None, pt.Op.store, expr.slotOk), + pt.TealOp(None, pt.Op.store, expr.slotValue), ] ) actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected def test_local_get_ex_direct_ref(): - args = [Txn.sender(), Int(6), Bytes("key")] - expr = App.localGetEx(args[0], args[1], args[2]) - assert expr.type_of() == TealType.none - assert expr.value().type_of() == TealType.anytype + args = [pt.Txn.sender(), pt.Int(6), pt.Bytes("key")] + expr = pt.App.localGetEx(args[0], args[1], args[2]) + assert expr.type_of() == pt.TealType.none + assert expr.value().type_of() == pt.TealType.anytype - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(args[0], Op.txn, "Sender"), - TealOp(args[1], Op.int, 6), - TealOp(args[2], Op.byte, '"key"'), - TealOp(expr, Op.app_local_get_ex), - TealOp(None, Op.store, expr.slotOk), - TealOp(None, Op.store, expr.slotValue), + pt.TealOp(args[0], pt.Op.txn, "Sender"), + pt.TealOp(args[1], pt.Op.int, 6), + pt.TealOp(args[2], pt.Op.byte, '"key"'), + pt.TealOp(expr, pt.Op.app_local_get_ex), + pt.TealOp(None, pt.Op.store, expr.slotOk), + pt.TealOp(None, pt.Op.store, expr.slotValue), ] ) actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected def test_local_get_ex_invalid(): - with pytest.raises(TealTypeError): - App.localGetEx(Txn.sender(), Int(0), Int(0x123456)) + with pytest.raises(pt.TealTypeError): + pt.App.localGetEx(pt.Txn.sender(), pt.Int(0), pt.Int(0x123456)) - with pytest.raises(TealTypeError): - App.localGetEx(Int(0), Bytes("app"), Bytes("key")) + with pytest.raises(pt.TealTypeError): + pt.App.localGetEx(pt.Int(0), pt.Bytes("app"), pt.Bytes("key")) def test_global_get(): - arg = Bytes("key") - expr = App.globalGet(arg) - assert expr.type_of() == TealType.anytype + arg = pt.Bytes("key") + expr = pt.App.globalGet(arg) + assert expr.type_of() == pt.TealType.anytype - expected = TealSimpleBlock( - [TealOp(arg, Op.byte, '"key"'), TealOp(expr, Op.app_global_get)] + expected = pt.TealSimpleBlock( + [pt.TealOp(arg, pt.Op.byte, '"key"'), pt.TealOp(expr, pt.Op.app_global_get)] ) actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected def test_global_get_invalid(): - with pytest.raises(TealTypeError): - App.globalGet(Int(7)) + with pytest.raises(pt.TealTypeError): + pt.App.globalGet(pt.Int(7)) def test_global_get_ex(): - args = [Int(6), Bytes("key")] - expr = App.globalGetEx(args[0], args[1]) - assert expr.type_of() == TealType.none - assert expr.value().type_of() == TealType.anytype + args = [pt.Int(6), pt.Bytes("key")] + expr = pt.App.globalGetEx(args[0], args[1]) + assert expr.type_of() == pt.TealType.none + assert expr.value().type_of() == pt.TealType.anytype - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(args[0], Op.int, 6), - TealOp(args[1], Op.byte, '"key"'), - TealOp(expr, Op.app_global_get_ex), - TealOp(None, Op.store, expr.slotOk), - TealOp(None, Op.store, expr.slotValue), + pt.TealOp(args[0], pt.Op.int, 6), + pt.TealOp(args[1], pt.Op.byte, '"key"'), + pt.TealOp(expr, pt.Op.app_global_get_ex), + pt.TealOp(None, pt.Op.store, expr.slotOk), + pt.TealOp(None, pt.Op.store, expr.slotValue), ] ) actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected def test_global_get_ex_direct_ref(): - args = [Txn.applications[0], Bytes("key")] - expr = App.globalGetEx(args[0], args[1]) - assert expr.type_of() == TealType.none - assert expr.value().type_of() == TealType.anytype + args = [pt.Txn.applications[0], pt.Bytes("key")] + expr = pt.App.globalGetEx(args[0], args[1]) + assert expr.type_of() == pt.TealType.none + assert expr.value().type_of() == pt.TealType.anytype - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(args[0], Op.txna, "Applications", 0), - TealOp(args[1], Op.byte, '"key"'), - TealOp(expr, Op.app_global_get_ex), - TealOp(None, Op.store, expr.slotOk), - TealOp(None, Op.store, expr.slotValue), + pt.TealOp(args[0], pt.Op.txna, "Applications", 0), + pt.TealOp(args[1], pt.Op.byte, '"key"'), + pt.TealOp(expr, pt.Op.app_global_get_ex), + pt.TealOp(None, pt.Op.store, expr.slotOk), + pt.TealOp(None, pt.Op.store, expr.slotValue), ] ) actual, _ = expr.__teal__(teal4Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected def test_global_get_ex_invalid(): - with pytest.raises(TealTypeError): - App.globalGetEx(Bytes("app"), Int(12)) + with pytest.raises(pt.TealTypeError): + pt.App.globalGetEx(pt.Bytes("app"), pt.Int(12)) - with pytest.raises(TealTypeError): - App.globalGetEx(Int(0), Int(1)) + with pytest.raises(pt.TealTypeError): + pt.App.globalGetEx(pt.Int(0), pt.Int(1)) def test_local_put(): - args = [Int(0), Bytes("key"), Int(5)] - expr = App.localPut(args[0], args[1], args[2]) - assert expr.type_of() == TealType.none + args = [pt.Int(0), pt.Bytes("key"), pt.Int(5)] + expr = pt.App.localPut(args[0], args[1], args[2]) + assert expr.type_of() == pt.TealType.none - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(args[0], Op.int, 0), - TealOp(args[1], Op.byte, '"key"'), - TealOp(args[2], Op.int, 5), - TealOp(expr, Op.app_local_put), + pt.TealOp(args[0], pt.Op.int, 0), + pt.TealOp(args[1], pt.Op.byte, '"key"'), + pt.TealOp(args[2], pt.Op.int, 5), + pt.TealOp(expr, pt.Op.app_local_put), ] ) actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected def test_local_put_direct_ref(): - args = [Txn.sender(), Bytes("key"), Int(5)] - expr = App.localPut(args[0], args[1], args[2]) - assert expr.type_of() == TealType.none + args = [pt.Txn.sender(), pt.Bytes("key"), pt.Int(5)] + expr = pt.App.localPut(args[0], args[1], args[2]) + assert expr.type_of() == pt.TealType.none - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(args[0], Op.txn, "Sender"), - TealOp(args[1], Op.byte, '"key"'), - TealOp(args[2], Op.int, 5), - TealOp(expr, Op.app_local_put), + pt.TealOp(args[0], pt.Op.txn, "Sender"), + pt.TealOp(args[1], pt.Op.byte, '"key"'), + pt.TealOp(args[2], pt.Op.int, 5), + pt.TealOp(expr, pt.Op.app_local_put), ] ) actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected def test_local_put_invalid(): - with pytest.raises(TealTypeError): - App.localPut(Txn.sender(), Int(55), Int(5)) + with pytest.raises(pt.TealTypeError): + pt.App.localPut(pt.Txn.sender(), pt.Int(55), pt.Int(5)) - with pytest.raises(TealTypeError): - App.localPut(Int(1), Int(0), Int(5)) + with pytest.raises(pt.TealTypeError): + pt.App.localPut(pt.Int(1), pt.Int(0), pt.Int(5)) - with pytest.raises(TealTypeError): - App.localPut(Int(1), Bytes("key"), Pop(Int(1))) + with pytest.raises(pt.TealTypeError): + pt.App.localPut(pt.Int(1), pt.Bytes("key"), pt.Pop(pt.Int(1))) def test_global_put(): - args = [Bytes("key"), Int(5)] - expr = App.globalPut(args[0], args[1]) - assert expr.type_of() == TealType.none + args = [pt.Bytes("key"), pt.Int(5)] + expr = pt.App.globalPut(args[0], args[1]) + assert expr.type_of() == pt.TealType.none - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(args[0], Op.byte, '"key"'), - TealOp(args[1], Op.int, 5), - TealOp(expr, Op.app_global_put), + pt.TealOp(args[0], pt.Op.byte, '"key"'), + pt.TealOp(args[1], pt.Op.int, 5), + pt.TealOp(expr, pt.Op.app_global_put), ] ) actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected def test_global_put_invalid(): - with pytest.raises(TealTypeError): - App.globalPut(Int(0), Int(5)) + with pytest.raises(pt.TealTypeError): + pt.App.globalPut(pt.Int(0), pt.Int(5)) - with pytest.raises(TealTypeError): - App.globalPut(Bytes("key"), Pop(Int(1))) + with pytest.raises(pt.TealTypeError): + pt.App.globalPut(pt.Bytes("key"), pt.Pop(pt.Int(1))) def test_local_del(): - args = [Int(0), Bytes("key")] - expr = App.localDel(args[0], args[1]) - assert expr.type_of() == TealType.none + args = [pt.Int(0), pt.Bytes("key")] + expr = pt.App.localDel(args[0], args[1]) + assert expr.type_of() == pt.TealType.none - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(args[0], Op.int, 0), - TealOp(args[1], Op.byte, '"key"'), - TealOp(expr, Op.app_local_del), + pt.TealOp(args[0], pt.Op.int, 0), + pt.TealOp(args[1], pt.Op.byte, '"key"'), + pt.TealOp(expr, pt.Op.app_local_del), ] ) actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected def test_local_del_direct_ref(): - args = [Txn.sender(), Bytes("key")] - expr = App.localDel(args[0], args[1]) - assert expr.type_of() == TealType.none + args = [pt.Txn.sender(), pt.Bytes("key")] + expr = pt.App.localDel(args[0], args[1]) + assert expr.type_of() == pt.TealType.none - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(args[0], Op.txn, "Sender"), - TealOp(args[1], Op.byte, '"key"'), - TealOp(expr, Op.app_local_del), + pt.TealOp(args[0], pt.Op.txn, "Sender"), + pt.TealOp(args[1], pt.Op.byte, '"key"'), + pt.TealOp(expr, pt.Op.app_local_del), ] ) actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected def test_local_del_invalid(): - with pytest.raises(TealTypeError): - App.localDel(Txn.sender(), Int(123)) + with pytest.raises(pt.TealTypeError): + pt.App.localDel(pt.Txn.sender(), pt.Int(123)) - with pytest.raises(TealTypeError): - App.localDel(Int(1), Int(2)) + with pytest.raises(pt.TealTypeError): + pt.App.localDel(pt.Int(1), pt.Int(2)) def test_global_del(): - arg = Bytes("key") - expr = App.globalDel(arg) - assert expr.type_of() == TealType.none + arg = pt.Bytes("key") + expr = pt.App.globalDel(arg) + assert expr.type_of() == pt.TealType.none - expected = TealSimpleBlock( - [TealOp(arg, Op.byte, '"key"'), TealOp(expr, Op.app_global_del)] + expected = pt.TealSimpleBlock( + [pt.TealOp(arg, pt.Op.byte, '"key"'), pt.TealOp(expr, pt.Op.app_global_del)] ) actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected def test_global_del_invalid(): - with pytest.raises(TealTypeError): - App.globalDel(Int(2)) + with pytest.raises(pt.TealTypeError): + pt.App.globalDel(pt.Int(2)) def test_app_param_approval_program_valid(): - arg = Int(1) - expr = AppParam.approvalProgram(arg) - assert expr.type_of() == TealType.none - assert expr.value().type_of() == TealType.bytes + arg = pt.Int(1) + expr = pt.AppParam.approvalProgram(arg) + assert expr.type_of() == pt.TealType.none + assert expr.value().type_of() == pt.TealType.bytes - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(arg, Op.int, 1), - TealOp(expr, Op.app_params_get, "AppApprovalProgram"), - TealOp(None, Op.store, expr.slotOk), - TealOp(None, Op.store, expr.slotValue), + pt.TealOp(arg, pt.Op.int, 1), + pt.TealOp(expr, pt.Op.app_params_get, "AppApprovalProgram"), + pt.TealOp(None, pt.Op.store, expr.slotOk), + pt.TealOp(None, pt.Op.store, expr.slotValue), ] ) actual, _ = expr.__teal__(teal5Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected def test_app_param_approval_program_invalid(): - with pytest.raises(TealTypeError): - AppParam.approvalProgram(Txn.sender()) + with pytest.raises(pt.TealTypeError): + pt.AppParam.approvalProgram(pt.Txn.sender()) def test_app_param_clear_state_program_valid(): - arg = Int(0) - expr = AppParam.clearStateProgram(arg) - assert expr.type_of() == TealType.none - assert expr.value().type_of() == TealType.bytes + arg = pt.Int(0) + expr = pt.AppParam.clearStateProgram(arg) + assert expr.type_of() == pt.TealType.none + assert expr.value().type_of() == pt.TealType.bytes - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(arg, Op.int, 0), - TealOp(expr, Op.app_params_get, "AppClearStateProgram"), - TealOp(None, Op.store, expr.slotOk), - TealOp(None, Op.store, expr.slotValue), + pt.TealOp(arg, pt.Op.int, 0), + pt.TealOp(expr, pt.Op.app_params_get, "AppClearStateProgram"), + pt.TealOp(None, pt.Op.store, expr.slotOk), + pt.TealOp(None, pt.Op.store, expr.slotValue), ] ) actual, _ = expr.__teal__(teal5Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected def test_app_param_clear_state_program_invalid(): - with pytest.raises(TealTypeError): - AppParam.clearStateProgram(Txn.sender()) + with pytest.raises(pt.TealTypeError): + pt.AppParam.clearStateProgram(pt.Txn.sender()) def test_app_param_global_num_unit_valid(): - arg = Int(1) - expr = AppParam.globalNumUnit(arg) - assert expr.type_of() == TealType.none - assert expr.value().type_of() == TealType.uint64 + arg = pt.Int(1) + expr = pt.AppParam.globalNumUnit(arg) + assert expr.type_of() == pt.TealType.none + assert expr.value().type_of() == pt.TealType.uint64 - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(arg, Op.int, 1), - TealOp(expr, Op.app_params_get, "AppGlobalNumUnit"), - TealOp(None, Op.store, expr.slotOk), - TealOp(None, Op.store, expr.slotValue), + pt.TealOp(arg, pt.Op.int, 1), + pt.TealOp(expr, pt.Op.app_params_get, "AppGlobalNumUnit"), + pt.TealOp(None, pt.Op.store, expr.slotOk), + pt.TealOp(None, pt.Op.store, expr.slotValue), ] ) actual, _ = expr.__teal__(teal5Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected def test_app_param_global_num_unit_invalid(): - with pytest.raises(TealTypeError): - AppParam.globalNumUnit(Txn.sender()) + with pytest.raises(pt.TealTypeError): + pt.AppParam.globalNumUnit(pt.Txn.sender()) def test_app_param_global_num_byte_slice_valid(): - arg = Int(1) - expr = AppParam.globalNumByteSlice(arg) - assert expr.type_of() == TealType.none - assert expr.value().type_of() == TealType.uint64 + arg = pt.Int(1) + expr = pt.AppParam.globalNumByteSlice(arg) + assert expr.type_of() == pt.TealType.none + assert expr.value().type_of() == pt.TealType.uint64 - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(arg, Op.int, 1), - TealOp(expr, Op.app_params_get, "AppGlobalNumByteSlice"), - TealOp(None, Op.store, expr.slotOk), - TealOp(None, Op.store, expr.slotValue), + pt.TealOp(arg, pt.Op.int, 1), + pt.TealOp(expr, pt.Op.app_params_get, "AppGlobalNumByteSlice"), + pt.TealOp(None, pt.Op.store, expr.slotOk), + pt.TealOp(None, pt.Op.store, expr.slotValue), ] ) actual, _ = expr.__teal__(teal5Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected def test_app_param_global_num_byte_slice_invalid(): - with pytest.raises(TealTypeError): - AppParam.globalNumByteSlice(Txn.sender()) + with pytest.raises(pt.TealTypeError): + pt.AppParam.globalNumByteSlice(pt.Txn.sender()) def test_app_param_local_num_unit_valid(): - arg = Int(1) - expr = AppParam.localNumUnit(arg) - assert expr.type_of() == TealType.none - assert expr.value().type_of() == TealType.uint64 + arg = pt.Int(1) + expr = pt.AppParam.localNumUnit(arg) + assert expr.type_of() == pt.TealType.none + assert expr.value().type_of() == pt.TealType.uint64 - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(arg, Op.int, 1), - TealOp(expr, Op.app_params_get, "AppLocalNumUnit"), - TealOp(None, Op.store, expr.slotOk), - TealOp(None, Op.store, expr.slotValue), + pt.TealOp(arg, pt.Op.int, 1), + pt.TealOp(expr, pt.Op.app_params_get, "AppLocalNumUnit"), + pt.TealOp(None, pt.Op.store, expr.slotOk), + pt.TealOp(None, pt.Op.store, expr.slotValue), ] ) actual, _ = expr.__teal__(teal5Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected def test_app_param_local_num_unit_invalid(): - with pytest.raises(TealTypeError): - AppParam.localNumUnit(Txn.sender()) + with pytest.raises(pt.TealTypeError): + pt.AppParam.localNumUnit(pt.Txn.sender()) def test_app_param_local_num_byte_slice_valid(): - arg = Int(1) - expr = AppParam.localNumByteSlice(arg) - assert expr.type_of() == TealType.none - assert expr.value().type_of() == TealType.uint64 + arg = pt.Int(1) + expr = pt.AppParam.localNumByteSlice(arg) + assert expr.type_of() == pt.TealType.none + assert expr.value().type_of() == pt.TealType.uint64 - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(arg, Op.int, 1), - TealOp(expr, Op.app_params_get, "AppLocalNumByteSlice"), - TealOp(None, Op.store, expr.slotOk), - TealOp(None, Op.store, expr.slotValue), + pt.TealOp(arg, pt.Op.int, 1), + pt.TealOp(expr, pt.Op.app_params_get, "AppLocalNumByteSlice"), + pt.TealOp(None, pt.Op.store, expr.slotOk), + pt.TealOp(None, pt.Op.store, expr.slotValue), ] ) actual, _ = expr.__teal__(teal5Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected def test_app_param_local_num_byte_slice_invalid(): - with pytest.raises(TealTypeError): - AppParam.localNumByteSlice(Txn.sender()) + with pytest.raises(pt.TealTypeError): + pt.AppParam.localNumByteSlice(pt.Txn.sender()) def test_app_param_extra_programs_page_valid(): - arg = Int(1) - expr = AppParam.extraProgramPages(arg) - assert expr.type_of() == TealType.none - assert expr.value().type_of() == TealType.uint64 + arg = pt.Int(1) + expr = pt.AppParam.extraProgramPages(arg) + assert expr.type_of() == pt.TealType.none + assert expr.value().type_of() == pt.TealType.uint64 - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(arg, Op.int, 1), - TealOp(expr, Op.app_params_get, "AppExtraProgramPages"), - TealOp(None, Op.store, expr.slotOk), - TealOp(None, Op.store, expr.slotValue), + pt.TealOp(arg, pt.Op.int, 1), + pt.TealOp(expr, pt.Op.app_params_get, "AppExtraProgramPages"), + pt.TealOp(None, pt.Op.store, expr.slotOk), + pt.TealOp(None, pt.Op.store, expr.slotValue), ] ) actual, _ = expr.__teal__(teal5Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected def test_app_param_extra_program_pages_invalid(): - with pytest.raises(TealTypeError): - AppParam.extraProgramPages(Txn.sender()) + with pytest.raises(pt.TealTypeError): + pt.AppParam.extraProgramPages(pt.Txn.sender()) def test_app_param_creator_valid(): - arg = Int(1) - expr = AppParam.creator(arg) - assert expr.type_of() == TealType.none - assert expr.value().type_of() == TealType.bytes + arg = pt.Int(1) + expr = pt.AppParam.creator(arg) + assert expr.type_of() == pt.TealType.none + assert expr.value().type_of() == pt.TealType.bytes - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(arg, Op.int, 1), - TealOp(expr, Op.app_params_get, "AppCreator"), - TealOp(None, Op.store, expr.slotOk), - TealOp(None, Op.store, expr.slotValue), + pt.TealOp(arg, pt.Op.int, 1), + pt.TealOp(expr, pt.Op.app_params_get, "AppCreator"), + pt.TealOp(None, pt.Op.store, expr.slotOk), + pt.TealOp(None, pt.Op.store, expr.slotValue), ] ) actual, _ = expr.__teal__(teal5Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected def test_app_param_creator_invalid(): - with pytest.raises(TealTypeError): - AppParam.creator(Txn.sender()) + with pytest.raises(pt.TealTypeError): + pt.AppParam.creator(pt.Txn.sender()) def test_app_param_address_valid(): - arg = Int(1) - expr = AppParam.address(arg) - assert expr.type_of() == TealType.none - assert expr.value().type_of() == TealType.bytes + arg = pt.Int(1) + expr = pt.AppParam.address(arg) + assert expr.type_of() == pt.TealType.none + assert expr.value().type_of() == pt.TealType.bytes - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(arg, Op.int, 1), - TealOp(expr, Op.app_params_get, "AppAddress"), - TealOp(None, Op.store, expr.slotOk), - TealOp(None, Op.store, expr.slotValue), + pt.TealOp(arg, pt.Op.int, 1), + pt.TealOp(expr, pt.Op.app_params_get, "AppAddress"), + pt.TealOp(None, pt.Op.store, expr.slotOk), + pt.TealOp(None, pt.Op.store, expr.slotValue), ] ) actual, _ = expr.__teal__(teal5Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected def test_app_param_address_invalid(): - with pytest.raises(TealTypeError): - AppParam.address(Txn.sender()) + with pytest.raises(pt.TealTypeError): + pt.AppParam.address(pt.Txn.sender()) diff --git a/pyteal/ast/arg.py b/pyteal/ast/arg.py index be0d3dd05..4c5697eda 100644 --- a/pyteal/ast/arg.py +++ b/pyteal/ast/arg.py @@ -1,13 +1,13 @@ from typing import Union, cast, TYPE_CHECKING -from ..types import TealType, require_type -from ..ir import TealOp, Op, TealBlock -from ..errors import TealInputError, verifyTealVersion -from .expr import Expr -from .leafexpr import LeafExpr +from pyteal.types import TealType, require_type +from pyteal.ir import TealOp, Op, TealBlock +from pyteal.errors import TealInputError, verifyTealVersion +from pyteal.ast.expr import Expr +from pyteal.ast.leafexpr import LeafExpr if TYPE_CHECKING: - from ..compiler import CompileOptions + from pyteal.compiler import CompileOptions class Arg(LeafExpr): diff --git a/pyteal/ast/arg_test.py b/pyteal/ast/arg_test.py index 7d622ddb7..90cfc8929 100644 --- a/pyteal/ast/arg_test.py +++ b/pyteal/ast/arg_test.py @@ -1,51 +1,50 @@ import pytest -from .. import * +import pyteal as pt -# this is not necessary but mypy complains if it's not included -from .. import CompileOptions - -teal2Options = CompileOptions(version=2) -teal4Options = CompileOptions(version=4) -teal5Options = CompileOptions(version=5) +teal2Options = pt.CompileOptions(version=2) +teal4Options = pt.CompileOptions(version=4) +teal5Options = pt.CompileOptions(version=5) def test_arg_static(): for i in range(256): - expr = Arg(i) - assert expr.type_of() == TealType.bytes + expr = pt.Arg(i) + assert expr.type_of() == pt.TealType.bytes assert not expr.has_return() - expected = TealSimpleBlock([TealOp(expr, Op.arg, i)]) + expected = pt.TealSimpleBlock([pt.TealOp(expr, pt.Op.arg, i)]) actual, _ = expr.__teal__(teal2Options) assert actual == expected def test_arg_dynamic(): - i = Int(7) - expr = Arg(i) - assert expr.type_of() == TealType.bytes + i = pt.Int(7) + expr = pt.Arg(i) + assert expr.type_of() == pt.TealType.bytes assert not expr.has_return() - expected = TealSimpleBlock([TealOp(i, Op.int, 7), TealOp(expr, Op.args)]) + expected = pt.TealSimpleBlock( + [pt.TealOp(i, pt.Op.int, 7), pt.TealOp(expr, pt.Op.args)] + ) actual, _ = expr.__teal__(teal5Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): expr.__teal__(teal4Options) def test_arg_invalid(): - with pytest.raises(TealTypeError): - Arg(Bytes("k")) + with pytest.raises(pt.TealTypeError): + pt.Arg(pt.Bytes("k")) - with pytest.raises(TealInputError): - Arg(-1) + with pytest.raises(pt.TealInputError): + pt.Arg(-1) - with pytest.raises(TealInputError): - Arg(256) + with pytest.raises(pt.TealInputError): + pt.Arg(256) diff --git a/pyteal/ast/array.py b/pyteal/ast/array.py index 5a1fb9bdc..3a9c4c61a 100644 --- a/pyteal/ast/array.py +++ b/pyteal/ast/array.py @@ -1,6 +1,6 @@ from abc import ABC, abstractmethod -from .expr import Expr +from pyteal.ast.expr import Expr class Array(ABC): diff --git a/pyteal/ast/assert_.py b/pyteal/ast/assert_.py index 0d9b65b17..6b448825b 100644 --- a/pyteal/ast/assert_.py +++ b/pyteal/ast/assert_.py @@ -1,11 +1,11 @@ from typing import TYPE_CHECKING -from ..types import TealType, require_type -from ..ir import TealOp, Op, TealBlock, TealSimpleBlock, TealConditionalBlock -from .expr import Expr +from pyteal.types import TealType, require_type +from pyteal.ir import TealOp, Op, TealBlock, TealSimpleBlock, TealConditionalBlock +from pyteal.ast.expr import Expr if TYPE_CHECKING: - from ..compiler import CompileOptions + from pyteal.compiler import CompileOptions class Assert(Expr): diff --git a/pyteal/ast/assert_test.py b/pyteal/ast/assert_test.py index dbce8bbd9..fd6b756ab 100644 --- a/pyteal/ast/assert_test.py +++ b/pyteal/ast/assert_test.py @@ -1,45 +1,44 @@ import pytest -from .. import * +import pyteal as pt -# this is not necessary but mypy complains if it's not included -from .. import CompileOptions - -teal2Options = CompileOptions(version=2) -teal3Options = CompileOptions(version=3) +teal2Options = pt.CompileOptions(version=2) +teal3Options = pt.CompileOptions(version=3) def test_teal_2_assert(): - arg = Int(1) - expr = Assert(arg) - assert expr.type_of() == TealType.none + arg = pt.Int(1) + expr = pt.Assert(arg) + assert expr.type_of() == pt.TealType.none expected, _ = arg.__teal__(teal2Options) - expectedBranch = TealConditionalBlock([]) - expectedBranch.setTrueBlock(TealSimpleBlock([])) - expectedBranch.setFalseBlock(Err().__teal__(teal2Options)[0]) + expectedBranch = pt.TealConditionalBlock([]) + expectedBranch.setTrueBlock(pt.TealSimpleBlock([])) + expectedBranch.setFalseBlock(pt.Err().__teal__(teal2Options)[0]) expected.setNextBlock(expectedBranch) actual, _ = expr.__teal__(teal2Options) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected def test_teal_3_assert(): - arg = Int(1) - expr = Assert(arg) - assert expr.type_of() == TealType.none + arg = pt.Int(1) + expr = pt.Assert(arg) + assert expr.type_of() == pt.TealType.none - expected = TealSimpleBlock([TealOp(arg, Op.int, 1), TealOp(expr, Op.assert_)]) + expected = pt.TealSimpleBlock( + [pt.TealOp(arg, pt.Op.int, 1), pt.TealOp(expr, pt.Op.assert_)] + ) actual, _ = expr.__teal__(teal3Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected def test_assert_invalid(): - with pytest.raises(TealTypeError): - Assert(Txn.receiver()) + with pytest.raises(pt.TealTypeError): + pt.Assert(pt.Txn.receiver()) diff --git a/pyteal/ast/asset.py b/pyteal/ast/asset.py index 1d416e787..a39879be2 100644 --- a/pyteal/ast/asset.py +++ b/pyteal/ast/asset.py @@ -1,10 +1,7 @@ -from enum import Enum - -from ..types import TealType, require_type -from ..ir import Op -from .expr import Expr -from .leafexpr import LeafExpr -from .maybe import MaybeValue +from pyteal.types import TealType, require_type +from pyteal.ir import Op +from pyteal.ast.expr import Expr +from pyteal.ast.maybe import MaybeValue class AssetHolding: diff --git a/pyteal/ast/asset_test.py b/pyteal/ast/asset_test.py index c51b8833a..6ac9cf0e1 100644 --- a/pyteal/ast/asset_test.py +++ b/pyteal/ast/asset_test.py @@ -1,711 +1,708 @@ import pytest -from .. import * +import pyteal as pt -# this is not necessary but mypy complains if it's not included -from .. import CompileOptions - -teal2Options = CompileOptions() -teal4Options = CompileOptions(version=4) -teal5Options = CompileOptions(version=5) +teal2Options = pt.CompileOptions() +teal4Options = pt.CompileOptions(version=4) +teal5Options = pt.CompileOptions(version=5) def test_asset_holding_balance(): - args = Int(0), Int(17) - expr = AssetHolding.balance(args[0], args[1]) - assert expr.type_of() == TealType.none - assert expr.value().type_of() == TealType.uint64 + args = pt.Int(0), pt.Int(17) + expr = pt.AssetHolding.balance(args[0], args[1]) + assert expr.type_of() == pt.TealType.none + assert expr.value().type_of() == pt.TealType.uint64 - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(args[0], Op.int, 0), - TealOp(args[1], Op.int, 17), - TealOp(expr, Op.asset_holding_get, "AssetBalance"), - TealOp(None, Op.store, expr.slotOk), - TealOp(None, Op.store, expr.slotValue), + pt.TealOp(args[0], pt.Op.int, 0), + pt.TealOp(args[1], pt.Op.int, 17), + pt.TealOp(expr, pt.Op.asset_holding_get, "AssetBalance"), + pt.TealOp(None, pt.Op.store, expr.slotOk), + pt.TealOp(None, pt.Op.store, expr.slotValue), ] ) actual, _ = expr.__teal__(teal2Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected def test_asset_holding_balance_direct_ref(): - args = [Txn.sender(), Txn.assets[17]] - expr = AssetHolding.balance(args[0], args[1]) - assert expr.type_of() == TealType.none - assert expr.value().type_of() == TealType.uint64 + args = [pt.Txn.sender(), pt.Txn.assets[17]] + expr = pt.AssetHolding.balance(args[0], args[1]) + assert expr.type_of() == pt.TealType.none + assert expr.value().type_of() == pt.TealType.uint64 - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(args[0], Op.txn, "Sender"), - TealOp(args[1], Op.txna, "Assets", 17), - TealOp(expr, Op.asset_holding_get, "AssetBalance"), - TealOp(None, Op.store, expr.slotOk), - TealOp(None, Op.store, expr.slotValue), + pt.TealOp(args[0], pt.Op.txn, "Sender"), + pt.TealOp(args[1], pt.Op.txna, "Assets", 17), + pt.TealOp(expr, pt.Op.asset_holding_get, "AssetBalance"), + pt.TealOp(None, pt.Op.store, expr.slotOk), + pt.TealOp(None, pt.Op.store, expr.slotValue), ] ) actual, _ = expr.__teal__(teal4Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected def test_asset_holding_balance_invalid(): - with pytest.raises(TealTypeError): - AssetHolding.balance(Txn.sender(), Bytes("100")) + with pytest.raises(pt.TealTypeError): + pt.AssetHolding.balance(pt.Txn.sender(), pt.Bytes("100")) - with pytest.raises(TealTypeError): - AssetHolding.balance(Int(0), Txn.receiver()) + with pytest.raises(pt.TealTypeError): + pt.AssetHolding.balance(pt.Int(0), pt.Txn.receiver()) def test_asset_holding_frozen(): - args = [Int(0), Int(17)] - expr = AssetHolding.frozen(args[0], args[1]) - assert expr.type_of() == TealType.none - assert expr.value().type_of() == TealType.uint64 + args = [pt.Int(0), pt.Int(17)] + expr = pt.AssetHolding.frozen(args[0], args[1]) + assert expr.type_of() == pt.TealType.none + assert expr.value().type_of() == pt.TealType.uint64 - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(args[0], Op.int, 0), - TealOp(args[1], Op.int, 17), - TealOp(expr, Op.asset_holding_get, "AssetFrozen"), - TealOp(None, Op.store, expr.slotOk), - TealOp(None, Op.store, expr.slotValue), + pt.TealOp(args[0], pt.Op.int, 0), + pt.TealOp(args[1], pt.Op.int, 17), + pt.TealOp(expr, pt.Op.asset_holding_get, "AssetFrozen"), + pt.TealOp(None, pt.Op.store, expr.slotOk), + pt.TealOp(None, pt.Op.store, expr.slotValue), ] ) actual, _ = expr.__teal__(teal2Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected def test_asset_holding_frozen_direct_ref(): - args = [Txn.sender(), Txn.assets[17]] - expr = AssetHolding.frozen(args[0], args[1]) - assert expr.type_of() == TealType.none - assert expr.value().type_of() == TealType.uint64 + args = [pt.Txn.sender(), pt.Txn.assets[17]] + expr = pt.AssetHolding.frozen(args[0], args[1]) + assert expr.type_of() == pt.TealType.none + assert expr.value().type_of() == pt.TealType.uint64 - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(args[0], Op.txn, "Sender"), - TealOp(args[1], Op.txna, "Assets", 17), - TealOp(expr, Op.asset_holding_get, "AssetFrozen"), - TealOp(None, Op.store, expr.slotOk), - TealOp(None, Op.store, expr.slotValue), + pt.TealOp(args[0], pt.Op.txn, "Sender"), + pt.TealOp(args[1], pt.Op.txna, "Assets", 17), + pt.TealOp(expr, pt.Op.asset_holding_get, "AssetFrozen"), + pt.TealOp(None, pt.Op.store, expr.slotOk), + pt.TealOp(None, pt.Op.store, expr.slotValue), ] ) actual, _ = expr.__teal__(teal4Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected def test_asset_holding_frozen_invalid(): - with pytest.raises(TealTypeError): - AssetHolding.frozen(Txn.sender(), Bytes("17")) + with pytest.raises(pt.TealTypeError): + pt.AssetHolding.frozen(pt.Txn.sender(), pt.Bytes("17")) - with pytest.raises(TealTypeError): - AssetHolding.frozen(Int(0), Txn.receiver()) + with pytest.raises(pt.TealTypeError): + pt.AssetHolding.frozen(pt.Int(0), pt.Txn.receiver()) def test_asset_param_total(): - arg = Int(0) - expr = AssetParam.total(arg) - assert expr.type_of() == TealType.none - assert expr.value().type_of() == TealType.uint64 + arg = pt.Int(0) + expr = pt.AssetParam.total(arg) + assert expr.type_of() == pt.TealType.none + assert expr.value().type_of() == pt.TealType.uint64 - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(arg, Op.int, 0), - TealOp(expr, Op.asset_params_get, "AssetTotal"), - TealOp(None, Op.store, expr.slotOk), - TealOp(None, Op.store, expr.slotValue), + pt.TealOp(arg, pt.Op.int, 0), + pt.TealOp(expr, pt.Op.asset_params_get, "AssetTotal"), + pt.TealOp(None, pt.Op.store, expr.slotOk), + pt.TealOp(None, pt.Op.store, expr.slotValue), ] ) actual, _ = expr.__teal__(teal2Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected def test_asset_param_total_direct_ref(): - arg = Txn.assets[0] - expr = AssetParam.total(arg) - assert expr.type_of() == TealType.none - assert expr.value().type_of() == TealType.uint64 + arg = pt.Txn.assets[0] + expr = pt.AssetParam.total(arg) + assert expr.type_of() == pt.TealType.none + assert expr.value().type_of() == pt.TealType.uint64 - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(arg, Op.txna, "Assets", 0), - TealOp(expr, Op.asset_params_get, "AssetTotal"), - TealOp(None, Op.store, expr.slotOk), - TealOp(None, Op.store, expr.slotValue), + pt.TealOp(arg, pt.Op.txna, "Assets", 0), + pt.TealOp(expr, pt.Op.asset_params_get, "AssetTotal"), + pt.TealOp(None, pt.Op.store, expr.slotOk), + pt.TealOp(None, pt.Op.store, expr.slotValue), ] ) actual, _ = expr.__teal__(teal4Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected def test_asset_param_total_invalid(): - with pytest.raises(TealTypeError): - AssetParam.total(Txn.sender()) + with pytest.raises(pt.TealTypeError): + pt.AssetParam.total(pt.Txn.sender()) def test_asset_param_decimals(): - arg = Int(0) - expr = AssetParam.decimals(arg) - assert expr.type_of() == TealType.none - assert expr.value().type_of() == TealType.uint64 + arg = pt.Int(0) + expr = pt.AssetParam.decimals(arg) + assert expr.type_of() == pt.TealType.none + assert expr.value().type_of() == pt.TealType.uint64 - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(arg, Op.int, 0), - TealOp(expr, Op.asset_params_get, "AssetDecimals"), - TealOp(None, Op.store, expr.slotOk), - TealOp(None, Op.store, expr.slotValue), + pt.TealOp(arg, pt.Op.int, 0), + pt.TealOp(expr, pt.Op.asset_params_get, "AssetDecimals"), + pt.TealOp(None, pt.Op.store, expr.slotOk), + pt.TealOp(None, pt.Op.store, expr.slotValue), ] ) actual, _ = expr.__teal__(teal2Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected def test_asset_param_decimals_direct_ref(): - arg = Txn.assets[0] - expr = AssetParam.decimals(arg) - assert expr.type_of() == TealType.none - assert expr.value().type_of() == TealType.uint64 + arg = pt.Txn.assets[0] + expr = pt.AssetParam.decimals(arg) + assert expr.type_of() == pt.TealType.none + assert expr.value().type_of() == pt.TealType.uint64 - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(arg, Op.txna, "Assets", 0), - TealOp(expr, Op.asset_params_get, "AssetDecimals"), - TealOp(None, Op.store, expr.slotOk), - TealOp(None, Op.store, expr.slotValue), + pt.TealOp(arg, pt.Op.txna, "Assets", 0), + pt.TealOp(expr, pt.Op.asset_params_get, "AssetDecimals"), + pt.TealOp(None, pt.Op.store, expr.slotOk), + pt.TealOp(None, pt.Op.store, expr.slotValue), ] ) actual, _ = expr.__teal__(teal4Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected def test_asset_param_decimals_invalid(): - with pytest.raises(TealTypeError): - AssetParam.decimals(Txn.sender()) + with pytest.raises(pt.TealTypeError): + pt.AssetParam.decimals(pt.Txn.sender()) def test_asset_param_default_frozen(): - arg = Int(0) - expr = AssetParam.defaultFrozen(arg) - assert expr.type_of() == TealType.none - assert expr.value().type_of() == TealType.uint64 + arg = pt.Int(0) + expr = pt.AssetParam.defaultFrozen(arg) + assert expr.type_of() == pt.TealType.none + assert expr.value().type_of() == pt.TealType.uint64 - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(arg, Op.int, 0), - TealOp(expr, Op.asset_params_get, "AssetDefaultFrozen"), - TealOp(None, Op.store, expr.slotOk), - TealOp(None, Op.store, expr.slotValue), + pt.TealOp(arg, pt.Op.int, 0), + pt.TealOp(expr, pt.Op.asset_params_get, "AssetDefaultFrozen"), + pt.TealOp(None, pt.Op.store, expr.slotOk), + pt.TealOp(None, pt.Op.store, expr.slotValue), ] ) actual, _ = expr.__teal__(teal2Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected def test_asset_param_default_frozen_direct_ref(): - arg = Txn.assets[0] - expr = AssetParam.defaultFrozen(arg) - assert expr.type_of() == TealType.none - assert expr.value().type_of() == TealType.uint64 + arg = pt.Txn.assets[0] + expr = pt.AssetParam.defaultFrozen(arg) + assert expr.type_of() == pt.TealType.none + assert expr.value().type_of() == pt.TealType.uint64 - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(arg, Op.txna, "Assets", 0), - TealOp(expr, Op.asset_params_get, "AssetDefaultFrozen"), - TealOp(None, Op.store, expr.slotOk), - TealOp(None, Op.store, expr.slotValue), + pt.TealOp(arg, pt.Op.txna, "Assets", 0), + pt.TealOp(expr, pt.Op.asset_params_get, "AssetDefaultFrozen"), + pt.TealOp(None, pt.Op.store, expr.slotOk), + pt.TealOp(None, pt.Op.store, expr.slotValue), ] ) actual, _ = expr.__teal__(teal4Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected def test_asset_param_default_frozen_invalid(): - with pytest.raises(TealTypeError): - AssetParam.defaultFrozen(Txn.sender()) + with pytest.raises(pt.TealTypeError): + pt.AssetParam.defaultFrozen(pt.Txn.sender()) def test_asset_param_unit_name(): - arg = Int(0) - expr = AssetParam.unitName(arg) - assert expr.type_of() == TealType.none - assert expr.value().type_of() == TealType.bytes + arg = pt.Int(0) + expr = pt.AssetParam.unitName(arg) + assert expr.type_of() == pt.TealType.none + assert expr.value().type_of() == pt.TealType.bytes - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(arg, Op.int, 0), - TealOp(expr, Op.asset_params_get, "AssetUnitName"), - TealOp(None, Op.store, expr.slotOk), - TealOp(None, Op.store, expr.slotValue), + pt.TealOp(arg, pt.Op.int, 0), + pt.TealOp(expr, pt.Op.asset_params_get, "AssetUnitName"), + pt.TealOp(None, pt.Op.store, expr.slotOk), + pt.TealOp(None, pt.Op.store, expr.slotValue), ] ) actual, _ = expr.__teal__(teal2Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected def test_asset_param_unit_name_direct_ref(): - arg = Txn.assets[0] - expr = AssetParam.unitName(arg) - assert expr.type_of() == TealType.none - assert expr.value().type_of() == TealType.bytes + arg = pt.Txn.assets[0] + expr = pt.AssetParam.unitName(arg) + assert expr.type_of() == pt.TealType.none + assert expr.value().type_of() == pt.TealType.bytes - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(arg, Op.txna, "Assets", 0), - TealOp(expr, Op.asset_params_get, "AssetUnitName"), - TealOp(None, Op.store, expr.slotOk), - TealOp(None, Op.store, expr.slotValue), + pt.TealOp(arg, pt.Op.txna, "Assets", 0), + pt.TealOp(expr, pt.Op.asset_params_get, "AssetUnitName"), + pt.TealOp(None, pt.Op.store, expr.slotOk), + pt.TealOp(None, pt.Op.store, expr.slotValue), ] ) actual, _ = expr.__teal__(teal4Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected def test_asset_param_unit_name_invalid(): - with pytest.raises(TealTypeError): - AssetParam.unitName(Txn.sender()) + with pytest.raises(pt.TealTypeError): + pt.AssetParam.unitName(pt.Txn.sender()) def test_asset_param_name(): - arg = Int(0) - expr = AssetParam.name(arg) - assert expr.type_of() == TealType.none - assert expr.value().type_of() == TealType.bytes + arg = pt.Int(0) + expr = pt.AssetParam.name(arg) + assert expr.type_of() == pt.TealType.none + assert expr.value().type_of() == pt.TealType.bytes - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(arg, Op.int, 0), - TealOp(expr, Op.asset_params_get, "AssetName"), - TealOp(None, Op.store, expr.slotOk), - TealOp(None, Op.store, expr.slotValue), + pt.TealOp(arg, pt.Op.int, 0), + pt.TealOp(expr, pt.Op.asset_params_get, "AssetName"), + pt.TealOp(None, pt.Op.store, expr.slotOk), + pt.TealOp(None, pt.Op.store, expr.slotValue), ] ) actual, _ = expr.__teal__(teal2Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected def test_asset_param_name_direct_ref(): - arg = Txn.assets[0] - expr = AssetParam.name(arg) - assert expr.type_of() == TealType.none - assert expr.value().type_of() == TealType.bytes + arg = pt.Txn.assets[0] + expr = pt.AssetParam.name(arg) + assert expr.type_of() == pt.TealType.none + assert expr.value().type_of() == pt.TealType.bytes - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(arg, Op.txna, "Assets", 0), - TealOp(expr, Op.asset_params_get, "AssetName"), - TealOp(None, Op.store, expr.slotOk), - TealOp(None, Op.store, expr.slotValue), + pt.TealOp(arg, pt.Op.txna, "Assets", 0), + pt.TealOp(expr, pt.Op.asset_params_get, "AssetName"), + pt.TealOp(None, pt.Op.store, expr.slotOk), + pt.TealOp(None, pt.Op.store, expr.slotValue), ] ) actual, _ = expr.__teal__(teal4Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected def test_asset_param_name_invalid(): - with pytest.raises(TealTypeError): - AssetParam.name(Txn.sender()) + with pytest.raises(pt.TealTypeError): + pt.AssetParam.name(pt.Txn.sender()) def test_asset_param_url(): - arg = Int(0) - expr = AssetParam.url(arg) - assert expr.type_of() == TealType.none - assert expr.value().type_of() == TealType.bytes + arg = pt.Int(0) + expr = pt.AssetParam.url(arg) + assert expr.type_of() == pt.TealType.none + assert expr.value().type_of() == pt.TealType.bytes - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(arg, Op.int, 0), - TealOp(expr, Op.asset_params_get, "AssetURL"), - TealOp(None, Op.store, expr.slotOk), - TealOp(None, Op.store, expr.slotValue), + pt.TealOp(arg, pt.Op.int, 0), + pt.TealOp(expr, pt.Op.asset_params_get, "AssetURL"), + pt.TealOp(None, pt.Op.store, expr.slotOk), + pt.TealOp(None, pt.Op.store, expr.slotValue), ] ) actual, _ = expr.__teal__(teal2Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected def test_asset_param_url_direct_ref(): - arg = Txn.assets[0] - expr = AssetParam.url(arg) - assert expr.type_of() == TealType.none - assert expr.value().type_of() == TealType.bytes + arg = pt.Txn.assets[0] + expr = pt.AssetParam.url(arg) + assert expr.type_of() == pt.TealType.none + assert expr.value().type_of() == pt.TealType.bytes - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(arg, Op.txna, "Assets", 0), - TealOp(expr, Op.asset_params_get, "AssetURL"), - TealOp(None, Op.store, expr.slotOk), - TealOp(None, Op.store, expr.slotValue), + pt.TealOp(arg, pt.Op.txna, "Assets", 0), + pt.TealOp(expr, pt.Op.asset_params_get, "AssetURL"), + pt.TealOp(None, pt.Op.store, expr.slotOk), + pt.TealOp(None, pt.Op.store, expr.slotValue), ] ) actual, _ = expr.__teal__(teal4Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected def test_asset_param_url_invalid(): - with pytest.raises(TealTypeError): - AssetParam.url(Txn.sender()) + with pytest.raises(pt.TealTypeError): + pt.AssetParam.url(pt.Txn.sender()) def test_asset_param_metadata_hash(): - arg = Int(0) - expr = AssetParam.metadataHash(arg) - assert expr.type_of() == TealType.none - assert expr.value().type_of() == TealType.bytes + arg = pt.Int(0) + expr = pt.AssetParam.metadataHash(arg) + assert expr.type_of() == pt.TealType.none + assert expr.value().type_of() == pt.TealType.bytes - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(arg, Op.int, 0), - TealOp(expr, Op.asset_params_get, "AssetMetadataHash"), - TealOp(None, Op.store, expr.slotOk), - TealOp(None, Op.store, expr.slotValue), + pt.TealOp(arg, pt.Op.int, 0), + pt.TealOp(expr, pt.Op.asset_params_get, "AssetMetadataHash"), + pt.TealOp(None, pt.Op.store, expr.slotOk), + pt.TealOp(None, pt.Op.store, expr.slotValue), ] ) actual, _ = expr.__teal__(teal2Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected def test_asset_param_metadata_hash_direct_ref(): - arg = Txn.assets[0] - expr = AssetParam.metadataHash(arg) - assert expr.type_of() == TealType.none - assert expr.value().type_of() == TealType.bytes + arg = pt.Txn.assets[0] + expr = pt.AssetParam.metadataHash(arg) + assert expr.type_of() == pt.TealType.none + assert expr.value().type_of() == pt.TealType.bytes - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(arg, Op.txna, "Assets", 0), - TealOp(expr, Op.asset_params_get, "AssetMetadataHash"), - TealOp(None, Op.store, expr.slotOk), - TealOp(None, Op.store, expr.slotValue), + pt.TealOp(arg, pt.Op.txna, "Assets", 0), + pt.TealOp(expr, pt.Op.asset_params_get, "AssetMetadataHash"), + pt.TealOp(None, pt.Op.store, expr.slotOk), + pt.TealOp(None, pt.Op.store, expr.slotValue), ] ) actual, _ = expr.__teal__(teal4Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected def test_asset_param_metadata_hash_invalid(): - with pytest.raises(TealTypeError): - AssetParam.metadataHash(Txn.sender()) + with pytest.raises(pt.TealTypeError): + pt.AssetParam.metadataHash(pt.Txn.sender()) def test_asset_param_manager(): - arg = Int(0) - expr = AssetParam.manager(arg) - assert expr.type_of() == TealType.none - assert expr.value().type_of() == TealType.bytes + arg = pt.Int(0) + expr = pt.AssetParam.manager(arg) + assert expr.type_of() == pt.TealType.none + assert expr.value().type_of() == pt.TealType.bytes - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(arg, Op.int, 0), - TealOp(expr, Op.asset_params_get, "AssetManager"), - TealOp(None, Op.store, expr.slotOk), - TealOp(None, Op.store, expr.slotValue), + pt.TealOp(arg, pt.Op.int, 0), + pt.TealOp(expr, pt.Op.asset_params_get, "AssetManager"), + pt.TealOp(None, pt.Op.store, expr.slotOk), + pt.TealOp(None, pt.Op.store, expr.slotValue), ] ) actual, _ = expr.__teal__(teal2Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected def test_asset_param_manager_direct_ref(): - arg = Txn.assets[0] - expr = AssetParam.manager(arg) - assert expr.type_of() == TealType.none - assert expr.value().type_of() == TealType.bytes + arg = pt.Txn.assets[0] + expr = pt.AssetParam.manager(arg) + assert expr.type_of() == pt.TealType.none + assert expr.value().type_of() == pt.TealType.bytes - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(arg, Op.txna, "Assets", 0), - TealOp(expr, Op.asset_params_get, "AssetManager"), - TealOp(None, Op.store, expr.slotOk), - TealOp(None, Op.store, expr.slotValue), + pt.TealOp(arg, pt.Op.txna, "Assets", 0), + pt.TealOp(expr, pt.Op.asset_params_get, "AssetManager"), + pt.TealOp(None, pt.Op.store, expr.slotOk), + pt.TealOp(None, pt.Op.store, expr.slotValue), ] ) actual, _ = expr.__teal__(teal4Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected def test_asset_param_manager_invalid(): - with pytest.raises(TealTypeError): - AssetParam.manager(Txn.sender()) + with pytest.raises(pt.TealTypeError): + pt.AssetParam.manager(pt.Txn.sender()) def test_asset_param_reserve(): - arg = Int(2) - expr = AssetParam.reserve(arg) - assert expr.type_of() == TealType.none - assert expr.value().type_of() == TealType.bytes + arg = pt.Int(2) + expr = pt.AssetParam.reserve(arg) + assert expr.type_of() == pt.TealType.none + assert expr.value().type_of() == pt.TealType.bytes - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(arg, Op.int, 2), - TealOp(expr, Op.asset_params_get, "AssetReserve"), - TealOp(None, Op.store, expr.slotOk), - TealOp(None, Op.store, expr.slotValue), + pt.TealOp(arg, pt.Op.int, 2), + pt.TealOp(expr, pt.Op.asset_params_get, "AssetReserve"), + pt.TealOp(None, pt.Op.store, expr.slotOk), + pt.TealOp(None, pt.Op.store, expr.slotValue), ] ) actual, _ = expr.__teal__(teal2Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected def test_asset_param_reserve_direct_ref(): - arg = Txn.assets[2] - expr = AssetParam.reserve(arg) - assert expr.type_of() == TealType.none - assert expr.value().type_of() == TealType.bytes + arg = pt.Txn.assets[2] + expr = pt.AssetParam.reserve(arg) + assert expr.type_of() == pt.TealType.none + assert expr.value().type_of() == pt.TealType.bytes - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(arg, Op.txna, "Assets", 2), - TealOp(expr, Op.asset_params_get, "AssetReserve"), - TealOp(None, Op.store, expr.slotOk), - TealOp(None, Op.store, expr.slotValue), + pt.TealOp(arg, pt.Op.txna, "Assets", 2), + pt.TealOp(expr, pt.Op.asset_params_get, "AssetReserve"), + pt.TealOp(None, pt.Op.store, expr.slotOk), + pt.TealOp(None, pt.Op.store, expr.slotValue), ] ) actual, _ = expr.__teal__(teal4Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected def test_asset_param_reserve_invalid(): - with pytest.raises(TealTypeError): - AssetParam.reserve(Txn.sender()) + with pytest.raises(pt.TealTypeError): + pt.AssetParam.reserve(pt.Txn.sender()) def test_asset_param_freeze(): - arg = Int(0) - expr = AssetParam.freeze(arg) - assert expr.type_of() == TealType.none - assert expr.value().type_of() == TealType.bytes + arg = pt.Int(0) + expr = pt.AssetParam.freeze(arg) + assert expr.type_of() == pt.TealType.none + assert expr.value().type_of() == pt.TealType.bytes - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(arg, Op.int, 0), - TealOp(expr, Op.asset_params_get, "AssetFreeze"), - TealOp(None, Op.store, expr.slotOk), - TealOp(None, Op.store, expr.slotValue), + pt.TealOp(arg, pt.Op.int, 0), + pt.TealOp(expr, pt.Op.asset_params_get, "AssetFreeze"), + pt.TealOp(None, pt.Op.store, expr.slotOk), + pt.TealOp(None, pt.Op.store, expr.slotValue), ] ) actual, _ = expr.__teal__(teal2Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected def test_asset_param_freeze_direct_ref(): - arg = Txn.assets[0] - expr = AssetParam.freeze(arg) - assert expr.type_of() == TealType.none - assert expr.value().type_of() == TealType.bytes + arg = pt.Txn.assets[0] + expr = pt.AssetParam.freeze(arg) + assert expr.type_of() == pt.TealType.none + assert expr.value().type_of() == pt.TealType.bytes - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(arg, Op.txna, "Assets", 0), - TealOp(expr, Op.asset_params_get, "AssetFreeze"), - TealOp(None, Op.store, expr.slotOk), - TealOp(None, Op.store, expr.slotValue), + pt.TealOp(arg, pt.Op.txna, "Assets", 0), + pt.TealOp(expr, pt.Op.asset_params_get, "AssetFreeze"), + pt.TealOp(None, pt.Op.store, expr.slotOk), + pt.TealOp(None, pt.Op.store, expr.slotValue), ] ) actual, _ = expr.__teal__(teal4Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected def test_asset_param_freeze_invalid(): - with pytest.raises(TealTypeError): - AssetParam.freeze(Txn.sender()) + with pytest.raises(pt.TealTypeError): + pt.AssetParam.freeze(pt.Txn.sender()) def test_asset_param_clawback(): - arg = Int(1) - expr = AssetParam.clawback(arg) - assert expr.type_of() == TealType.none - assert expr.value().type_of() == TealType.bytes + arg = pt.Int(1) + expr = pt.AssetParam.clawback(arg) + assert expr.type_of() == pt.TealType.none + assert expr.value().type_of() == pt.TealType.bytes - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(arg, Op.int, 1), - TealOp(expr, Op.asset_params_get, "AssetClawback"), - TealOp(None, Op.store, expr.slotOk), - TealOp(None, Op.store, expr.slotValue), + pt.TealOp(arg, pt.Op.int, 1), + pt.TealOp(expr, pt.Op.asset_params_get, "AssetClawback"), + pt.TealOp(None, pt.Op.store, expr.slotOk), + pt.TealOp(None, pt.Op.store, expr.slotValue), ] ) actual, _ = expr.__teal__(teal2Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected def test_asset_param_clawback_direct_ref(): - arg = Txn.assets[1] - expr = AssetParam.clawback(arg) - assert expr.type_of() == TealType.none - assert expr.value().type_of() == TealType.bytes + arg = pt.Txn.assets[1] + expr = pt.AssetParam.clawback(arg) + assert expr.type_of() == pt.TealType.none + assert expr.value().type_of() == pt.TealType.bytes - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(arg, Op.txna, "Assets", 1), - TealOp(expr, Op.asset_params_get, "AssetClawback"), - TealOp(None, Op.store, expr.slotOk), - TealOp(None, Op.store, expr.slotValue), + pt.TealOp(arg, pt.Op.txna, "Assets", 1), + pt.TealOp(expr, pt.Op.asset_params_get, "AssetClawback"), + pt.TealOp(None, pt.Op.store, expr.slotOk), + pt.TealOp(None, pt.Op.store, expr.slotValue), ] ) actual, _ = expr.__teal__(teal4Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected def test_asset_param_clawback_invalid(): - with pytest.raises(TealTypeError): - AssetParam.clawback(Txn.sender()) + with pytest.raises(pt.TealTypeError): + pt.AssetParam.clawback(pt.Txn.sender()) def test_asset_param_creator_valid(): - arg = Int(1) - expr = AssetParam.creator(arg) - assert expr.type_of() == TealType.none - assert expr.value().type_of() == TealType.bytes + arg = pt.Int(1) + expr = pt.AssetParam.creator(arg) + assert expr.type_of() == pt.TealType.none + assert expr.value().type_of() == pt.TealType.bytes - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(arg, Op.int, 1), - TealOp(expr, Op.asset_params_get, "AssetCreator"), - TealOp(None, Op.store, expr.slotOk), - TealOp(None, Op.store, expr.slotValue), + pt.TealOp(arg, pt.Op.int, 1), + pt.TealOp(expr, pt.Op.asset_params_get, "AssetCreator"), + pt.TealOp(None, pt.Op.store, expr.slotOk), + pt.TealOp(None, pt.Op.store, expr.slotValue), ] ) actual, _ = expr.__teal__(teal5Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected def test_asset_param_creator_invalid(): - with pytest.raises(TealTypeError): - AssetParam.creator(Txn.sender()) + with pytest.raises(pt.TealTypeError): + pt.AssetParam.creator(pt.Txn.sender()) diff --git a/pyteal/ast/binaryexpr.py b/pyteal/ast/binaryexpr.py index a2020c843..2243d901b 100644 --- a/pyteal/ast/binaryexpr.py +++ b/pyteal/ast/binaryexpr.py @@ -1,12 +1,12 @@ from typing import Union, Tuple, cast, TYPE_CHECKING -from ..types import TealType, require_type -from ..errors import verifyTealVersion -from ..ir import TealOp, Op, TealBlock -from .expr import Expr +from pyteal.types import TealType, require_type +from pyteal.errors import verifyTealVersion +from pyteal.ir import TealOp, Op, TealBlock +from pyteal.ast.expr import Expr if TYPE_CHECKING: - from ..compiler import CompileOptions + from pyteal.compiler import CompileOptions class BinaryExpr(Expr): diff --git a/pyteal/ast/binaryexpr_test.py b/pyteal/ast/binaryexpr_test.py index 9602be208..a1b493365 100644 --- a/pyteal/ast/binaryexpr_test.py +++ b/pyteal/ast/binaryexpr_test.py @@ -1,1499 +1,1560 @@ import pytest -from .. import * +import pyteal as pt -# this is not necessary but mypy complains if it's not included -from .. import CompileOptions - -teal2Options = CompileOptions(version=2) -teal3Options = CompileOptions(version=3) -teal4Options = CompileOptions(version=4) -teal5Options = CompileOptions(version=5) +teal2Options = pt.CompileOptions(version=2) +teal3Options = pt.CompileOptions(version=3) +teal4Options = pt.CompileOptions(version=4) +teal5Options = pt.CompileOptions(version=5) def test_add(): - args = [Int(2), Int(3)] - expr = Add(args[0], args[1]) - assert expr.type_of() == TealType.uint64 + args = [pt.Int(2), pt.Int(3)] + expr = pt.Add(args[0], args[1]) + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock( - [TealOp(args[0], Op.int, 2), TealOp(args[1], Op.int, 3), TealOp(expr, Op.add)] + expected = pt.TealSimpleBlock( + [ + pt.TealOp(args[0], pt.Op.int, 2), + pt.TealOp(args[1], pt.Op.int, 3), + pt.TealOp(expr, pt.Op.add), + ] ) actual, _ = expr.__teal__(teal2Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected def test_add_overload(): - args = [Int(2), Int(3), Int(4)] + args = [pt.Int(2), pt.Int(3), pt.Int(4)] expr = args[0] + args[1] + args[2] - assert expr.type_of() == TealType.uint64 + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(args[0], Op.int, 2), - TealOp(args[1], Op.int, 3), - TealOp(None, Op.add), - TealOp(args[2], Op.int, 4), - TealOp(None, Op.add), + pt.TealOp(args[0], pt.Op.int, 2), + pt.TealOp(args[1], pt.Op.int, 3), + pt.TealOp(None, pt.Op.add), + pt.TealOp(args[2], pt.Op.int, 4), + pt.TealOp(None, pt.Op.add), ] ) actual, _ = expr.__teal__(teal2Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected def test_add_invalid(): - with pytest.raises(TealTypeError): - Add(Int(2), Txn.receiver()) + with pytest.raises(pt.TealTypeError): + pt.Add(pt.Int(2), pt.Txn.receiver()) - with pytest.raises(TealTypeError): - Add(Txn.sender(), Int(2)) + with pytest.raises(pt.TealTypeError): + pt.Add(pt.Txn.sender(), pt.Int(2)) def test_minus(): - args = [Int(5), Int(6)] - expr = Minus(args[0], args[1]) - assert expr.type_of() == TealType.uint64 + args = [pt.Int(5), pt.Int(6)] + expr = pt.Minus(args[0], args[1]) + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock( - [TealOp(args[0], Op.int, 5), TealOp(args[1], Op.int, 6), TealOp(expr, Op.minus)] + expected = pt.TealSimpleBlock( + [ + pt.TealOp(args[0], pt.Op.int, 5), + pt.TealOp(args[1], pt.Op.int, 6), + pt.TealOp(expr, pt.Op.minus), + ] ) actual, _ = expr.__teal__(teal2Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected def test_minus_overload(): - args = [Int(10), Int(1), Int(2)] + args = [pt.Int(10), pt.Int(1), pt.Int(2)] expr = args[0] - args[1] - args[2] - assert expr.type_of() == TealType.uint64 + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(args[0], Op.int, 10), - TealOp(args[1], Op.int, 1), - TealOp(None, Op.minus), - TealOp(args[2], Op.int, 2), - TealOp(None, Op.minus), + pt.TealOp(args[0], pt.Op.int, 10), + pt.TealOp(args[1], pt.Op.int, 1), + pt.TealOp(None, pt.Op.minus), + pt.TealOp(args[2], pt.Op.int, 2), + pt.TealOp(None, pt.Op.minus), ] ) actual, _ = expr.__teal__(teal2Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected def test_minus_invalid(): - with pytest.raises(TealTypeError): - Minus(Int(2), Txn.receiver()) + with pytest.raises(pt.TealTypeError): + pt.Minus(pt.Int(2), pt.Txn.receiver()) - with pytest.raises(TealTypeError): - Minus(Txn.sender(), Int(2)) + with pytest.raises(pt.TealTypeError): + pt.Minus(pt.Txn.sender(), pt.Int(2)) def test_mul(): - args = [Int(3), Int(8)] - expr = Mul(args[0], args[1]) - assert expr.type_of() == TealType.uint64 + args = [pt.Int(3), pt.Int(8)] + expr = pt.Mul(args[0], args[1]) + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock( - [TealOp(args[0], Op.int, 3), TealOp(args[1], Op.int, 8), TealOp(expr, Op.mul)] + expected = pt.TealSimpleBlock( + [ + pt.TealOp(args[0], pt.Op.int, 3), + pt.TealOp(args[1], pt.Op.int, 8), + pt.TealOp(expr, pt.Op.mul), + ] ) actual, _ = expr.__teal__(teal2Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected def test_mul_overload(): - args = [Int(3), Int(8), Int(10)] + args = [pt.Int(3), pt.Int(8), pt.Int(10)] expr = args[0] * args[1] * args[2] - assert expr.type_of() == TealType.uint64 + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(args[0], Op.int, 3), - TealOp(args[1], Op.int, 8), - TealOp(None, Op.mul), - TealOp(args[2], Op.int, 10), - TealOp(None, Op.mul), + pt.TealOp(args[0], pt.Op.int, 3), + pt.TealOp(args[1], pt.Op.int, 8), + pt.TealOp(None, pt.Op.mul), + pt.TealOp(args[2], pt.Op.int, 10), + pt.TealOp(None, pt.Op.mul), ] ) actual, _ = expr.__teal__(teal2Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected def test_mul_invalid(): - with pytest.raises(TealTypeError): - Mul(Int(2), Txn.receiver()) + with pytest.raises(pt.TealTypeError): + pt.Mul(pt.Int(2), pt.Txn.receiver()) - with pytest.raises(TealTypeError): - Mul(Txn.sender(), Int(2)) + with pytest.raises(pt.TealTypeError): + pt.Mul(pt.Txn.sender(), pt.Int(2)) def test_div(): - args = [Int(9), Int(3)] - expr = Div(args[0], args[1]) - assert expr.type_of() == TealType.uint64 + args = [pt.Int(9), pt.Int(3)] + expr = pt.Div(args[0], args[1]) + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock( - [TealOp(args[0], Op.int, 9), TealOp(args[1], Op.int, 3), TealOp(expr, Op.div)] + expected = pt.TealSimpleBlock( + [ + pt.TealOp(args[0], pt.Op.int, 9), + pt.TealOp(args[1], pt.Op.int, 3), + pt.TealOp(expr, pt.Op.div), + ] ) actual, _ = expr.__teal__(teal2Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected def test_div_overload(): - args = [Int(9), Int(3), Int(3)] + args = [pt.Int(9), pt.Int(3), pt.Int(3)] expr = args[0] / args[1] / args[2] - assert expr.type_of() == TealType.uint64 + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(args[0], Op.int, 9), - TealOp(args[1], Op.int, 3), - TealOp(None, Op.div), - TealOp(args[2], Op.int, 3), - TealOp(None, Op.div), + pt.TealOp(args[0], pt.Op.int, 9), + pt.TealOp(args[1], pt.Op.int, 3), + pt.TealOp(None, pt.Op.div), + pt.TealOp(args[2], pt.Op.int, 3), + pt.TealOp(None, pt.Op.div), ] ) actual, _ = expr.__teal__(teal2Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected def test_div_invalid(): - with pytest.raises(TealTypeError): - Div(Int(2), Txn.receiver()) + with pytest.raises(pt.TealTypeError): + pt.Div(pt.Int(2), pt.Txn.receiver()) - with pytest.raises(TealTypeError): - Div(Txn.sender(), Int(2)) + with pytest.raises(pt.TealTypeError): + pt.Div(pt.Txn.sender(), pt.Int(2)) def test_mod(): - args = [Int(10), Int(9)] - expr = Mod(args[0], args[1]) - assert expr.type_of() == TealType.uint64 + args = [pt.Int(10), pt.Int(9)] + expr = pt.Mod(args[0], args[1]) + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock( - [TealOp(args[0], Op.int, 10), TealOp(args[1], Op.int, 9), TealOp(expr, Op.mod)] + expected = pt.TealSimpleBlock( + [ + pt.TealOp(args[0], pt.Op.int, 10), + pt.TealOp(args[1], pt.Op.int, 9), + pt.TealOp(expr, pt.Op.mod), + ] ) actual, _ = expr.__teal__(teal2Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected def test_mod_overload(): - args = [Int(10), Int(9), Int(100)] + args = [pt.Int(10), pt.Int(9), pt.Int(100)] expr = args[0] % args[1] % args[2] - assert expr.type_of() == TealType.uint64 + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(args[0], Op.int, 10), - TealOp(args[1], Op.int, 9), - TealOp(None, Op.mod), - TealOp(args[2], Op.int, 100), - TealOp(None, Op.mod), + pt.TealOp(args[0], pt.Op.int, 10), + pt.TealOp(args[1], pt.Op.int, 9), + pt.TealOp(None, pt.Op.mod), + pt.TealOp(args[2], pt.Op.int, 100), + pt.TealOp(None, pt.Op.mod), ] ) actual, _ = expr.__teal__(teal2Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected def test_mod_invalid(): - with pytest.raises(TealTypeError): - Mod(Txn.receiver(), Int(2)) + with pytest.raises(pt.TealTypeError): + pt.Mod(pt.Txn.receiver(), pt.Int(2)) - with pytest.raises(TealTypeError): - Mod(Int(2), Txn.sender()) + with pytest.raises(pt.TealTypeError): + pt.Mod(pt.Int(2), pt.Txn.sender()) def test_exp(): - args = [Int(2), Int(9)] - expr = Exp(args[0], args[1]) - assert expr.type_of() == TealType.uint64 + args = [pt.Int(2), pt.Int(9)] + expr = pt.Exp(args[0], args[1]) + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock( - [TealOp(args[0], Op.int, 2), TealOp(args[1], Op.int, 9), TealOp(expr, Op.exp)] + expected = pt.TealSimpleBlock( + [ + pt.TealOp(args[0], pt.Op.int, 2), + pt.TealOp(args[1], pt.Op.int, 9), + pt.TealOp(expr, pt.Op.exp), + ] ) actual, _ = expr.__teal__(teal4Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): expr.__teal__(teal3Options) def test_exp_overload(): - args = [Int(2), Int(3), Int(1)] + args = [pt.Int(2), pt.Int(3), pt.Int(1)] # this is equivalent to args[0] ** (args[1] ** args[2]) expr = args[0] ** args[1] ** args[2] - assert expr.type_of() == TealType.uint64 + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(args[0], Op.int, 2), - TealOp(args[1], Op.int, 3), - TealOp(args[2], Op.int, 1), - TealOp(None, Op.exp), - TealOp(None, Op.exp), + pt.TealOp(args[0], pt.Op.int, 2), + pt.TealOp(args[1], pt.Op.int, 3), + pt.TealOp(args[2], pt.Op.int, 1), + pt.TealOp(None, pt.Op.exp), + pt.TealOp(None, pt.Op.exp), ] ) actual, _ = expr.__teal__(teal4Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): expr.__teal__(teal3Options) def test_exp_invalid(): - with pytest.raises(TealTypeError): - Exp(Txn.receiver(), Int(2)) + with pytest.raises(pt.TealTypeError): + pt.Exp(pt.Txn.receiver(), pt.Int(2)) - with pytest.raises(TealTypeError): - Exp(Int(2), Txn.sender()) + with pytest.raises(pt.TealTypeError): + pt.Exp(pt.Int(2), pt.Txn.sender()) def test_arithmetic(): - args = [Int(2), Int(3), Int(5), Int(6), Int(8), Int(9)] + args = [pt.Int(2), pt.Int(3), pt.Int(5), pt.Int(6), pt.Int(8), pt.Int(9)] v = ((args[0] + args[1]) / ((args[2] - args[3]) * args[4])) % args[5] - assert v.type_of() == TealType.uint64 + assert v.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(args[0], Op.int, 2), - TealOp(args[1], Op.int, 3), - TealOp(None, Op.add), - TealOp(args[2], Op.int, 5), - TealOp(args[3], Op.int, 6), - TealOp(None, Op.minus), - TealOp(args[4], Op.int, 8), - TealOp(None, Op.mul), - TealOp(None, Op.div), - TealOp(args[5], Op.int, 9), - TealOp(None, Op.mod), + pt.TealOp(args[0], pt.Op.int, 2), + pt.TealOp(args[1], pt.Op.int, 3), + pt.TealOp(None, pt.Op.add), + pt.TealOp(args[2], pt.Op.int, 5), + pt.TealOp(args[3], pt.Op.int, 6), + pt.TealOp(None, pt.Op.minus), + pt.TealOp(args[4], pt.Op.int, 8), + pt.TealOp(None, pt.Op.mul), + pt.TealOp(None, pt.Op.div), + pt.TealOp(args[5], pt.Op.int, 9), + pt.TealOp(None, pt.Op.mod), ] ) actual, _ = v.__teal__(teal2Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected def test_bitwise_and(): - args = [Int(1), Int(2)] - expr = BitwiseAnd(args[0], args[1]) - assert expr.type_of() == TealType.uint64 + args = [pt.Int(1), pt.Int(2)] + expr = pt.BitwiseAnd(args[0], args[1]) + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(args[0], Op.int, 1), - TealOp(args[1], Op.int, 2), - TealOp(expr, Op.bitwise_and), + pt.TealOp(args[0], pt.Op.int, 1), + pt.TealOp(args[1], pt.Op.int, 2), + pt.TealOp(expr, pt.Op.bitwise_and), ] ) actual, _ = expr.__teal__(teal2Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected def test_bitwise_and_overload(): - args = [Int(1), Int(2), Int(4)] + args = [pt.Int(1), pt.Int(2), pt.Int(4)] expr = args[0] & args[1] & args[2] - assert expr.type_of() == TealType.uint64 + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(args[0], Op.int, 1), - TealOp(args[1], Op.int, 2), - TealOp(None, Op.bitwise_and), - TealOp(args[2], Op.int, 4), - TealOp(None, Op.bitwise_and), + pt.TealOp(args[0], pt.Op.int, 1), + pt.TealOp(args[1], pt.Op.int, 2), + pt.TealOp(None, pt.Op.bitwise_and), + pt.TealOp(args[2], pt.Op.int, 4), + pt.TealOp(None, pt.Op.bitwise_and), ] ) actual, _ = expr.__teal__(teal2Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected def test_bitwise_and_invalid(): - with pytest.raises(TealTypeError): - BitwiseAnd(Int(2), Txn.receiver()) + with pytest.raises(pt.TealTypeError): + pt.BitwiseAnd(pt.Int(2), pt.Txn.receiver()) - with pytest.raises(TealTypeError): - BitwiseAnd(Txn.sender(), Int(2)) + with pytest.raises(pt.TealTypeError): + pt.BitwiseAnd(pt.Txn.sender(), pt.Int(2)) def test_bitwise_or(): - args = [Int(1), Int(2)] - expr = BitwiseOr(args[0], args[1]) - assert expr.type_of() == TealType.uint64 + args = [pt.Int(1), pt.Int(2)] + expr = pt.BitwiseOr(args[0], args[1]) + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(args[0], Op.int, 1), - TealOp(args[1], Op.int, 2), - TealOp(expr, Op.bitwise_or), + pt.TealOp(args[0], pt.Op.int, 1), + pt.TealOp(args[1], pt.Op.int, 2), + pt.TealOp(expr, pt.Op.bitwise_or), ] ) actual, _ = expr.__teal__(teal2Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected def test_bitwise_or_overload(): - args = [Int(1), Int(2), Int(4)] + args = [pt.Int(1), pt.Int(2), pt.Int(4)] expr = args[0] | args[1] | args[2] - assert expr.type_of() == TealType.uint64 + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(args[0], Op.int, 1), - TealOp(args[1], Op.int, 2), - TealOp(None, Op.bitwise_or), - TealOp(args[2], Op.int, 4), - TealOp(None, Op.bitwise_or), + pt.TealOp(args[0], pt.Op.int, 1), + pt.TealOp(args[1], pt.Op.int, 2), + pt.TealOp(None, pt.Op.bitwise_or), + pt.TealOp(args[2], pt.Op.int, 4), + pt.TealOp(None, pt.Op.bitwise_or), ] ) actual, _ = expr.__teal__(teal2Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected def test_bitwise_or_invalid(): - with pytest.raises(TealTypeError): - BitwiseOr(Int(2), Txn.receiver()) + with pytest.raises(pt.TealTypeError): + pt.BitwiseOr(pt.Int(2), pt.Txn.receiver()) - with pytest.raises(TealTypeError): - BitwiseOr(Txn.sender(), Int(2)) + with pytest.raises(pt.TealTypeError): + pt.BitwiseOr(pt.Txn.sender(), pt.Int(2)) def test_bitwise_xor(): - args = [Int(1), Int(3)] - expr = BitwiseXor(args[0], args[1]) - assert expr.type_of() == TealType.uint64 + args = [pt.Int(1), pt.Int(3)] + expr = pt.BitwiseXor(args[0], args[1]) + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(args[0], Op.int, 1), - TealOp(args[1], Op.int, 3), - TealOp(expr, Op.bitwise_xor), + pt.TealOp(args[0], pt.Op.int, 1), + pt.TealOp(args[1], pt.Op.int, 3), + pt.TealOp(expr, pt.Op.bitwise_xor), ] ) actual, _ = expr.__teal__(teal2Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected def test_bitwise_xor_overload(): - args = [Int(1), Int(3), Int(5)] + args = [pt.Int(1), pt.Int(3), pt.Int(5)] expr = args[0] ^ args[1] ^ args[2] - assert expr.type_of() == TealType.uint64 + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(args[0], Op.int, 1), - TealOp(args[1], Op.int, 3), - TealOp(None, Op.bitwise_xor), - TealOp(args[2], Op.int, 5), - TealOp(None, Op.bitwise_xor), + pt.TealOp(args[0], pt.Op.int, 1), + pt.TealOp(args[1], pt.Op.int, 3), + pt.TealOp(None, pt.Op.bitwise_xor), + pt.TealOp(args[2], pt.Op.int, 5), + pt.TealOp(None, pt.Op.bitwise_xor), ] ) actual, _ = expr.__teal__(teal2Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected def test_bitwise_xor_invalid(): - with pytest.raises(TealTypeError): - BitwiseXor(Int(2), Txn.receiver()) + with pytest.raises(pt.TealTypeError): + pt.BitwiseXor(pt.Int(2), pt.Txn.receiver()) - with pytest.raises(TealTypeError): - BitwiseXor(Txn.sender(), Int(2)) + with pytest.raises(pt.TealTypeError): + pt.BitwiseXor(pt.Txn.sender(), pt.Int(2)) def test_shift_left(): - args = [Int(5), Int(1)] - expr = ShiftLeft(args[0], args[1]) - assert expr.type_of() == TealType.uint64 + args = [pt.Int(5), pt.Int(1)] + expr = pt.ShiftLeft(args[0], args[1]) + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock( - [TealOp(args[0], Op.int, 5), TealOp(args[1], Op.int, 1), TealOp(expr, Op.shl)] + expected = pt.TealSimpleBlock( + [ + pt.TealOp(args[0], pt.Op.int, 5), + pt.TealOp(args[1], pt.Op.int, 1), + pt.TealOp(expr, pt.Op.shl), + ] ) actual, _ = expr.__teal__(teal4Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): expr.__teal__(teal3Options) def test_shift_left_overload(): - args = [Int(5), Int(1), Int(2)] + args = [pt.Int(5), pt.Int(1), pt.Int(2)] expr = args[0] << args[1] << args[2] - assert expr.type_of() == TealType.uint64 + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(args[0], Op.int, 5), - TealOp(args[1], Op.int, 1), - TealOp(None, Op.shl), - TealOp(args[2], Op.int, 2), - TealOp(None, Op.shl), + pt.TealOp(args[0], pt.Op.int, 5), + pt.TealOp(args[1], pt.Op.int, 1), + pt.TealOp(None, pt.Op.shl), + pt.TealOp(args[2], pt.Op.int, 2), + pt.TealOp(None, pt.Op.shl), ] ) actual, _ = expr.__teal__(teal4Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): expr.__teal__(teal3Options) def test_shift_left_invalid(): - with pytest.raises(TealTypeError): - ShiftLeft(Int(2), Txn.receiver()) + with pytest.raises(pt.TealTypeError): + pt.ShiftLeft(pt.Int(2), pt.Txn.receiver()) - with pytest.raises(TealTypeError): - ShiftLeft(Txn.sender(), Int(2)) + with pytest.raises(pt.TealTypeError): + pt.ShiftLeft(pt.Txn.sender(), pt.Int(2)) def test_shift_right(): - args = [Int(5), Int(1)] - expr = ShiftRight(args[0], args[1]) - assert expr.type_of() == TealType.uint64 + args = [pt.Int(5), pt.Int(1)] + expr = pt.ShiftRight(args[0], args[1]) + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock( - [TealOp(args[0], Op.int, 5), TealOp(args[1], Op.int, 1), TealOp(expr, Op.shr)] + expected = pt.TealSimpleBlock( + [ + pt.TealOp(args[0], pt.Op.int, 5), + pt.TealOp(args[1], pt.Op.int, 1), + pt.TealOp(expr, pt.Op.shr), + ] ) actual, _ = expr.__teal__(teal4Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): expr.__teal__(teal3Options) def test_shift_right_overload(): - args = [Int(5), Int(1), Int(2)] + args = [pt.Int(5), pt.Int(1), pt.Int(2)] expr = args[0] >> args[1] >> args[2] - assert expr.type_of() == TealType.uint64 + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(args[0], Op.int, 5), - TealOp(args[1], Op.int, 1), - TealOp(None, Op.shr), - TealOp(args[2], Op.int, 2), - TealOp(None, Op.shr), + pt.TealOp(args[0], pt.Op.int, 5), + pt.TealOp(args[1], pt.Op.int, 1), + pt.TealOp(None, pt.Op.shr), + pt.TealOp(args[2], pt.Op.int, 2), + pt.TealOp(None, pt.Op.shr), ] ) actual, _ = expr.__teal__(teal4Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): expr.__teal__(teal3Options) def test_shift_right_invalid(): - with pytest.raises(TealTypeError): - ShiftRight(Int(2), Txn.receiver()) + with pytest.raises(pt.TealTypeError): + pt.ShiftRight(pt.Int(2), pt.Txn.receiver()) - with pytest.raises(TealTypeError): - ShiftRight(Txn.sender(), Int(2)) + with pytest.raises(pt.TealTypeError): + pt.ShiftRight(pt.Txn.sender(), pt.Int(2)) def test_eq(): - args_int = [Int(2), Int(3)] - expr_int = Eq(args_int[0], args_int[1]) - assert expr_int.type_of() == TealType.uint64 + args_int = [pt.Int(2), pt.Int(3)] + expr_int = pt.Eq(args_int[0], args_int[1]) + assert expr_int.type_of() == pt.TealType.uint64 - expected_int = TealSimpleBlock( + expected_int = pt.TealSimpleBlock( [ - TealOp(args_int[0], Op.int, 2), - TealOp(args_int[1], Op.int, 3), - TealOp(expr_int, Op.eq), + pt.TealOp(args_int[0], pt.Op.int, 2), + pt.TealOp(args_int[1], pt.Op.int, 3), + pt.TealOp(expr_int, pt.Op.eq), ] ) actual_int, _ = expr_int.__teal__(teal2Options) actual_int.addIncoming() - actual_int = TealBlock.NormalizeBlocks(actual_int) + actual_int = pt.TealBlock.NormalizeBlocks(actual_int) assert actual_int == expected_int - args_bytes = [Txn.receiver(), Txn.sender()] - expr_bytes = Eq(args_bytes[0], args_bytes[1]) - assert expr_bytes.type_of() == TealType.uint64 + args_bytes = [pt.Txn.receiver(), pt.Txn.sender()] + expr_bytes = pt.Eq(args_bytes[0], args_bytes[1]) + assert expr_bytes.type_of() == pt.TealType.uint64 - expected_bytes = TealSimpleBlock( + expected_bytes = pt.TealSimpleBlock( [ - TealOp(args_bytes[0], Op.txn, "Receiver"), - TealOp(args_bytes[1], Op.txn, "Sender"), - TealOp(expr_bytes, Op.eq), + pt.TealOp(args_bytes[0], pt.Op.txn, "Receiver"), + pt.TealOp(args_bytes[1], pt.Op.txn, "Sender"), + pt.TealOp(expr_bytes, pt.Op.eq), ] ) actual_bytes, _ = expr_bytes.__teal__(teal2Options) actual_bytes.addIncoming() - actual_bytes = TealBlock.NormalizeBlocks(actual_bytes) + actual_bytes = pt.TealBlock.NormalizeBlocks(actual_bytes) assert actual_bytes == expected_bytes def test_eq_overload(): - args_int = [Int(2), Int(3)] + args_int = [pt.Int(2), pt.Int(3)] expr_int = args_int[0] == args_int[1] - assert expr_int.type_of() == TealType.uint64 + assert expr_int.type_of() == pt.TealType.uint64 - expected_int = TealSimpleBlock( + expected_int = pt.TealSimpleBlock( [ - TealOp(args_int[0], Op.int, 2), - TealOp(args_int[1], Op.int, 3), - TealOp(expr_int, Op.eq), + pt.TealOp(args_int[0], pt.Op.int, 2), + pt.TealOp(args_int[1], pt.Op.int, 3), + pt.TealOp(expr_int, pt.Op.eq), ] ) actual_int, _ = expr_int.__teal__(teal2Options) actual_int.addIncoming() - actual_int = TealBlock.NormalizeBlocks(actual_int) + actual_int = pt.TealBlock.NormalizeBlocks(actual_int) assert actual_int == expected_int - args_bytes = [Txn.receiver(), Txn.sender()] + args_bytes = [pt.Txn.receiver(), pt.Txn.sender()] expr_bytes = args_bytes[0] == args_bytes[1] - assert expr_bytes.type_of() == TealType.uint64 + assert expr_bytes.type_of() == pt.TealType.uint64 - expected_bytes = TealSimpleBlock( + expected_bytes = pt.TealSimpleBlock( [ - TealOp(args_bytes[0], Op.txn, "Receiver"), - TealOp(args_bytes[1], Op.txn, "Sender"), - TealOp(expr_bytes, Op.eq), + pt.TealOp(args_bytes[0], pt.Op.txn, "Receiver"), + pt.TealOp(args_bytes[1], pt.Op.txn, "Sender"), + pt.TealOp(expr_bytes, pt.Op.eq), ] ) actual_bytes, _ = expr_bytes.__teal__(teal2Options) actual_bytes.addIncoming() - actual_bytes = TealBlock.NormalizeBlocks(actual_bytes) + actual_bytes = pt.TealBlock.NormalizeBlocks(actual_bytes) assert actual_bytes == expected_bytes def test_eq_invalid(): - with pytest.raises(TealTypeError): - Eq(Txn.fee(), Txn.receiver()) + with pytest.raises(pt.TealTypeError): + pt.Eq(pt.Txn.fee(), pt.Txn.receiver()) - with pytest.raises(TealTypeError): - Eq(Txn.sender(), Int(7)) + with pytest.raises(pt.TealTypeError): + pt.Eq(pt.Txn.sender(), pt.Int(7)) def test_neq(): - args_int = [Int(2), Int(3)] - expr_int = Neq(args_int[0], args_int[1]) - assert expr_int.type_of() == TealType.uint64 + args_int = [pt.Int(2), pt.Int(3)] + expr_int = pt.Neq(args_int[0], args_int[1]) + assert expr_int.type_of() == pt.TealType.uint64 - expected_int = TealSimpleBlock( + expected_int = pt.TealSimpleBlock( [ - TealOp(args_int[0], Op.int, 2), - TealOp(args_int[1], Op.int, 3), - TealOp(expr_int, Op.neq), + pt.TealOp(args_int[0], pt.Op.int, 2), + pt.TealOp(args_int[1], pt.Op.int, 3), + pt.TealOp(expr_int, pt.Op.neq), ] ) actual_int, _ = expr_int.__teal__(teal2Options) actual_int.addIncoming() - actual_int = TealBlock.NormalizeBlocks(actual_int) + actual_int = pt.TealBlock.NormalizeBlocks(actual_int) assert actual_int == expected_int - args_bytes = [Txn.receiver(), Txn.sender()] - expr_bytes = Neq(args_bytes[0], args_bytes[1]) - assert expr_bytes.type_of() == TealType.uint64 + args_bytes = [pt.Txn.receiver(), pt.Txn.sender()] + expr_bytes = pt.Neq(args_bytes[0], args_bytes[1]) + assert expr_bytes.type_of() == pt.TealType.uint64 - expected_bytes = TealSimpleBlock( + expected_bytes = pt.TealSimpleBlock( [ - TealOp(args_bytes[0], Op.txn, "Receiver"), - TealOp(args_bytes[1], Op.txn, "Sender"), - TealOp(expr_bytes, Op.neq), + pt.TealOp(args_bytes[0], pt.Op.txn, "Receiver"), + pt.TealOp(args_bytes[1], pt.Op.txn, "Sender"), + pt.TealOp(expr_bytes, pt.Op.neq), ] ) actual_bytes, _ = expr_bytes.__teal__(teal2Options) actual_bytes.addIncoming() - actual_bytes = TealBlock.NormalizeBlocks(actual_bytes) + actual_bytes = pt.TealBlock.NormalizeBlocks(actual_bytes) assert actual_bytes == expected_bytes def test_neq_overload(): - args_int = [Int(2), Int(3)] + args_int = [pt.Int(2), pt.Int(3)] expr_int = args_int[0] != args_int[1] - assert expr_int.type_of() == TealType.uint64 + assert expr_int.type_of() == pt.TealType.uint64 - expected_int = TealSimpleBlock( + expected_int = pt.TealSimpleBlock( [ - TealOp(args_int[0], Op.int, 2), - TealOp(args_int[1], Op.int, 3), - TealOp(expr_int, Op.neq), + pt.TealOp(args_int[0], pt.Op.int, 2), + pt.TealOp(args_int[1], pt.Op.int, 3), + pt.TealOp(expr_int, pt.Op.neq), ] ) actual_int, _ = expr_int.__teal__(teal2Options) actual_int.addIncoming() - actual_int = TealBlock.NormalizeBlocks(actual_int) + actual_int = pt.TealBlock.NormalizeBlocks(actual_int) assert actual_int == expected_int - args_bytes = [Txn.receiver(), Txn.sender()] + args_bytes = [pt.Txn.receiver(), pt.Txn.sender()] expr_bytes = args_bytes[0] != args_bytes[1] - assert expr_bytes.type_of() == TealType.uint64 + assert expr_bytes.type_of() == pt.TealType.uint64 - expected_bytes = TealSimpleBlock( + expected_bytes = pt.TealSimpleBlock( [ - TealOp(args_bytes[0], Op.txn, "Receiver"), - TealOp(args_bytes[1], Op.txn, "Sender"), - TealOp(expr_bytes, Op.neq), + pt.TealOp(args_bytes[0], pt.Op.txn, "Receiver"), + pt.TealOp(args_bytes[1], pt.Op.txn, "Sender"), + pt.TealOp(expr_bytes, pt.Op.neq), ] ) actual_bytes, _ = expr_bytes.__teal__(teal2Options) actual_bytes.addIncoming() - actual_bytes = TealBlock.NormalizeBlocks(actual_bytes) + actual_bytes = pt.TealBlock.NormalizeBlocks(actual_bytes) assert actual_bytes == expected_bytes def test_neq_invalid(): - with pytest.raises(TealTypeError): - Neq(Txn.fee(), Txn.receiver()) + with pytest.raises(pt.TealTypeError): + pt.Neq(pt.Txn.fee(), pt.Txn.receiver()) - with pytest.raises(TealTypeError): - Neq(Txn.sender(), Int(7)) + with pytest.raises(pt.TealTypeError): + pt.Neq(pt.Txn.sender(), pt.Int(7)) def test_lt(): - args = [Int(2), Int(3)] - expr = Lt(args[0], args[1]) - assert expr.type_of() == TealType.uint64 + args = [pt.Int(2), pt.Int(3)] + expr = pt.Lt(args[0], args[1]) + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock( - [TealOp(args[0], Op.int, 2), TealOp(args[1], Op.int, 3), TealOp(expr, Op.lt)] + expected = pt.TealSimpleBlock( + [ + pt.TealOp(args[0], pt.Op.int, 2), + pt.TealOp(args[1], pt.Op.int, 3), + pt.TealOp(expr, pt.Op.lt), + ] ) actual, _ = expr.__teal__(teal2Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected def test_lt_overload(): - args = [Int(2), Int(3)] + args = [pt.Int(2), pt.Int(3)] expr = args[0] < args[1] - assert expr.type_of() == TealType.uint64 + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock( - [TealOp(args[0], Op.int, 2), TealOp(args[1], Op.int, 3), TealOp(expr, Op.lt)] + expected = pt.TealSimpleBlock( + [ + pt.TealOp(args[0], pt.Op.int, 2), + pt.TealOp(args[1], pt.Op.int, 3), + pt.TealOp(expr, pt.Op.lt), + ] ) actual, _ = expr.__teal__(teal2Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected def test_lt_invalid(): - with pytest.raises(TealTypeError): - Lt(Int(7), Txn.receiver()) + with pytest.raises(pt.TealTypeError): + pt.Lt(pt.Int(7), pt.Txn.receiver()) - with pytest.raises(TealTypeError): - Lt(Txn.sender(), Int(7)) + with pytest.raises(pt.TealTypeError): + pt.Lt(pt.Txn.sender(), pt.Int(7)) def test_le(): - args = [Int(1), Int(2)] - expr = Le(args[0], args[1]) - assert expr.type_of() == TealType.uint64 + args = [pt.Int(1), pt.Int(2)] + expr = pt.Le(args[0], args[1]) + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock( - [TealOp(args[0], Op.int, 1), TealOp(args[1], Op.int, 2), TealOp(expr, Op.le)] + expected = pt.TealSimpleBlock( + [ + pt.TealOp(args[0], pt.Op.int, 1), + pt.TealOp(args[1], pt.Op.int, 2), + pt.TealOp(expr, pt.Op.le), + ] ) actual, _ = expr.__teal__(teal2Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected def test_le_overload(): - args = [Int(1), Int(2)] + args = [pt.Int(1), pt.Int(2)] expr = args[0] <= args[1] - assert expr.type_of() == TealType.uint64 + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock( - [TealOp(args[0], Op.int, 1), TealOp(args[1], Op.int, 2), TealOp(expr, Op.le)] + expected = pt.TealSimpleBlock( + [ + pt.TealOp(args[0], pt.Op.int, 1), + pt.TealOp(args[1], pt.Op.int, 2), + pt.TealOp(expr, pt.Op.le), + ] ) actual, _ = expr.__teal__(teal2Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected def test_le_invalid(): - with pytest.raises(TealTypeError): - Le(Int(1), Txn.receiver()) + with pytest.raises(pt.TealTypeError): + pt.Le(pt.Int(1), pt.Txn.receiver()) - with pytest.raises(TealTypeError): - Le(Txn.sender(), Int(1)) + with pytest.raises(pt.TealTypeError): + pt.Le(pt.Txn.sender(), pt.Int(1)) def test_gt(): - args = [Int(2), Int(3)] - expr = Gt(args[0], args[1]) - assert expr.type_of() == TealType.uint64 + args = [pt.Int(2), pt.Int(3)] + expr = pt.Gt(args[0], args[1]) + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock( - [TealOp(args[0], Op.int, 2), TealOp(args[1], Op.int, 3), TealOp(expr, Op.gt)] + expected = pt.TealSimpleBlock( + [ + pt.TealOp(args[0], pt.Op.int, 2), + pt.TealOp(args[1], pt.Op.int, 3), + pt.TealOp(expr, pt.Op.gt), + ] ) actual, _ = expr.__teal__(teal2Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected def test_gt_overload(): - args = [Int(2), Int(3)] + args = [pt.Int(2), pt.Int(3)] expr = args[0] > args[1] - assert expr.type_of() == TealType.uint64 + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock( - [TealOp(args[0], Op.int, 2), TealOp(args[1], Op.int, 3), TealOp(expr, Op.gt)] + expected = pt.TealSimpleBlock( + [ + pt.TealOp(args[0], pt.Op.int, 2), + pt.TealOp(args[1], pt.Op.int, 3), + pt.TealOp(expr, pt.Op.gt), + ] ) actual, _ = expr.__teal__(teal2Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected def test_gt_invalid(): - with pytest.raises(TealTypeError): - Gt(Int(1), Txn.receiver()) + with pytest.raises(pt.TealTypeError): + pt.Gt(pt.Int(1), pt.Txn.receiver()) - with pytest.raises(TealTypeError): - Gt(Txn.receiver(), Int(1)) + with pytest.raises(pt.TealTypeError): + pt.Gt(pt.Txn.receiver(), pt.Int(1)) def test_ge(): - args = [Int(1), Int(10)] - expr = Ge(args[0], args[1]) - assert expr.type_of() == TealType.uint64 + args = [pt.Int(1), pt.Int(10)] + expr = pt.Ge(args[0], args[1]) + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock( - [TealOp(args[0], Op.int, 1), TealOp(args[1], Op.int, 10), TealOp(expr, Op.ge)] + expected = pt.TealSimpleBlock( + [ + pt.TealOp(args[0], pt.Op.int, 1), + pt.TealOp(args[1], pt.Op.int, 10), + pt.TealOp(expr, pt.Op.ge), + ] ) actual, _ = expr.__teal__(teal2Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected def test_ge_overload(): - args = [Int(1), Int(10)] + args = [pt.Int(1), pt.Int(10)] expr = args[0] >= args[1] - assert expr.type_of() == TealType.uint64 + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock( - [TealOp(args[0], Op.int, 1), TealOp(args[1], Op.int, 10), TealOp(expr, Op.ge)] + expected = pt.TealSimpleBlock( + [ + pt.TealOp(args[0], pt.Op.int, 1), + pt.TealOp(args[1], pt.Op.int, 10), + pt.TealOp(expr, pt.Op.ge), + ] ) actual, _ = expr.__teal__(teal2Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected def test_ge_invalid(): - with pytest.raises(TealTypeError): - Ge(Int(1), Txn.receiver()) + with pytest.raises(pt.TealTypeError): + pt.Ge(pt.Int(1), pt.Txn.receiver()) - with pytest.raises(TealTypeError): - Ge(Txn.receiver(), Int(1)) + with pytest.raises(pt.TealTypeError): + pt.Ge(pt.Txn.receiver(), pt.Int(1)) def test_get_bit_int(): - args = [Int(3), Int(1)] - expr = GetBit(args[0], args[1]) - assert expr.type_of() == TealType.uint64 + args = [pt.Int(3), pt.Int(1)] + expr = pt.GetBit(args[0], args[1]) + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(args[0], Op.int, 3), - TealOp(args[1], Op.int, 1), - TealOp(expr, Op.getbit), + pt.TealOp(args[0], pt.Op.int, 3), + pt.TealOp(args[1], pt.Op.int, 1), + pt.TealOp(expr, pt.Op.getbit), ] ) actual, _ = expr.__teal__(teal3Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): expr.__teal__(teal2Options) def test_get_bit_bytes(): - args = [Bytes("base16", "0xFF"), Int(1)] - expr = GetBit(args[0], args[1]) - assert expr.type_of() == TealType.uint64 + args = [pt.Bytes("base16", "0xFF"), pt.Int(1)] + expr = pt.GetBit(args[0], args[1]) + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(args[0], Op.byte, "0xFF"), - TealOp(args[1], Op.int, 1), - TealOp(expr, Op.getbit), + pt.TealOp(args[0], pt.Op.byte, "0xFF"), + pt.TealOp(args[1], pt.Op.int, 1), + pt.TealOp(expr, pt.Op.getbit), ] ) actual, _ = expr.__teal__(teal3Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): expr.__teal__(teal2Options) def test_get_bit_invalid(): - with pytest.raises(TealTypeError): - GetBit(Int(3), Bytes("index")) + with pytest.raises(pt.TealTypeError): + pt.GetBit(pt.Int(3), pt.Bytes("index")) - with pytest.raises(TealTypeError): - GetBit(Bytes("base16", "0xFF"), Bytes("index")) + with pytest.raises(pt.TealTypeError): + pt.GetBit(pt.Bytes("base16", "0xFF"), pt.Bytes("index")) def test_get_byte(): - args = [Bytes("base16", "0xFF"), Int(0)] - expr = GetByte(args[0], args[1]) - assert expr.type_of() == TealType.uint64 + args = [pt.Bytes("base16", "0xFF"), pt.Int(0)] + expr = pt.GetByte(args[0], args[1]) + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(args[0], Op.byte, "0xFF"), - TealOp(args[1], Op.int, 0), - TealOp(expr, Op.getbyte), + pt.TealOp(args[0], pt.Op.byte, "0xFF"), + pt.TealOp(args[1], pt.Op.int, 0), + pt.TealOp(expr, pt.Op.getbyte), ] ) actual, _ = expr.__teal__(teal3Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): expr.__teal__(teal2Options) def test_get_byte_invalid(): - with pytest.raises(TealTypeError): - GetByte(Int(3), Int(0)) + with pytest.raises(pt.TealTypeError): + pt.GetByte(pt.Int(3), pt.Int(0)) - with pytest.raises(TealTypeError): - GetBit(Bytes("base16", "0xFF"), Bytes("index")) + with pytest.raises(pt.TealTypeError): + pt.GetBit(pt.Bytes("base16", "0xFF"), pt.Bytes("index")) def test_b_add(): args = [ - Bytes("base16", "0xFFFFFFFFFFFFFFFFFF"), - Bytes("base16", "0xFFFFFFFFFFFFFFFFFE"), + pt.Bytes("base16", "0xFFFFFFFFFFFFFFFFFF"), + pt.Bytes("base16", "0xFFFFFFFFFFFFFFFFFE"), ] - expr = BytesAdd(args[0], args[1]) - assert expr.type_of() == TealType.bytes + expr = pt.BytesAdd(args[0], args[1]) + assert expr.type_of() == pt.TealType.bytes - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(args[0], Op.byte, "0xFFFFFFFFFFFFFFFFFF"), - TealOp(args[1], Op.byte, "0xFFFFFFFFFFFFFFFFFE"), - TealOp(expr, Op.b_add), + pt.TealOp(args[0], pt.Op.byte, "0xFFFFFFFFFFFFFFFFFF"), + pt.TealOp(args[1], pt.Op.byte, "0xFFFFFFFFFFFFFFFFFE"), + pt.TealOp(expr, pt.Op.b_add), ] ) actual, _ = expr.__teal__(teal4Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): expr.__teal__(teal3Options) def test_b_add_invalid(): - with pytest.raises(TealTypeError): - BytesAdd(Int(2), Txn.receiver()) + with pytest.raises(pt.TealTypeError): + pt.BytesAdd(pt.Int(2), pt.Txn.receiver()) - with pytest.raises(TealTypeError): - BytesAdd(Bytes("base16", "0xFF"), Int(2)) + with pytest.raises(pt.TealTypeError): + pt.BytesAdd(pt.Bytes("base16", "0xFF"), pt.Int(2)) def test_b_minus(): args = [ - Bytes("base16", "0xFFFFFFFFFFFFFFFFFF"), - Bytes("base16", "0xFFFFFFFFFFFFFFFFFE"), + pt.Bytes("base16", "0xFFFFFFFFFFFFFFFFFF"), + pt.Bytes("base16", "0xFFFFFFFFFFFFFFFFFE"), ] - expr = BytesMinus(args[0], args[1]) - assert expr.type_of() == TealType.bytes + expr = pt.BytesMinus(args[0], args[1]) + assert expr.type_of() == pt.TealType.bytes - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(args[0], Op.byte, "0xFFFFFFFFFFFFFFFFFF"), - TealOp(args[1], Op.byte, "0xFFFFFFFFFFFFFFFFFE"), - TealOp(expr, Op.b_minus), + pt.TealOp(args[0], pt.Op.byte, "0xFFFFFFFFFFFFFFFFFF"), + pt.TealOp(args[1], pt.Op.byte, "0xFFFFFFFFFFFFFFFFFE"), + pt.TealOp(expr, pt.Op.b_minus), ] ) actual, _ = expr.__teal__(teal4Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): expr.__teal__(teal3Options) def test_b_minus_invalid(): - with pytest.raises(TealTypeError): - BytesMinus(Int(2), Txn.receiver()) + with pytest.raises(pt.TealTypeError): + pt.BytesMinus(pt.Int(2), pt.Txn.receiver()) - with pytest.raises(TealTypeError): - BytesMinus(Bytes("base16", "0xFF"), Int(2)) + with pytest.raises(pt.TealTypeError): + pt.BytesMinus(pt.Bytes("base16", "0xFF"), pt.Int(2)) def test_b_div(): - args = [Bytes("base16", "0xFFFFFFFFFFFFFFFF00"), Bytes("base16", "0xFF")] - expr = BytesDiv(args[0], args[1]) - assert expr.type_of() == TealType.bytes + args = [pt.Bytes("base16", "0xFFFFFFFFFFFFFFFF00"), pt.Bytes("base16", "0xFF")] + expr = pt.BytesDiv(args[0], args[1]) + assert expr.type_of() == pt.TealType.bytes - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(args[0], Op.byte, "0xFFFFFFFFFFFFFFFF00"), - TealOp(args[1], Op.byte, "0xFF"), - TealOp(expr, Op.b_div), + pt.TealOp(args[0], pt.Op.byte, "0xFFFFFFFFFFFFFFFF00"), + pt.TealOp(args[1], pt.Op.byte, "0xFF"), + pt.TealOp(expr, pt.Op.b_div), ] ) actual, _ = expr.__teal__(teal4Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): expr.__teal__(teal3Options) def test_b_div_invalid(): - with pytest.raises(TealTypeError): - BytesDiv(Int(2), Txn.receiver()) + with pytest.raises(pt.TealTypeError): + pt.BytesDiv(pt.Int(2), pt.Txn.receiver()) - with pytest.raises(TealTypeError): - BytesDiv(Bytes("base16", "0xFF"), Int(2)) + with pytest.raises(pt.TealTypeError): + pt.BytesDiv(pt.Bytes("base16", "0xFF"), pt.Int(2)) def test_b_mul(): - args = [Bytes("base16", "0xFFFFFFFFFFFFFFFF"), Bytes("base16", "0xFF")] - expr = BytesMul(args[0], args[1]) - assert expr.type_of() == TealType.bytes + args = [pt.Bytes("base16", "0xFFFFFFFFFFFFFFFF"), pt.Bytes("base16", "0xFF")] + expr = pt.BytesMul(args[0], args[1]) + assert expr.type_of() == pt.TealType.bytes - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(args[0], Op.byte, "0xFFFFFFFFFFFFFFFF"), - TealOp(args[1], Op.byte, "0xFF"), - TealOp(expr, Op.b_mul), + pt.TealOp(args[0], pt.Op.byte, "0xFFFFFFFFFFFFFFFF"), + pt.TealOp(args[1], pt.Op.byte, "0xFF"), + pt.TealOp(expr, pt.Op.b_mul), ] ) actual, _ = expr.__teal__(teal4Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): expr.__teal__(teal3Options) def test_b_mul_invalid(): - with pytest.raises(TealTypeError): - BytesMul(Int(2), Txn.receiver()) + with pytest.raises(pt.TealTypeError): + pt.BytesMul(pt.Int(2), pt.Txn.receiver()) - with pytest.raises(TealTypeError): - BytesMul(Bytes("base16", "0xFF"), Int(2)) + with pytest.raises(pt.TealTypeError): + pt.BytesMul(pt.Bytes("base16", "0xFF"), pt.Int(2)) def test_b_mod(): - args = [Bytes("base16", "0xFFFFFFFFFFFFFFFFFF"), Bytes("base16", "0xFF")] - expr = BytesMod(args[0], args[1]) - assert expr.type_of() == TealType.bytes + args = [pt.Bytes("base16", "0xFFFFFFFFFFFFFFFFFF"), pt.Bytes("base16", "0xFF")] + expr = pt.BytesMod(args[0], args[1]) + assert expr.type_of() == pt.TealType.bytes - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(args[0], Op.byte, "0xFFFFFFFFFFFFFFFFFF"), - TealOp(args[1], Op.byte, "0xFF"), - TealOp(expr, Op.b_mod), + pt.TealOp(args[0], pt.Op.byte, "0xFFFFFFFFFFFFFFFFFF"), + pt.TealOp(args[1], pt.Op.byte, "0xFF"), + pt.TealOp(expr, pt.Op.b_mod), ] ) actual, _ = expr.__teal__(teal4Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): expr.__teal__(teal3Options) def test_b_mod_invalid(): - with pytest.raises(TealTypeError): - BytesMod(Int(2), Txn.receiver()) + with pytest.raises(pt.TealTypeError): + pt.BytesMod(pt.Int(2), pt.Txn.receiver()) - with pytest.raises(TealTypeError): - BytesMod(Bytes("base16", "0xFF"), Int(2)) + with pytest.raises(pt.TealTypeError): + pt.BytesMod(pt.Bytes("base16", "0xFF"), pt.Int(2)) def test_b_and(): - args = [Bytes("base16", "0xFFFFFFFFFFFFFFFFF0"), Bytes("base16", "0xFF")] - expr = BytesAnd(args[0], args[1]) - assert expr.type_of() == TealType.bytes + args = [pt.Bytes("base16", "0xFFFFFFFFFFFFFFFFF0"), pt.Bytes("base16", "0xFF")] + expr = pt.BytesAnd(args[0], args[1]) + assert expr.type_of() == pt.TealType.bytes - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(args[0], Op.byte, "0xFFFFFFFFFFFFFFFFF0"), - TealOp(args[1], Op.byte, "0xFF"), - TealOp(expr, Op.b_and), + pt.TealOp(args[0], pt.Op.byte, "0xFFFFFFFFFFFFFFFFF0"), + pt.TealOp(args[1], pt.Op.byte, "0xFF"), + pt.TealOp(expr, pt.Op.b_and), ] ) actual, _ = expr.__teal__(teal4Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): expr.__teal__(teal3Options) def test_b_and_invalid(): - with pytest.raises(TealTypeError): - BytesAnd(Int(2), Txn.receiver()) + with pytest.raises(pt.TealTypeError): + pt.BytesAnd(pt.Int(2), pt.Txn.receiver()) - with pytest.raises(TealTypeError): - BytesAnd(Bytes("base16", "0xFF"), Int(2)) + with pytest.raises(pt.TealTypeError): + pt.BytesAnd(pt.Bytes("base16", "0xFF"), pt.Int(2)) def test_b_or(): - args = [Bytes("base16", "0xFFFFFFFFFFFFFFFFF0"), Bytes("base16", "0xFF")] - expr = BytesOr(args[0], args[1]) - assert expr.type_of() == TealType.bytes + args = [pt.Bytes("base16", "0xFFFFFFFFFFFFFFFFF0"), pt.Bytes("base16", "0xFF")] + expr = pt.BytesOr(args[0], args[1]) + assert expr.type_of() == pt.TealType.bytes - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(args[0], Op.byte, "0xFFFFFFFFFFFFFFFFF0"), - TealOp(args[1], Op.byte, "0xFF"), - TealOp(expr, Op.b_or), + pt.TealOp(args[0], pt.Op.byte, "0xFFFFFFFFFFFFFFFFF0"), + pt.TealOp(args[1], pt.Op.byte, "0xFF"), + pt.TealOp(expr, pt.Op.b_or), ] ) actual, _ = expr.__teal__(teal4Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): expr.__teal__(teal3Options) def test_b_or_invalid(): - with pytest.raises(TealTypeError): - BytesOr(Int(2), Txn.receiver()) + with pytest.raises(pt.TealTypeError): + pt.BytesOr(pt.Int(2), pt.Txn.receiver()) - with pytest.raises(TealTypeError): - BytesOr(Bytes("base16", "0xFF"), Int(2)) + with pytest.raises(pt.TealTypeError): + pt.BytesOr(pt.Bytes("base16", "0xFF"), pt.Int(2)) def test_b_xor(): - args = [Bytes("base16", "0xFFFFFFFFFFFFFFFFF0"), Bytes("base16", "0xFF")] - expr = BytesXor(args[0], args[1]) - assert expr.type_of() == TealType.bytes + args = [pt.Bytes("base16", "0xFFFFFFFFFFFFFFFFF0"), pt.Bytes("base16", "0xFF")] + expr = pt.BytesXor(args[0], args[1]) + assert expr.type_of() == pt.TealType.bytes - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(args[0], Op.byte, "0xFFFFFFFFFFFFFFFFF0"), - TealOp(args[1], Op.byte, "0xFF"), - TealOp(expr, Op.b_xor), + pt.TealOp(args[0], pt.Op.byte, "0xFFFFFFFFFFFFFFFFF0"), + pt.TealOp(args[1], pt.Op.byte, "0xFF"), + pt.TealOp(expr, pt.Op.b_xor), ] ) actual, _ = expr.__teal__(teal4Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): expr.__teal__(teal3Options) def test_b_xor_invalid(): - with pytest.raises(TealTypeError): - BytesXor(Int(2), Txn.receiver()) + with pytest.raises(pt.TealTypeError): + pt.BytesXor(pt.Int(2), pt.Txn.receiver()) - with pytest.raises(TealTypeError): - BytesXor(Bytes("base16", "0xFF"), Int(2)) + with pytest.raises(pt.TealTypeError): + pt.BytesXor(pt.Bytes("base16", "0xFF"), pt.Int(2)) def test_b_eq(): args = [ - Bytes("base16", "0xFFFFFFFFFFFFFFFFFF"), - Bytes("base16", "0xFFFFFFFFFFFFFFFFFF"), + pt.Bytes("base16", "0xFFFFFFFFFFFFFFFFFF"), + pt.Bytes("base16", "0xFFFFFFFFFFFFFFFFFF"), ] - expr = BytesEq(args[0], args[1]) - assert expr.type_of() == TealType.uint64 + expr = pt.BytesEq(args[0], args[1]) + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(args[0], Op.byte, "0xFFFFFFFFFFFFFFFFFF"), - TealOp(args[1], Op.byte, "0xFFFFFFFFFFFFFFFFFF"), - TealOp(expr, Op.b_eq), + pt.TealOp(args[0], pt.Op.byte, "0xFFFFFFFFFFFFFFFFFF"), + pt.TealOp(args[1], pt.Op.byte, "0xFFFFFFFFFFFFFFFFFF"), + pt.TealOp(expr, pt.Op.b_eq), ] ) actual, _ = expr.__teal__(teal4Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): expr.__teal__(teal3Options) def test_b_eq_invalid(): - with pytest.raises(TealTypeError): - BytesEq(Int(2), Txn.receiver()) + with pytest.raises(pt.TealTypeError): + pt.BytesEq(pt.Int(2), pt.Txn.receiver()) - with pytest.raises(TealTypeError): - BytesEq(Bytes("base16", "0xFF"), Int(2)) + with pytest.raises(pt.TealTypeError): + pt.BytesEq(pt.Bytes("base16", "0xFF"), pt.Int(2)) def test_b_neq(): args = [ - Bytes("base16", "0xFFFFFFFFFFFFFFFFFF"), - Bytes("base16", "0xFFFFFFFFFFFFFFFFFF"), + pt.Bytes("base16", "0xFFFFFFFFFFFFFFFFFF"), + pt.Bytes("base16", "0xFFFFFFFFFFFFFFFFFF"), ] - expr = BytesNeq(args[0], args[1]) - assert expr.type_of() == TealType.uint64 + expr = pt.BytesNeq(args[0], args[1]) + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(args[0], Op.byte, "0xFFFFFFFFFFFFFFFFFF"), - TealOp(args[1], Op.byte, "0xFFFFFFFFFFFFFFFFFF"), - TealOp(expr, Op.b_neq), + pt.TealOp(args[0], pt.Op.byte, "0xFFFFFFFFFFFFFFFFFF"), + pt.TealOp(args[1], pt.Op.byte, "0xFFFFFFFFFFFFFFFFFF"), + pt.TealOp(expr, pt.Op.b_neq), ] ) actual, _ = expr.__teal__(teal4Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): expr.__teal__(teal3Options) def test_b_neq_invalid(): - with pytest.raises(TealTypeError): - BytesNeq(Int(2), Txn.receiver()) + with pytest.raises(pt.TealTypeError): + pt.BytesNeq(pt.Int(2), pt.Txn.receiver()) - with pytest.raises(TealTypeError): - BytesNeq(Bytes("base16", "0xFF"), Int(2)) + with pytest.raises(pt.TealTypeError): + pt.BytesNeq(pt.Bytes("base16", "0xFF"), pt.Int(2)) def test_b_lt(): args = [ - Bytes("base16", "0xFFFFFFFFFFFFFFFFF0"), - Bytes("base16", "0xFFFFFFFFFFFFFFFFFF"), + pt.Bytes("base16", "0xFFFFFFFFFFFFFFFFF0"), + pt.Bytes("base16", "0xFFFFFFFFFFFFFFFFFF"), ] - expr = BytesLt(args[0], args[1]) - assert expr.type_of() == TealType.uint64 + expr = pt.BytesLt(args[0], args[1]) + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(args[0], Op.byte, "0xFFFFFFFFFFFFFFFFF0"), - TealOp(args[1], Op.byte, "0xFFFFFFFFFFFFFFFFFF"), - TealOp(expr, Op.b_lt), + pt.TealOp(args[0], pt.Op.byte, "0xFFFFFFFFFFFFFFFFF0"), + pt.TealOp(args[1], pt.Op.byte, "0xFFFFFFFFFFFFFFFFFF"), + pt.TealOp(expr, pt.Op.b_lt), ] ) actual, _ = expr.__teal__(teal4Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): expr.__teal__(teal3Options) def test_b_lt_invalid(): - with pytest.raises(TealTypeError): - BytesLt(Int(2), Txn.receiver()) + with pytest.raises(pt.TealTypeError): + pt.BytesLt(pt.Int(2), pt.Txn.receiver()) - with pytest.raises(TealTypeError): - BytesLt(Bytes("base16", "0xFF"), Int(2)) + with pytest.raises(pt.TealTypeError): + pt.BytesLt(pt.Bytes("base16", "0xFF"), pt.Int(2)) def test_b_le(): args = [ - Bytes("base16", "0xFFFFFFFFFFFFFFFFF0"), - Bytes("base16", "0xFFFFFFFFFFFFFFFFFF"), + pt.Bytes("base16", "0xFFFFFFFFFFFFFFFFF0"), + pt.Bytes("base16", "0xFFFFFFFFFFFFFFFFFF"), ] - expr = BytesLe(args[0], args[1]) - assert expr.type_of() == TealType.uint64 + expr = pt.BytesLe(args[0], args[1]) + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(args[0], Op.byte, "0xFFFFFFFFFFFFFFFFF0"), - TealOp(args[1], Op.byte, "0xFFFFFFFFFFFFFFFFFF"), - TealOp(expr, Op.b_le), + pt.TealOp(args[0], pt.Op.byte, "0xFFFFFFFFFFFFFFFFF0"), + pt.TealOp(args[1], pt.Op.byte, "0xFFFFFFFFFFFFFFFFFF"), + pt.TealOp(expr, pt.Op.b_le), ] ) actual, _ = expr.__teal__(teal4Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): expr.__teal__(teal3Options) def test_b_le_invalid(): - with pytest.raises(TealTypeError): - BytesLe(Int(2), Txn.receiver()) + with pytest.raises(pt.TealTypeError): + pt.BytesLe(pt.Int(2), pt.Txn.receiver()) - with pytest.raises(TealTypeError): - BytesLe(Bytes("base16", "0xFF"), Int(2)) + with pytest.raises(pt.TealTypeError): + pt.BytesLe(pt.Bytes("base16", "0xFF"), pt.Int(2)) def test_b_gt(): args = [ - Bytes("base16", "0xFFFFFFFFFFFFFFFFFF"), - Bytes("base16", "0xFFFFFFFFFFFFFFFFF0"), + pt.Bytes("base16", "0xFFFFFFFFFFFFFFFFFF"), + pt.Bytes("base16", "0xFFFFFFFFFFFFFFFFF0"), ] - expr = BytesGt(args[0], args[1]) - assert expr.type_of() == TealType.uint64 + expr = pt.BytesGt(args[0], args[1]) + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(args[0], Op.byte, "0xFFFFFFFFFFFFFFFFFF"), - TealOp(args[1], Op.byte, "0xFFFFFFFFFFFFFFFFF0"), - TealOp(expr, Op.b_gt), + pt.TealOp(args[0], pt.Op.byte, "0xFFFFFFFFFFFFFFFFFF"), + pt.TealOp(args[1], pt.Op.byte, "0xFFFFFFFFFFFFFFFFF0"), + pt.TealOp(expr, pt.Op.b_gt), ] ) actual, _ = expr.__teal__(teal4Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): expr.__teal__(teal3Options) def test_b_gt_invalid(): - with pytest.raises(TealTypeError): - BytesGt(Int(2), Txn.receiver()) + with pytest.raises(pt.TealTypeError): + pt.BytesGt(pt.Int(2), pt.Txn.receiver()) - with pytest.raises(TealTypeError): - BytesGt(Bytes("base16", "0xFF"), Int(2)) + with pytest.raises(pt.TealTypeError): + pt.BytesGt(pt.Bytes("base16", "0xFF"), pt.Int(2)) def test_b_ge(): args = [ - Bytes("base16", "0xFFFFFFFFFFFFFFFFFF"), - Bytes("base16", "0xFFFFFFFFFFFFFFFFF0"), + pt.Bytes("base16", "0xFFFFFFFFFFFFFFFFFF"), + pt.Bytes("base16", "0xFFFFFFFFFFFFFFFFF0"), ] - expr = BytesGe(args[0], args[1]) - assert expr.type_of() == TealType.uint64 + expr = pt.BytesGe(args[0], args[1]) + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(args[0], Op.byte, "0xFFFFFFFFFFFFFFFFFF"), - TealOp(args[1], Op.byte, "0xFFFFFFFFFFFFFFFFF0"), - TealOp(expr, Op.b_ge), + pt.TealOp(args[0], pt.Op.byte, "0xFFFFFFFFFFFFFFFFFF"), + pt.TealOp(args[1], pt.Op.byte, "0xFFFFFFFFFFFFFFFFF0"), + pt.TealOp(expr, pt.Op.b_ge), ] ) actual, _ = expr.__teal__(teal4Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): expr.__teal__(teal3Options) def test_b_ge_invalid(): - with pytest.raises(TealTypeError): - BytesGe(Int(2), Txn.receiver()) + with pytest.raises(pt.TealTypeError): + pt.BytesGe(pt.Int(2), pt.Txn.receiver()) - with pytest.raises(TealTypeError): - BytesGe(Bytes("base16", "0xFF"), Int(2)) + with pytest.raises(pt.TealTypeError): + pt.BytesGe(pt.Bytes("base16", "0xFF"), pt.Int(2)) def test_extract_uint(): for expression, op in ( - (ExtractUint16, Op.extract_uint16), - (ExtractUint32, Op.extract_uint32), - (ExtractUint64, Op.extract_uint64), + (pt.ExtractUint16, pt.Op.extract_uint16), + (pt.ExtractUint32, pt.Op.extract_uint32), + (pt.ExtractUint64, pt.Op.extract_uint64), ): args = [ - Bytes("base16", "0xFFFFFFFFFFFFFFFFFF"), - Int(2), + pt.Bytes("base16", "0xFFFFFFFFFFFFFFFFFF"), + pt.Int(2), ] expr = expression(args[0], args[1]) - assert expr.type_of() == TealType.uint64 + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(args[0], Op.byte, "0xFFFFFFFFFFFFFFFFFF"), - TealOp(args[1], Op.int, 2), - TealOp(expr, op), + pt.TealOp(args[0], pt.Op.byte, "0xFFFFFFFFFFFFFFFFFF"), + pt.TealOp(args[1], pt.Op.int, 2), + pt.TealOp(expr, op), ] ) actual, _ = expr.__teal__(teal5Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): expr.__teal__(teal4Options) def test_extract_uint_invalid(): - for expression in (ExtractUint16, ExtractUint32, ExtractUint64): - with pytest.raises(TealTypeError): - expression(Int(2), Txn.receiver()) + for expression in (pt.ExtractUint16, pt.ExtractUint32, pt.ExtractUint64): + with pytest.raises(pt.TealTypeError): + expression(pt.Int(2), pt.Txn.receiver()) - with pytest.raises(TealTypeError): - expression(Bytes("base16", "0xFF"), Txn.receiver()) + with pytest.raises(pt.TealTypeError): + expression(pt.Bytes("base16", "0xFF"), pt.Txn.receiver()) diff --git a/pyteal/ast/break_.py b/pyteal/ast/break_.py index 827c77bdd..80b2e4590 100644 --- a/pyteal/ast/break_.py +++ b/pyteal/ast/break_.py @@ -1,13 +1,13 @@ from typing import TYPE_CHECKING -from ..types import TealType -from ..errors import TealCompileError -from .expr import Expr -from ..ir import TealSimpleBlock +from pyteal.types import TealType +from pyteal.errors import TealCompileError +from pyteal.ast.expr import Expr +from pyteal.ir import TealSimpleBlock if TYPE_CHECKING: - from ..compiler import CompileOptions + from pyteal.compiler import CompileOptions class Break(Expr): diff --git a/pyteal/ast/break_test.py b/pyteal/ast/break_test.py index e99568266..1e6684692 100644 --- a/pyteal/ast/break_test.py +++ b/pyteal/ast/break_test.py @@ -1,36 +1,33 @@ import pytest -from .. import * +import pyteal as pt -# this is not necessary but mypy complains if it's not included -from .. import CompileOptions - -options = CompileOptions() +options = pt.CompileOptions() def test_break_fail(): - with pytest.raises(TealCompileError): - Break().__teal__(options) + with pytest.raises(pt.TealCompileError): + pt.Break().__teal__(options) - with pytest.raises(TealCompileError): - If(Int(1), Break()).__teal__(options) + with pytest.raises(pt.TealCompileError): + pt.If(pt.Int(1), pt.Break()).__teal__(options) - with pytest.raises(TealCompileError): - Seq([Break()]).__teal__(options) + with pytest.raises(pt.TealCompileError): + pt.Seq([pt.Break()]).__teal__(options) with pytest.raises(TypeError): - Break(Int(1)) + pt.Break(pt.Int(1)) def test_break(): - expr = Break() + expr = pt.Break() - assert expr.type_of() == TealType.none + assert expr.type_of() == pt.TealType.none assert not expr.has_return() - expected = TealSimpleBlock([]) + expected = pt.TealSimpleBlock([]) options.enterLoop() actual, _ = expr.__teal__(options) diff --git a/pyteal/ast/bytes.py b/pyteal/ast/bytes.py index 1bb58b4dc..e88163611 100644 --- a/pyteal/ast/bytes.py +++ b/pyteal/ast/bytes.py @@ -1,13 +1,13 @@ from typing import Union, cast, overload, TYPE_CHECKING -from ..types import TealType, valid_base16, valid_base32, valid_base64 -from ..util import escapeStr -from ..ir import TealOp, Op, TealBlock -from ..errors import TealInputError -from .leafexpr import LeafExpr +from pyteal.types import TealType, valid_base16, valid_base32, valid_base64 +from pyteal.util import escapeStr +from pyteal.ir import TealOp, Op, TealBlock +from pyteal.errors import TealInputError +from pyteal.ast.leafexpr import LeafExpr if TYPE_CHECKING: - from ..compiler import CompileOptions + from pyteal.compiler import CompileOptions class Bytes(LeafExpr): diff --git a/pyteal/ast/bytes_test.py b/pyteal/ast/bytes_test.py index 3f0bb49f4..2ad5382a5 100644 --- a/pyteal/ast/bytes_test.py +++ b/pyteal/ast/bytes_test.py @@ -1,11 +1,8 @@ import pytest -from .. import * +import pyteal as pt -# this is not necessary but mypy complains if it's not included -from .. import CompileOptions - -options = CompileOptions() +options = pt.CompileOptions() def test_bytes_base32_no_padding(): @@ -17,9 +14,11 @@ def test_bytes_base32_no_padding(): "MFRGGZDF", "7Z5PWO2C6LFNQFGHWKSK5H47IQP5OJW2M3HA2QPXTY3WTNP5NU2MHBW27M", ): - expr = Bytes("base32", s) - assert expr.type_of() == TealType.bytes - expected = TealSimpleBlock([TealOp(expr, Op.byte, "base32(" + s + ")")]) + expr = pt.Bytes("base32", s) + assert expr.type_of() == pt.TealType.bytes + expected = pt.TealSimpleBlock( + [pt.TealOp(expr, pt.Op.byte, "base32(" + s + ")")] + ) actual, _ = expr.__teal__(options) assert actual == expected @@ -32,141 +31,147 @@ def test_bytes_base32_padding(): "MFRGGZA=", "7Z5PWO2C6LFNQFGHWKSK5H47IQP5OJW2M3HA2QPXTY3WTNP5NU2MHBW27M======", ): - expr = Bytes("base32", s) - assert expr.type_of() == TealType.bytes - expected = TealSimpleBlock([TealOp(expr, Op.byte, "base32(" + s + ")")]) + expr = pt.Bytes("base32", s) + assert expr.type_of() == pt.TealType.bytes + expected = pt.TealSimpleBlock( + [pt.TealOp(expr, pt.Op.byte, "base32(" + s + ")")] + ) actual, _ = expr.__teal__(options) assert actual == expected def test_bytes_base32_empty(): - expr = Bytes("base32", "") - assert expr.type_of() == TealType.bytes - expected = TealSimpleBlock([TealOp(expr, Op.byte, "base32()")]) + expr = pt.Bytes("base32", "") + assert expr.type_of() == pt.TealType.bytes + expected = pt.TealSimpleBlock([pt.TealOp(expr, pt.Op.byte, "base32()")]) actual, _ = expr.__teal__(options) assert actual == expected def test_bytes_base64(): - expr = Bytes("base64", "Zm9vYmE=") - assert expr.type_of() == TealType.bytes - expected = TealSimpleBlock([TealOp(expr, Op.byte, "base64(Zm9vYmE=)")]) + expr = pt.Bytes("base64", "Zm9vYmE=") + assert expr.type_of() == pt.TealType.bytes + expected = pt.TealSimpleBlock([pt.TealOp(expr, pt.Op.byte, "base64(Zm9vYmE=)")]) actual, _ = expr.__teal__(options) assert actual == expected def test_bytes_base64_empty(): - expr = Bytes("base64", "") - assert expr.type_of() == TealType.bytes - expected = TealSimpleBlock([TealOp(expr, Op.byte, "base64()")]) + expr = pt.Bytes("base64", "") + assert expr.type_of() == pt.TealType.bytes + expected = pt.TealSimpleBlock([pt.TealOp(expr, pt.Op.byte, "base64()")]) actual, _ = expr.__teal__(options) assert actual == expected def test_bytes_base16(): - expr = Bytes("base16", "A21212EF") - assert expr.type_of() == TealType.bytes - expected = TealSimpleBlock([TealOp(expr, Op.byte, "0xA21212EF")]) + expr = pt.Bytes("base16", "A21212EF") + assert expr.type_of() == pt.TealType.bytes + expected = pt.TealSimpleBlock([pt.TealOp(expr, pt.Op.byte, "0xA21212EF")]) actual, _ = expr.__teal__(options) assert actual == expected def test_bytes_base16_prefix(): - expr = Bytes("base16", "0xA21212EF") - assert expr.type_of() == TealType.bytes - expected = TealSimpleBlock([TealOp(expr, Op.byte, "0xA21212EF")]) + expr = pt.Bytes("base16", "0xA21212EF") + assert expr.type_of() == pt.TealType.bytes + expected = pt.TealSimpleBlock([pt.TealOp(expr, pt.Op.byte, "0xA21212EF")]) actual, _ = expr.__teal__(options) assert actual == expected def test_bytes_base16_empty(): - expr = Bytes("base16", "") - assert expr.type_of() == TealType.bytes - expected = TealSimpleBlock([TealOp(expr, Op.byte, "0x")]) + expr = pt.Bytes("base16", "") + assert expr.type_of() == pt.TealType.bytes + expected = pt.TealSimpleBlock([pt.TealOp(expr, pt.Op.byte, "0x")]) actual, _ = expr.__teal__(options) assert actual == expected def test_bytes_utf8(): - expr = Bytes("hello world") - assert expr.type_of() == TealType.bytes - expected = TealSimpleBlock([TealOp(expr, Op.byte, '"hello world"')]) + expr = pt.Bytes("hello world") + assert expr.type_of() == pt.TealType.bytes + expected = pt.TealSimpleBlock([pt.TealOp(expr, pt.Op.byte, '"hello world"')]) actual, _ = expr.__teal__(options) assert actual == expected def test_bytes_utf8_special_chars(): - expr = Bytes("\t \n \r\n \\ \" ' 😀") - assert expr.type_of() == TealType.bytes - expected = TealSimpleBlock( - [TealOp(expr, Op.byte, '"\\t \\n \\r\\n \\\\ \\" \' \\xf0\\x9f\\x98\\x80"')] + expr = pt.Bytes("\t \n \r\n \\ \" ' 😀") + assert expr.type_of() == pt.TealType.bytes + expected = pt.TealSimpleBlock( + [ + pt.TealOp( + expr, pt.Op.byte, '"\\t \\n \\r\\n \\\\ \\" \' \\xf0\\x9f\\x98\\x80"' + ) + ] ) actual, _ = expr.__teal__(options) assert actual == expected def test_bytes_utf8_empty(): - expr = Bytes("") - assert expr.type_of() == TealType.bytes - expected = TealSimpleBlock([TealOp(expr, Op.byte, '""')]) + expr = pt.Bytes("") + assert expr.type_of() == pt.TealType.bytes + expected = pt.TealSimpleBlock([pt.TealOp(expr, pt.Op.byte, '""')]) actual, _ = expr.__teal__(options) assert actual == expected def test_bytes_raw(): for value in (b"hello world", bytearray(b"hello world")): - expr = Bytes(value) - assert expr.type_of() == TealType.bytes - expected = TealSimpleBlock([TealOp(expr, Op.byte, "0x" + value.hex())]) + expr = pt.Bytes(value) + assert expr.type_of() == pt.TealType.bytes + expected = pt.TealSimpleBlock([pt.TealOp(expr, pt.Op.byte, "0x" + value.hex())]) actual, _ = expr.__teal__(options) assert actual == expected def test_bytes_raw_empty(): for value in (b"", bytearray(b"")): - expr = Bytes(value) - assert expr.type_of() == TealType.bytes - expected = TealSimpleBlock([TealOp(expr, Op.byte, "0x")]) + expr = pt.Bytes(value) + assert expr.type_of() == pt.TealType.bytes + expected = pt.TealSimpleBlock([pt.TealOp(expr, pt.Op.byte, "0x")]) actual, _ = expr.__teal__(options) assert actual == expected def test_bytes_invalid(): - with pytest.raises(TealInputError): - Bytes("base16", b"FF") + with pytest.raises(pt.TealInputError): + pt.Bytes("base16", b"FF") - with pytest.raises(TealInputError): - Bytes(b"base16", "FF") + with pytest.raises(pt.TealInputError): + pt.Bytes(b"base16", "FF") - with pytest.raises(TealInputError): - Bytes("base23", "") + with pytest.raises(pt.TealInputError): + pt.Bytes("base23", "") - with pytest.raises(TealInputError): - Bytes("base32", "Zm9vYmE=") + with pytest.raises(pt.TealInputError): + pt.Bytes("base32", "Zm9vYmE=") - with pytest.raises(TealInputError): - Bytes("base32", "MFRGG====") + with pytest.raises(pt.TealInputError): + pt.Bytes("base32", "MFRGG====") - with pytest.raises(TealInputError): - Bytes("base32", "MFRGG==") + with pytest.raises(pt.TealInputError): + pt.Bytes("base32", "MFRGG==") - with pytest.raises(TealInputError): - Bytes("base32", "CCCCCC==") + with pytest.raises(pt.TealInputError): + pt.Bytes("base32", "CCCCCC==") - with pytest.raises(TealInputError): - Bytes("base32", "CCCCCC") + with pytest.raises(pt.TealInputError): + pt.Bytes("base32", "CCCCCC") - with pytest.raises(TealInputError): - Bytes("base32", "C=======") + with pytest.raises(pt.TealInputError): + pt.Bytes("base32", "C=======") - with pytest.raises(TealInputError): - Bytes("base32", "C") + with pytest.raises(pt.TealInputError): + pt.Bytes("base32", "C") - with pytest.raises(TealInputError): - Bytes("base32", "=") + with pytest.raises(pt.TealInputError): + pt.Bytes("base32", "=") - with pytest.raises(TealInputError): - Bytes("base64", "?????") + with pytest.raises(pt.TealInputError): + pt.Bytes("base64", "?????") - with pytest.raises(TealInputError): - Bytes("base16", "7Z5PWO2C6LFNQFGHWKSK5H47IQP5OJW2M3HA2QPXTY3WTNP5NU2MHBW27M") + with pytest.raises(pt.TealInputError): + pt.Bytes("base16", "7Z5PWO2C6LFNQFGHWKSK5H47IQP5OJW2M3HA2QPXTY3WTNP5NU2MHBW27M") diff --git a/pyteal/ast/cond.py b/pyteal/ast/cond.py index 348921b61..8dbe92cab 100644 --- a/pyteal/ast/cond.py +++ b/pyteal/ast/cond.py @@ -1,14 +1,12 @@ from typing import List, cast, TYPE_CHECKING -from ..types import TealType, require_type -from ..ir import TealOp, Op, TealSimpleBlock, TealConditionalBlock -from ..errors import TealInputError -from .expr import Expr -from .err import Err -from .if_ import If +from pyteal.types import TealType, require_type +from pyteal.ir import TealOp, Op, TealSimpleBlock, TealConditionalBlock +from pyteal.errors import TealInputError +from pyteal.ast.expr import Expr if TYPE_CHECKING: - from ..compiler import CompileOptions + from pyteal.compiler import CompileOptions class Cond(Expr): diff --git a/pyteal/ast/cond_test.py b/pyteal/ast/cond_test.py index 419030e39..9a8f8f8c1 100644 --- a/pyteal/ast/cond_test.py +++ b/pyteal/ast/cond_test.py @@ -1,43 +1,40 @@ import pytest -from .. import * +import pyteal as pt -# this is not necessary but mypy complains if it's not included -from .. import CompileOptions - -options = CompileOptions() +options = pt.CompileOptions() def test_cond_one_pred(): - expr = Cond([Int(1), Int(2)]) - assert expr.type_of() == TealType.uint64 + expr = pt.Cond([pt.Int(1), pt.Int(2)]) + assert expr.type_of() == pt.TealType.uint64 - cond1, _ = Int(1).__teal__(options) - pred1, _ = Int(2).__teal__(options) - cond1Branch = TealConditionalBlock([]) + cond1, _ = pt.Int(1).__teal__(options) + pred1, _ = pt.Int(2).__teal__(options) + cond1Branch = pt.TealConditionalBlock([]) cond1.setNextBlock(cond1Branch) cond1Branch.setTrueBlock(pred1) - cond1Branch.setFalseBlock(Err().__teal__(options)[0]) - pred1.setNextBlock(TealSimpleBlock([])) + cond1Branch.setFalseBlock(pt.Err().__teal__(options)[0]) + pred1.setNextBlock(pt.TealSimpleBlock([])) expected = cond1 actual, _ = expr.__teal__(options) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected def test_cond_two_pred(): - expr = Cond([Int(1), Bytes("one")], [Int(0), Bytes("zero")]) - assert expr.type_of() == TealType.bytes + expr = pt.Cond([pt.Int(1), pt.Bytes("one")], [pt.Int(0), pt.Bytes("zero")]) + assert expr.type_of() == pt.TealType.bytes - cond1, _ = Int(1).__teal__(options) - pred1, _ = Bytes("one").__teal__(options) - cond1Branch = TealConditionalBlock([]) - cond2, _ = Int(0).__teal__(options) - pred2, _ = Bytes("zero").__teal__(options) - cond2Branch = TealConditionalBlock([]) - end = TealSimpleBlock([]) + cond1, _ = pt.Int(1).__teal__(options) + pred1, _ = pt.Bytes("one").__teal__(options) + cond1Branch = pt.TealConditionalBlock([]) + cond2, _ = pt.Int(0).__teal__(options) + pred2, _ = pt.Bytes("zero").__teal__(options) + cond2Branch = pt.TealConditionalBlock([]) + end = pt.TealSimpleBlock([]) cond1.setNextBlock(cond1Branch) cond1Branch.setTrueBlock(pred1) @@ -46,31 +43,33 @@ def test_cond_two_pred(): cond2.setNextBlock(cond2Branch) cond2Branch.setTrueBlock(pred2) - cond2Branch.setFalseBlock(Err().__teal__(options)[0]) + cond2Branch.setFalseBlock(pt.Err().__teal__(options)[0]) pred2.setNextBlock(end) expected = cond1 actual, _ = expr.__teal__(options) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected def test_cond_three_pred(): - expr = Cond([Int(1), Int(2)], [Int(3), Int(4)], [Int(5), Int(6)]) - assert expr.type_of() == TealType.uint64 - - cond1, _ = Int(1).__teal__(options) - pred1, _ = Int(2).__teal__(options) - cond1Branch = TealConditionalBlock([]) - cond2, _ = Int(3).__teal__(options) - pred2, _ = Int(4).__teal__(options) - cond2Branch = TealConditionalBlock([]) - cond3, _ = Int(5).__teal__(options) - pred3, _ = Int(6).__teal__(options) - cond3Branch = TealConditionalBlock([]) - end = TealSimpleBlock([]) + expr = pt.Cond( + [pt.Int(1), pt.Int(2)], [pt.Int(3), pt.Int(4)], [pt.Int(5), pt.Int(6)] + ) + assert expr.type_of() == pt.TealType.uint64 + + cond1, _ = pt.Int(1).__teal__(options) + pred1, _ = pt.Int(2).__teal__(options) + cond1Branch = pt.TealConditionalBlock([]) + cond2, _ = pt.Int(3).__teal__(options) + pred2, _ = pt.Int(4).__teal__(options) + cond2Branch = pt.TealConditionalBlock([]) + cond3, _ = pt.Int(5).__teal__(options) + pred3, _ = pt.Int(6).__teal__(options) + cond3Branch = pt.TealConditionalBlock([]) + end = pt.TealSimpleBlock([]) cond1.setNextBlock(cond1Branch) cond1Branch.setTrueBlock(pred1) @@ -84,39 +83,44 @@ def test_cond_three_pred(): cond3.setNextBlock(cond3Branch) cond3Branch.setTrueBlock(pred3) - cond3Branch.setFalseBlock(Err().__teal__(options)[0]) + cond3Branch.setFalseBlock(pt.Err().__teal__(options)[0]) pred3.setNextBlock(end) expected = cond1 actual, _ = expr.__teal__(options) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected def test_cond_has_return(): - exprWithReturn = Cond([Int(1), Return(Int(1))], [Int(0), Return(Int(0))]) + exprWithReturn = pt.Cond( + [pt.Int(1), pt.Return(pt.Int(1))], [pt.Int(0), pt.Return(pt.Int(0))] + ) assert exprWithReturn.has_return() - exprWithoutReturn = Cond([Int(1), Bytes("one")], [Int(0), Bytes("zero")]) + exprWithoutReturn = pt.Cond( + [pt.Int(1), pt.Bytes("one")], [pt.Int(0), pt.Bytes("zero")] + ) assert not exprWithoutReturn.has_return() - exprSemiReturn = Cond( - [Int(1), Return(Int(1))], [Int(0), App.globalPut(Bytes("key"), Bytes("value"))] + exprSemiReturn = pt.Cond( + [pt.Int(1), pt.Return(pt.Int(1))], + [pt.Int(0), pt.App.globalPut(pt.Bytes("key"), pt.Bytes("value"))], ) assert not exprSemiReturn.has_return() def test_cond_invalid(): - with pytest.raises(TealInputError): - Cond() + with pytest.raises(pt.TealInputError): + pt.Cond() - with pytest.raises(TealInputError): - Cond([]) + with pytest.raises(pt.TealInputError): + pt.Cond([]) - with pytest.raises(TealTypeError): - Cond([Int(1), Int(2)], [Int(2), Txn.receiver()]) + with pytest.raises(pt.TealTypeError): + pt.Cond([pt.Int(1), pt.Int(2)], [pt.Int(2), pt.Txn.receiver()]) - with pytest.raises(TealTypeError): - Cond([Arg(0), Int(2)]) + with pytest.raises(pt.TealTypeError): + pt.Cond([pt.Arg(0), pt.Int(2)]) diff --git a/pyteal/ast/continue_.py b/pyteal/ast/continue_.py index ddfef43bf..a134925bd 100644 --- a/pyteal/ast/continue_.py +++ b/pyteal/ast/continue_.py @@ -1,13 +1,13 @@ from typing import TYPE_CHECKING -from ..types import TealType -from ..errors import TealCompileError -from .expr import Expr -from ..ir import TealSimpleBlock +from pyteal.types import TealType +from pyteal.errors import TealCompileError +from pyteal.ast.expr import Expr +from pyteal.ir import TealSimpleBlock if TYPE_CHECKING: - from ..compiler import CompileOptions + from pyteal.compiler import CompileOptions class Continue(Expr): diff --git a/pyteal/ast/continue_test.py b/pyteal/ast/continue_test.py index 30f4ccb08..3a310934c 100644 --- a/pyteal/ast/continue_test.py +++ b/pyteal/ast/continue_test.py @@ -1,35 +1,32 @@ import pytest -from .. import * +import pyteal as pt -# this is not necessary but mypy complains if it's not included -from .. import CompileOptions - -options = CompileOptions() +options = pt.CompileOptions() def test_continue_fail(): - with pytest.raises(TealCompileError): - Continue().__teal__(options) + with pytest.raises(pt.TealCompileError): + pt.Continue().__teal__(options) - with pytest.raises(TealCompileError): - If(Int(1), Continue()).__teal__(options) + with pytest.raises(pt.TealCompileError): + pt.If(pt.Int(1), pt.Continue()).__teal__(options) - with pytest.raises(TealCompileError): - Seq([Continue()]).__teal__(options) + with pytest.raises(pt.TealCompileError): + pt.Seq([pt.Continue()]).__teal__(options) with pytest.raises(TypeError): - Continue(Int(1)) + pt.Continue(pt.Int(1)) def test_continue(): - expr = Continue() + expr = pt.Continue() - assert expr.type_of() == TealType.none + assert expr.type_of() == pt.TealType.none assert not expr.has_return() - expected = TealSimpleBlock([]) + expected = pt.TealSimpleBlock([]) options.enterLoop() actual, _ = expr.__teal__(options) diff --git a/pyteal/ast/err.py b/pyteal/ast/err.py index aea616863..1b4df9bb8 100644 --- a/pyteal/ast/err.py +++ b/pyteal/ast/err.py @@ -1,11 +1,11 @@ from typing import TYPE_CHECKING -from ..types import TealType -from ..ir import TealOp, Op, TealBlock -from .expr import Expr +from pyteal.types import TealType +from pyteal.ir import TealOp, Op, TealBlock +from pyteal.ast.expr import Expr if TYPE_CHECKING: - from ..compiler import CompileOptions + from pyteal.compiler import CompileOptions class Err(Expr): diff --git a/pyteal/ast/err_test.py b/pyteal/ast/err_test.py index aec4e51ca..7e95383df 100644 --- a/pyteal/ast/err_test.py +++ b/pyteal/ast/err_test.py @@ -1,12 +1,10 @@ -import pytest - -from .. import * +import pyteal as pt def test_err(): - expr = Err() - assert expr.type_of() == TealType.none + expr = pt.Err() + assert expr.type_of() == pt.TealType.none assert expr.has_return() - expected = TealSimpleBlock([TealOp(expr, Op.err)]) - actual, _ = expr.__teal__(CompileOptions()) + expected = pt.TealSimpleBlock([pt.TealOp(expr, pt.Op.err)]) + actual, _ = expr.__teal__(pt.CompileOptions()) assert actual == expected diff --git a/pyteal/ast/expr.py b/pyteal/ast/expr.py index 43a8da06d..c2de1ca32 100644 --- a/pyteal/ast/expr.py +++ b/pyteal/ast/expr.py @@ -1,11 +1,11 @@ from abc import ABC, abstractmethod from typing import Tuple, List, TYPE_CHECKING -from ..types import TealType -from ..ir import TealBlock, TealSimpleBlock +from pyteal.types import TealType +from pyteal.ir import TealBlock, TealSimpleBlock if TYPE_CHECKING: - from ..compiler import CompileOptions + from pyteal.compiler import CompileOptions class Expr(ABC): @@ -40,92 +40,92 @@ def __teal__(self, options: "CompileOptions") -> Tuple[TealBlock, TealSimpleBloc pass def __lt__(self, other): - from .binaryexpr import Lt + from pyteal.ast.binaryexpr import Lt return Lt(self, other) def __gt__(self, other): - from .binaryexpr import Gt + from pyteal.ast.binaryexpr import Gt return Gt(self, other) def __le__(self, other): - from .binaryexpr import Le + from pyteal.ast.binaryexpr import Le return Le(self, other) def __ge__(self, other): - from .binaryexpr import Ge + from pyteal.ast.binaryexpr import Ge return Ge(self, other) def __eq__(self, other): - from .binaryexpr import Eq + from pyteal.ast.binaryexpr import Eq return Eq(self, other) def __ne__(self, other): - from .binaryexpr import Neq + from pyteal.ast.binaryexpr import Neq return Neq(self, other) def __add__(self, other): - from .naryexpr import Add + from pyteal.ast.naryexpr import Add return Add(self, other) def __sub__(self, other): - from .binaryexpr import Minus + from pyteal.ast.binaryexpr import Minus return Minus(self, other) def __mul__(self, other): - from .naryexpr import Mul + from pyteal.ast.naryexpr import Mul return Mul(self, other) def __truediv__(self, other): - from .binaryexpr import Div + from pyteal.ast.binaryexpr import Div return Div(self, other) def __mod__(self, other): - from .binaryexpr import Mod + from pyteal.ast.binaryexpr import Mod return Mod(self, other) def __pow__(self, other): - from .binaryexpr import Exp + from pyteal.ast.binaryexpr import Exp return Exp(self, other) def __invert__(self): - from .unaryexpr import BitwiseNot + from pyteal.ast.unaryexpr import BitwiseNot return BitwiseNot(self) def __and__(self, other): - from .binaryexpr import BitwiseAnd + from pyteal.ast.binaryexpr import BitwiseAnd return BitwiseAnd(self, other) def __or__(self, other): - from .binaryexpr import BitwiseOr + from pyteal.ast.binaryexpr import BitwiseOr return BitwiseOr(self, other) def __xor__(self, other): - from .binaryexpr import BitwiseXor + from pyteal.ast.binaryexpr import BitwiseXor return BitwiseXor(self, other) def __lshift__(self, other): - from .binaryexpr import ShiftLeft + from pyteal.ast.binaryexpr import ShiftLeft return ShiftLeft(self, other) def __rshift__(self, other): - from .binaryexpr import ShiftRight + from pyteal.ast.binaryexpr import ShiftRight return ShiftRight(self, other) @@ -136,7 +136,7 @@ def And(self, other: "Expr") -> "Expr": This is the same as using :func:`And()` with two arguments. """ - from .naryexpr import And + from pyteal.ast.naryexpr import And return And(self, other) @@ -147,7 +147,7 @@ def Or(self, other: "Expr") -> "Expr": This is the same as using :func:`Or()` with two arguments. """ - from .naryexpr import Or + from pyteal.ast.naryexpr import Or return Or(self, other) diff --git a/pyteal/ast/for_.py b/pyteal/ast/for_.py index 137d71725..d8a0cca27 100644 --- a/pyteal/ast/for_.py +++ b/pyteal/ast/for_.py @@ -1,14 +1,12 @@ from typing import TYPE_CHECKING, Optional -from ..types import TealType, require_type -from ..ir import TealSimpleBlock, TealConditionalBlock -from ..errors import TealCompileError -from .expr import Expr -from .seq import Seq -from .int import Int +from pyteal.types import TealType, require_type +from pyteal.ir import TealSimpleBlock, TealConditionalBlock +from pyteal.errors import TealCompileError +from pyteal.ast.expr import Expr if TYPE_CHECKING: - from ..compiler import CompileOptions + from pyteal.compiler import CompileOptions class For(Expr): diff --git a/pyteal/ast/for_test.py b/pyteal/ast/for_test.py index a5ccfbb2f..87151a374 100644 --- a/pyteal/ast/for_test.py +++ b/pyteal/ast/for_test.py @@ -1,68 +1,65 @@ import pytest -from .. import * +import pyteal as pt -# this is not necessary but mypy complains if it's not included -from .. import CompileOptions - -options = CompileOptions() +options = pt.CompileOptions() def test_for_compiles(): - i = ScratchVar() + i = pt.ScratchVar() - expr = For(i.store(Int(0)), Int(1), i.store(i.load() + Int(1))).Do( - App.globalPut(Itob(Int(0)), Itob(Int(2))) + expr = pt.For(i.store(pt.Int(0)), pt.Int(1), i.store(i.load() + pt.Int(1))).Do( + pt.App.globalPut(pt.Itob(pt.Int(0)), pt.Itob(pt.Int(2))) ) - assert expr.type_of() == TealType.none + assert expr.type_of() == pt.TealType.none assert not expr.has_return() expr.__teal__(options) def test_nested_for_compiles(): - i = ScratchVar() - expr = For(i.store(Int(0)), Int(1), i.store(i.load() + Int(1))).Do( - Seq( + i = pt.ScratchVar() + expr = pt.For(i.store(pt.Int(0)), pt.Int(1), i.store(i.load() + pt.Int(1))).Do( + pt.Seq( [ - For(i.store(Int(0)), Int(1), i.store(i.load() + Int(1))).Do( - Seq([i.store(Int(0))]) + pt.For(i.store(pt.Int(0)), pt.Int(1), i.store(i.load() + pt.Int(1))).Do( + pt.Seq([i.store(pt.Int(0))]) ) ] ) ) - assert expr.type_of() == TealType.none + assert expr.type_of() == pt.TealType.none assert not expr.has_return() def test_continue_break(): - i = ScratchVar() - expr = For(i.store(Int(0)), Int(1), i.store(i.load() + Int(1))).Do( - Seq([If(Int(1), Break(), Continue())]) + i = pt.ScratchVar() + expr = pt.For(i.store(pt.Int(0)), pt.Int(1), i.store(i.load() + pt.Int(1))).Do( + pt.Seq([pt.If(pt.Int(1), pt.Break(), pt.Continue())]) ) - assert expr.type_of() == TealType.none + assert expr.type_of() == pt.TealType.none assert not expr.has_return() expr.__teal__(options) def test_for(): - i = ScratchVar() + i = pt.ScratchVar() items = [ - (i.store(Int(0))), - i.load() < Int(10), - i.store(i.load() + Int(1)), - App.globalPut(Itob(i.load()), i.load() * Int(2)), + (i.store(pt.Int(0))), + i.load() < pt.Int(10), + i.store(i.load() + pt.Int(1)), + pt.App.globalPut(pt.Itob(i.load()), i.load() * pt.Int(2)), ] - expr = For(items[0], items[1], items[2]).Do(Seq([items[3]])) + expr = pt.For(items[0], items[1], items[2]).Do(pt.Seq([items[3]])) - assert expr.type_of() == TealType.none + assert expr.type_of() == pt.TealType.none assert not expr.has_return() expected, varEnd = items[0].__teal__(options) condStart, condEnd = items[1].__teal__(options) stepStart, stepEnd = items[2].__teal__(options) - do, doEnd = Seq([items[3]]).__teal__(options) - expectedBranch = TealConditionalBlock([]) - end = TealSimpleBlock([]) + do, doEnd = pt.Seq([items[3]]).__teal__(options) + expectedBranch = pt.TealConditionalBlock([]) + end = pt.TealSimpleBlock([]) varEnd.setNextBlock(condStart) doEnd.setNextBlock(stepStart) @@ -78,17 +75,17 @@ def test_for(): def test_for_continue(): - i = ScratchVar() + i = pt.ScratchVar() items = [ - (i.store(Int(0))), - i.load() < Int(10), - i.store(i.load() + Int(1)), - If(i.load() < Int(4), Continue()), - App.globalPut(Itob(i.load()), i.load() * Int(2)), + (i.store(pt.Int(0))), + i.load() < pt.Int(10), + i.store(i.load() + pt.Int(1)), + pt.If(i.load() < pt.Int(4), pt.Continue()), + pt.App.globalPut(pt.Itob(i.load()), i.load() * pt.Int(2)), ] - expr = For(items[0], items[1], items[2]).Do(Seq([items[3], items[4]])) + expr = pt.For(items[0], items[1], items[2]).Do(pt.Seq([items[3], items[4]])) - assert expr.type_of() == TealType.none + assert expr.type_of() == pt.TealType.none assert not expr.has_return() options.enterLoop() @@ -96,9 +93,9 @@ def test_for_continue(): expected, varEnd = items[0].__teal__(options) condStart, condEnd = items[1].__teal__(options) stepStart, stepEnd = items[2].__teal__(options) - do, doEnd = Seq([items[3], items[4]]).__teal__(options) - expectedBranch = TealConditionalBlock([]) - end = TealSimpleBlock([]) + do, doEnd = pt.Seq([items[3], items[4]]).__teal__(options) + expectedBranch = pt.TealConditionalBlock([]) + end = pt.TealSimpleBlock([]) doEnd.setNextBlock(stepStart) stepEnd.setNextBlock(condStart) @@ -119,17 +116,17 @@ def test_for_continue(): def test_for_break(): - i = ScratchVar() + i = pt.ScratchVar() items = [ - (i.store(Int(0))), - i.load() < Int(10), - i.store(i.load() + Int(1)), - If(i.load() == Int(6), Break()), - App.globalPut(Itob(i.load()), i.load() * Int(2)), + (i.store(pt.Int(0))), + i.load() < pt.Int(10), + i.store(i.load() + pt.Int(1)), + pt.If(i.load() == pt.Int(6), pt.Break()), + pt.App.globalPut(pt.Itob(i.load()), i.load() * pt.Int(2)), ] - expr = For(items[0], items[1], items[2]).Do(Seq([items[3], items[4]])) + expr = pt.For(items[0], items[1], items[2]).Do(pt.Seq([items[3], items[4]])) - assert expr.type_of() == TealType.none + assert expr.type_of() == pt.TealType.none assert not expr.has_return() options.enterLoop() @@ -137,9 +134,9 @@ def test_for_break(): expected, varEnd = items[0].__teal__(options) condStart, condEnd = items[1].__teal__(options) stepStart, stepEnd = items[2].__teal__(options) - do, doEnd = Seq([items[3], items[4]]).__teal__(options) - expectedBranch = TealConditionalBlock([]) - end = TealSimpleBlock([]) + do, doEnd = pt.Seq([items[3], items[4]]).__teal__(options) + expectedBranch = pt.TealConditionalBlock([]) + end = pt.TealSimpleBlock([]) doEnd.setNextBlock(stepStart) stepEnd.setNextBlock(condStart) @@ -161,37 +158,39 @@ def test_for_break(): def test_invalid_for(): with pytest.raises(TypeError): - expr = For() + expr = pt.For() with pytest.raises(TypeError): - expr = For(Int(2)) + expr = pt.For(pt.Int(2)) with pytest.raises(TypeError): - expr = For(Int(1), Int(2)) + expr = pt.For(pt.Int(1), pt.Int(2)) - with pytest.raises(TealTypeError): - i = ScratchVar() - expr = For(i.store(Int(0)), Int(1), Int(2)) + with pytest.raises(pt.TealTypeError): + i = pt.ScratchVar() + expr = pt.For(i.store(pt.Int(0)), pt.Int(1), pt.Int(2)) expr.__teal__(options) - with pytest.raises(TealCompileError): - i = ScratchVar() - expr = For(i.store(Int(0)), Int(1), i.store(i.load() + Int(1))) + with pytest.raises(pt.TealCompileError): + i = pt.ScratchVar() + expr = pt.For(i.store(pt.Int(0)), pt.Int(1), i.store(i.load() + pt.Int(1))) expr.type_of() - with pytest.raises(TealCompileError): - i = ScratchVar() - expr = For(i.store(Int(0)), Int(1), i.store(i.load() + Int(1))) + with pytest.raises(pt.TealCompileError): + i = pt.ScratchVar() + expr = pt.For(i.store(pt.Int(0)), pt.Int(1), i.store(i.load() + pt.Int(1))) expr.__str__() - with pytest.raises(TealTypeError): - i = ScratchVar() - expr = For(i.store(Int(0)), Int(1), i.store(i.load() + Int(1))).Do(Int(0)) + with pytest.raises(pt.TealTypeError): + i = pt.ScratchVar() + expr = pt.For(i.store(pt.Int(0)), pt.Int(1), i.store(i.load() + pt.Int(1))).Do( + pt.Int(0) + ) - with pytest.raises(TealCompileError): + with pytest.raises(pt.TealCompileError): expr = ( - For(i.store(Int(0)), Int(1), i.store(i.load() + Int(1))) - .Do(Continue()) - .Do(Continue()) + pt.For(i.store(pt.Int(0)), pt.Int(1), i.store(i.load() + pt.Int(1))) + .Do(pt.Continue()) + .Do(pt.Continue()) ) expr.__str__() diff --git a/pyteal/ast/gaid.py b/pyteal/ast/gaid.py index 7b95f1617..d9faa6128 100644 --- a/pyteal/ast/gaid.py +++ b/pyteal/ast/gaid.py @@ -1,14 +1,14 @@ from typing import cast, Union, TYPE_CHECKING -from ..types import TealType, require_type -from ..ir import TealOp, Op, TealBlock -from ..errors import TealInputError, verifyTealVersion -from ..config import MAX_GROUP_SIZE -from .expr import Expr -from .leafexpr import LeafExpr +from pyteal.types import TealType, require_type +from pyteal.ir import TealOp, Op, TealBlock +from pyteal.errors import TealInputError, verifyTealVersion +from pyteal.config import MAX_GROUP_SIZE +from pyteal.ast.expr import Expr +from pyteal.ast.leafexpr import LeafExpr if TYPE_CHECKING: - from ..compiler import CompileOptions + from pyteal.compiler import CompileOptions class GeneratedID(LeafExpr): diff --git a/pyteal/ast/gaid_test.py b/pyteal/ast/gaid_test.py index d42f57de2..9da4fe254 100644 --- a/pyteal/ast/gaid_test.py +++ b/pyteal/ast/gaid_test.py @@ -1,24 +1,21 @@ import pytest -from .. import * +import pyteal as pt -# this is not necessary but mypy complains if it's not included -from .. import MAX_GROUP_SIZE, CompileOptions - -teal3Options = CompileOptions(version=3) -teal4Options = CompileOptions(version=4) +teal3Options = pt.CompileOptions(version=3) +teal4Options = pt.CompileOptions(version=4) def test_gaid_teal_3(): - with pytest.raises(TealInputError): - GeneratedID(0).__teal__(teal3Options) + with pytest.raises(pt.TealInputError): + pt.GeneratedID(0).__teal__(teal3Options) def test_gaid(): - expr = GeneratedID(0) - assert expr.type_of() == TealType.uint64 + expr = pt.GeneratedID(0) + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock([TealOp(expr, Op.gaid, 0)]) + expected = pt.TealSimpleBlock([pt.TealOp(expr, pt.Op.gaid, 0)]) actual, _ = expr.__teal__(teal4Options) @@ -26,32 +23,34 @@ def test_gaid(): def test_gaid_invalid(): - with pytest.raises(TealInputError): - GeneratedID(-1) + with pytest.raises(pt.TealInputError): + pt.GeneratedID(-1) - with pytest.raises(TealInputError): - GeneratedID(MAX_GROUP_SIZE) + with pytest.raises(pt.TealInputError): + pt.GeneratedID(pt.MAX_GROUP_SIZE) def test_gaid_dynamic_teal_3(): - with pytest.raises(TealInputError): - GeneratedID(Int(0)).__teal__(teal3Options) + with pytest.raises(pt.TealInputError): + pt.GeneratedID(pt.Int(0)).__teal__(teal3Options) def test_gaid_dynamic(): - arg = Int(0) - expr = GeneratedID(arg) - assert expr.type_of() == TealType.uint64 + arg = pt.Int(0) + expr = pt.GeneratedID(arg) + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock([TealOp(arg, Op.int, 0), TealOp(expr, Op.gaids)]) + expected = pt.TealSimpleBlock( + [pt.TealOp(arg, pt.Op.int, 0), pt.TealOp(expr, pt.Op.gaids)] + ) actual, _ = expr.__teal__(teal4Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected def test_gaid_dynamic_invalid(): - with pytest.raises(TealTypeError): - GeneratedID(Bytes("index")) + with pytest.raises(pt.TealTypeError): + pt.GeneratedID(pt.Bytes("index")) diff --git a/pyteal/ast/gitxn.py b/pyteal/ast/gitxn.py index 4108d86ff..486120e07 100644 --- a/pyteal/ast/gitxn.py +++ b/pyteal/ast/gitxn.py @@ -2,14 +2,13 @@ from pyteal.config import MAX_GROUP_SIZE -from ..errors import TealInputError, verifyFieldVersion, verifyTealVersion -from ..ir import TealOp, Op, TealBlock -from .expr import Expr -from .txn import TxnExpr, TxnField, TxnObject, TxnaExpr -from ..types import require_type, TealType +from pyteal.errors import TealInputError, verifyFieldVersion, verifyTealVersion +from pyteal.ir import TealOp, Op, TealBlock +from pyteal.ast.expr import Expr +from pyteal.ast.txn import TxnExpr, TxnField, TxnObject, TxnaExpr if TYPE_CHECKING: - from ..compiler import CompileOptions + from pyteal.compiler import CompileOptions class GitxnExpr(TxnExpr): diff --git a/pyteal/ast/gitxn_test.py b/pyteal/ast/gitxn_test.py index ab950067b..44415df83 100644 --- a/pyteal/ast/gitxn_test.py +++ b/pyteal/ast/gitxn_test.py @@ -1,20 +1,20 @@ import pytest -from .. import * +import pyteal as pt -teal5Options = CompileOptions(version=5) -teal6Options = CompileOptions(version=6) +teal5Options = pt.CompileOptions(version=5) +teal6Options = pt.CompileOptions(version=6) def test_gitxn_invalid(): for ctor, e in [ ( - lambda: Gitxn[MAX_GROUP_SIZE], - TealInputError, + lambda: pt.Gitxn[pt.MAX_GROUP_SIZE], + pt.TealInputError, ), ( - lambda: Gitxn[-1], - TealInputError, + lambda: pt.Gitxn[-1], + pt.TealInputError, ), ]: with pytest.raises(e): @@ -22,19 +22,19 @@ def test_gitxn_invalid(): def test_gitxn_valid(): - for i in range(MAX_GROUP_SIZE): - Gitxn[i].sender() + for i in range(pt.MAX_GROUP_SIZE): + pt.Gitxn[i].sender() def test_gitxn_expr_invalid(): for f, e in [ ( - lambda: GitxnExpr(Int(1), TxnField.sender), - TealInputError, + lambda: pt.GitxnExpr(pt.Int(1), pt.TxnField.sender), + pt.TealInputError, ), ( - lambda: GitxnExpr(1, TxnField.sender).__teal__(teal5Options), - TealInputError, + lambda: pt.GitxnExpr(1, pt.TxnField.sender).__teal__(teal5Options), + pt.TealInputError, ), ]: with pytest.raises(e): @@ -42,26 +42,30 @@ def test_gitxn_expr_invalid(): def test_gitxn_expr_valid(): - GitxnExpr(1, TxnField.sender).__teal__(teal6Options) + pt.GitxnExpr(1, pt.TxnField.sender).__teal__(teal6Options) def test_gitxna_expr_invalid(): for f, e in [ ( - lambda: GitxnaExpr("Invalid_type", TxnField.application_args, 1), - TealInputError, + lambda: pt.GitxnaExpr("Invalid_type", pt.TxnField.application_args, 1), + pt.TealInputError, ), ( - lambda: GitxnaExpr(1, TxnField.application_args, "Invalid_type"), - TealInputError, + lambda: pt.GitxnaExpr(1, pt.TxnField.application_args, "Invalid_type"), + pt.TealInputError, ), ( - lambda: GitxnaExpr(0, TxnField.application_args, Assert(Int(1))), - TealTypeError, + lambda: pt.GitxnaExpr( + 0, pt.TxnField.application_args, pt.Assert(pt.Int(1)) + ), + pt.TealTypeError, ), ( - lambda: GitxnaExpr(0, TxnField.application_args, 0).__teal__(teal5Options), - TealInputError, + lambda: pt.GitxnaExpr(0, pt.TxnField.application_args, 0).__teal__( + teal5Options + ), + pt.TealInputError, ), ]: with pytest.raises(e): @@ -72,8 +76,8 @@ def test_gitxna_valid(): [ e.__teal__(teal6Options) for e in [ - GitxnaExpr(0, TxnField.application_args, 1), - GitxnaExpr(0, TxnField.application_args, Int(1)), + pt.GitxnaExpr(0, pt.TxnField.application_args, 1), + pt.GitxnaExpr(0, pt.TxnField.application_args, pt.Int(1)), ] ] diff --git a/pyteal/ast/gload.py b/pyteal/ast/gload.py index d0963dc27..0c4b86c55 100644 --- a/pyteal/ast/gload.py +++ b/pyteal/ast/gload.py @@ -1,14 +1,14 @@ from typing import cast, Union, TYPE_CHECKING -from ..types import TealType, require_type -from ..ir import TealOp, Op, TealBlock -from ..errors import TealInputError, verifyTealVersion -from ..config import MAX_GROUP_SIZE, NUM_SLOTS -from .expr import Expr -from .leafexpr import LeafExpr +from pyteal.types import TealType, require_type +from pyteal.ir import TealOp, Op, TealBlock +from pyteal.errors import TealInputError, verifyTealVersion +from pyteal.config import MAX_GROUP_SIZE, NUM_SLOTS +from pyteal.ast.expr import Expr +from pyteal.ast.leafexpr import LeafExpr if TYPE_CHECKING: - from ..compiler import CompileOptions + from pyteal.compiler import CompileOptions class ImportScratchValue(LeafExpr): diff --git a/pyteal/ast/gload_test.py b/pyteal/ast/gload_test.py index 882280e19..fe182af6a 100644 --- a/pyteal/ast/gload_test.py +++ b/pyteal/ast/gload_test.py @@ -1,36 +1,33 @@ import pytest -from .. import * +import pyteal as pt -# this is not necessary but mypy complains if it's not included -from .. import MAX_GROUP_SIZE, NUM_SLOTS, CompileOptions - -teal3Options = CompileOptions(version=3) -teal4Options = CompileOptions(version=4) -teal6Options = CompileOptions(version=6) +teal3Options = pt.CompileOptions(version=3) +teal4Options = pt.CompileOptions(version=4) +teal6Options = pt.CompileOptions(version=6) def test_gload_teal_3(): - with pytest.raises(TealInputError): - ImportScratchValue(0, 1).__teal__(teal3Options) + with pytest.raises(pt.TealInputError): + pt.ImportScratchValue(0, 1).__teal__(teal3Options) - with pytest.raises(TealInputError): - ImportScratchValue(Int(0), 1).__teal__(teal3Options) + with pytest.raises(pt.TealInputError): + pt.ImportScratchValue(pt.Int(0), 1).__teal__(teal3Options) - with pytest.raises(TealInputError): - ImportScratchValue(Int(0), Int(1)).__teal__(teal3Options) + with pytest.raises(pt.TealInputError): + pt.ImportScratchValue(pt.Int(0), pt.Int(1)).__teal__(teal3Options) def test_gload_teal_4(): - with pytest.raises(TealInputError): - ImportScratchValue(Int(0), Int(2)).__teal__(teal4Options) + with pytest.raises(pt.TealInputError): + pt.ImportScratchValue(pt.Int(0), pt.Int(2)).__teal__(teal4Options) def test_gload(): - expr = ImportScratchValue(0, 1) - assert expr.type_of() == TealType.anytype + expr = pt.ImportScratchValue(0, 1) + assert expr.type_of() == pt.TealType.anytype - expected = TealSimpleBlock([TealOp(expr, Op.gload, 0, 1)]) + expected = pt.TealSimpleBlock([pt.TealOp(expr, pt.Op.gload, 0, 1)]) actual, _ = expr.__teal__(teal4Options) @@ -38,51 +35,57 @@ def test_gload(): def test_gloads(): - arg = Int(1) - expr = ImportScratchValue(arg, 0) - assert expr.type_of() == TealType.anytype + arg = pt.Int(1) + expr = pt.ImportScratchValue(arg, 0) + assert expr.type_of() == pt.TealType.anytype - expected = TealSimpleBlock([TealOp(arg, Op.int, 1), TealOp(expr, Op.gloads, 0)]) + expected = pt.TealSimpleBlock( + [pt.TealOp(arg, pt.Op.int, 1), pt.TealOp(expr, pt.Op.gloads, 0)] + ) actual, _ = expr.__teal__(teal4Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected def test_gloadss(): - txID = Int(1) - slotID = Int(0) - expr = ImportScratchValue(txID, slotID) - assert expr.type_of() == TealType.anytype - - expected = TealSimpleBlock( - [TealOp(txID, Op.int, 1), TealOp(slotID, Op.int, 0), TealOp(expr, Op.gloadss)] + txID = pt.Int(1) + slotID = pt.Int(0) + expr = pt.ImportScratchValue(txID, slotID) + assert expr.type_of() == pt.TealType.anytype + + expected = pt.TealSimpleBlock( + [ + pt.TealOp(txID, pt.Op.int, 1), + pt.TealOp(slotID, pt.Op.int, 0), + pt.TealOp(expr, pt.Op.gloadss), + ] ) actual, _ = expr.__teal__(teal6Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected def test_gload_invalid(): - with pytest.raises(TealInputError): - ImportScratchValue(-1, 0) + with pytest.raises(pt.TealInputError): + pt.ImportScratchValue(-1, 0) - with pytest.raises(TealInputError): - ImportScratchValue(MAX_GROUP_SIZE, 0) + with pytest.raises(pt.TealInputError): + pt.ImportScratchValue(pt.MAX_GROUP_SIZE, 0) - with pytest.raises(TealInputError): - ImportScratchValue(0, -1) + with pytest.raises(pt.TealInputError): + pt.ImportScratchValue(0, -1) - with pytest.raises(TealInputError): - ImportScratchValue(0, NUM_SLOTS) + with pytest.raises(pt.TealInputError): + pt.ImportScratchValue(0, pt.NUM_SLOTS) - with pytest.raises(TealInputError): - ImportScratchValue(0, Int(0)) + with pytest.raises(pt.TealInputError): + pt.ImportScratchValue(0, pt.Int(0)) - with pytest.raises(TealTypeError): - ImportScratchValue(Bytes("AQID"), 0) # byte encoding of [1, 2, 3] + with pytest.raises(pt.TealTypeError): + pt.ImportScratchValue(pt.Bytes("AQID"), 0) # byte encoding of [1, 2, 3] diff --git a/pyteal/ast/global_.py b/pyteal/ast/global_.py index 070dfdca3..b6b9c339f 100644 --- a/pyteal/ast/global_.py +++ b/pyteal/ast/global_.py @@ -1,13 +1,13 @@ from typing import TYPE_CHECKING from enum import Enum -from ..types import TealType -from ..errors import verifyFieldVersion -from ..ir import TealOp, Op, TealBlock -from .leafexpr import LeafExpr +from pyteal.types import TealType +from pyteal.errors import verifyFieldVersion +from pyteal.ir import TealOp, Op, TealBlock +from pyteal.ast.leafexpr import LeafExpr if TYPE_CHECKING: - from ..compiler import CompileOptions + from pyteal.compiler import CompileOptions class GlobalField(Enum): diff --git a/pyteal/ast/global_test.py b/pyteal/ast/global_test.py index 969f042e8..4e72b3bd1 100644 --- a/pyteal/ast/global_test.py +++ b/pyteal/ast/global_test.py @@ -1,21 +1,18 @@ import pytest -from .. import * +import pyteal as pt -# this is not necessary but mypy complains if it's not included -from .. import CompileOptions - -teal2Options = CompileOptions(version=2) -teal3Options = CompileOptions(version=3) -teal5Options = CompileOptions(version=5) -teal6Options = CompileOptions(version=6) +teal2Options = pt.CompileOptions(version=2) +teal3Options = pt.CompileOptions(version=3) +teal5Options = pt.CompileOptions(version=5) +teal6Options = pt.CompileOptions(version=6) def test_global_min_txn_fee(): - expr = Global.min_txn_fee() - assert expr.type_of() == TealType.uint64 + expr = pt.Global.min_txn_fee() + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock([TealOp(expr, Op.global_, "MinTxnFee")]) + expected = pt.TealSimpleBlock([pt.TealOp(expr, pt.Op.global_, "MinTxnFee")]) actual, _ = expr.__teal__(teal2Options) @@ -23,10 +20,10 @@ def test_global_min_txn_fee(): def test_global_min_balance(): - expr = Global.min_balance() - assert expr.type_of() == TealType.uint64 + expr = pt.Global.min_balance() + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock([TealOp(expr, Op.global_, "MinBalance")]) + expected = pt.TealSimpleBlock([pt.TealOp(expr, pt.Op.global_, "MinBalance")]) actual, _ = expr.__teal__(teal2Options) @@ -34,10 +31,10 @@ def test_global_min_balance(): def test_global_max_txn_life(): - expr = Global.max_txn_life() - assert expr.type_of() == TealType.uint64 + expr = pt.Global.max_txn_life() + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock([TealOp(expr, Op.global_, "MaxTxnLife")]) + expected = pt.TealSimpleBlock([pt.TealOp(expr, pt.Op.global_, "MaxTxnLife")]) actual, _ = expr.__teal__(teal2Options) @@ -45,10 +42,10 @@ def test_global_max_txn_life(): def test_global_zero_address(): - expr = Global.zero_address() - assert expr.type_of() == TealType.bytes + expr = pt.Global.zero_address() + assert expr.type_of() == pt.TealType.bytes - expected = TealSimpleBlock([TealOp(expr, Op.global_, "ZeroAddress")]) + expected = pt.TealSimpleBlock([pt.TealOp(expr, pt.Op.global_, "ZeroAddress")]) actual, _ = expr.__teal__(teal2Options) @@ -56,10 +53,10 @@ def test_global_zero_address(): def test_global_group_size(): - expr = Global.group_size() - assert expr.type_of() == TealType.uint64 + expr = pt.Global.group_size() + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock([TealOp(expr, Op.global_, "GroupSize")]) + expected = pt.TealSimpleBlock([pt.TealOp(expr, pt.Op.global_, "GroupSize")]) actual, _ = expr.__teal__(teal2Options) @@ -67,10 +64,10 @@ def test_global_group_size(): def test_global_logic_sig_version(): - expr = Global.logic_sig_version() - assert expr.type_of() == TealType.uint64 + expr = pt.Global.logic_sig_version() + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock([TealOp(expr, Op.global_, "LogicSigVersion")]) + expected = pt.TealSimpleBlock([pt.TealOp(expr, pt.Op.global_, "LogicSigVersion")]) actual, _ = expr.__teal__(teal2Options) @@ -78,10 +75,10 @@ def test_global_logic_sig_version(): def test_global_round(): - expr = Global.round() - assert expr.type_of() == TealType.uint64 + expr = pt.Global.round() + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock([TealOp(expr, Op.global_, "Round")]) + expected = pt.TealSimpleBlock([pt.TealOp(expr, pt.Op.global_, "Round")]) actual, _ = expr.__teal__(teal2Options) @@ -89,10 +86,10 @@ def test_global_round(): def test_global_latest_timestamp(): - expr = Global.latest_timestamp() - assert expr.type_of() == TealType.uint64 + expr = pt.Global.latest_timestamp() + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock([TealOp(expr, Op.global_, "LatestTimestamp")]) + expected = pt.TealSimpleBlock([pt.TealOp(expr, pt.Op.global_, "LatestTimestamp")]) actual, _ = expr.__teal__(teal2Options) @@ -100,10 +97,12 @@ def test_global_latest_timestamp(): def test_global_current_application_id(): - expr = Global.current_application_id() - assert expr.type_of() == TealType.uint64 + expr = pt.Global.current_application_id() + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock([TealOp(expr, Op.global_, "CurrentApplicationID")]) + expected = pt.TealSimpleBlock( + [pt.TealOp(expr, pt.Op.global_, "CurrentApplicationID")] + ) actual, _ = expr.__teal__(teal2Options) @@ -111,84 +110,90 @@ def test_global_current_application_id(): def test_global_creator_address(): - expr = Global.creator_address() - assert expr.type_of() == TealType.bytes + expr = pt.Global.creator_address() + assert expr.type_of() == pt.TealType.bytes - expected = TealSimpleBlock([TealOp(expr, Op.global_, "CreatorAddress")]) + expected = pt.TealSimpleBlock([pt.TealOp(expr, pt.Op.global_, "CreatorAddress")]) actual, _ = expr.__teal__(teal3Options) assert actual == expected - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): expr.__teal__(teal2Options) def test_global_current_application_address(): - expr = Global.current_application_address() - assert expr.type_of() == TealType.bytes + expr = pt.Global.current_application_address() + assert expr.type_of() == pt.TealType.bytes - expected = TealSimpleBlock([TealOp(expr, Op.global_, "CurrentApplicationAddress")]) + expected = pt.TealSimpleBlock( + [pt.TealOp(expr, pt.Op.global_, "CurrentApplicationAddress")] + ) actual, _ = expr.__teal__(teal5Options) assert actual == expected - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): expr.__teal__(teal3Options) def test_global_group_id(): - expr = Global.group_id() - assert expr.type_of() == TealType.bytes + expr = pt.Global.group_id() + assert expr.type_of() == pt.TealType.bytes - expected = TealSimpleBlock([TealOp(expr, Op.global_, "GroupID")]) + expected = pt.TealSimpleBlock([pt.TealOp(expr, pt.Op.global_, "GroupID")]) actual, _ = expr.__teal__(teal5Options) assert actual == expected - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): expr.__teal__(teal3Options) def test_global_opcode_budget(): - expr = Global.opcode_budget() - assert expr.type_of() == TealType.uint64 + expr = pt.Global.opcode_budget() + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock([TealOp(expr, Op.global_, "OpcodeBudget")]) + expected = pt.TealSimpleBlock([pt.TealOp(expr, pt.Op.global_, "OpcodeBudget")]) actual, _ = expr.__teal__(teal6Options) assert actual == expected - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): expr.__teal__(teal5Options) def test_global_caller_application_id(): - expr = Global.caller_app_id() - assert expr.type_of() == TealType.uint64 + expr = pt.Global.caller_app_id() + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock([TealOp(expr, Op.global_, "CallerApplicationID")]) + expected = pt.TealSimpleBlock( + [pt.TealOp(expr, pt.Op.global_, "CallerApplicationID")] + ) actual, _ = expr.__teal__(teal6Options) assert actual == expected - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): expr.__teal__(teal5Options) def test_global_caller_app_address(): - expr = Global.caller_app_address() - assert expr.type_of() == TealType.bytes + expr = pt.Global.caller_app_address() + assert expr.type_of() == pt.TealType.bytes - expected = TealSimpleBlock([TealOp(expr, Op.global_, "CallerApplicationAddress")]) + expected = pt.TealSimpleBlock( + [pt.TealOp(expr, pt.Op.global_, "CallerApplicationAddress")] + ) actual, _ = expr.__teal__(teal6Options) assert actual == expected - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): expr.__teal__(teal5Options) diff --git a/pyteal/ast/gtxn.py b/pyteal/ast/gtxn.py index b12f8f9d5..640f0605d 100644 --- a/pyteal/ast/gtxn.py +++ b/pyteal/ast/gtxn.py @@ -1,14 +1,14 @@ from typing import Union, cast, TYPE_CHECKING -from ..types import TealType, require_type -from ..ir import TealOp, Op, TealBlock -from ..errors import TealInputError, verifyFieldVersion, verifyTealVersion -from ..config import MAX_GROUP_SIZE -from .expr import Expr -from .txn import TxnField, TxnExpr, TxnaExpr, TxnObject +from pyteal.types import TealType, require_type +from pyteal.ir import TealOp, Op, TealBlock +from pyteal.errors import TealInputError, verifyFieldVersion, verifyTealVersion +from pyteal.config import MAX_GROUP_SIZE +from pyteal.ast.expr import Expr +from pyteal.ast.txn import TxnField, TxnExpr, TxnaExpr, TxnObject if TYPE_CHECKING: - from ..compiler import CompileOptions + from pyteal.compiler import CompileOptions def validate_txn_index_or_throw(txnIndex: Union[int, Expr]): diff --git a/pyteal/ast/gtxn_test.py b/pyteal/ast/gtxn_test.py index 9b895d100..9f48e5317 100644 --- a/pyteal/ast/gtxn_test.py +++ b/pyteal/ast/gtxn_test.py @@ -1,16 +1,16 @@ import pytest -from .. import * +import pyteal as pt -teal6Options = CompileOptions(version=6) +teal6Options = pt.CompileOptions(version=6) def test_gtxn_invalid(): for f, e in [ - (lambda: Gtxn[-1], TealInputError), - (lambda: Gtxn[MAX_GROUP_SIZE + 1], TealInputError), - (lambda: Gtxn[Pop(Int(0))], TealTypeError), - (lambda: Gtxn[Bytes("index")], TealTypeError), + (lambda: pt.Gtxn[-1], pt.TealInputError), + (lambda: pt.Gtxn[pt.MAX_GROUP_SIZE + 1], pt.TealInputError), + (lambda: pt.Gtxn[pt.Pop(pt.Int(0))], pt.TealTypeError), + (lambda: pt.Gtxn[pt.Bytes("index")], pt.TealTypeError), ]: with pytest.raises(e): f() @@ -18,7 +18,10 @@ def test_gtxn_invalid(): def test_gtxn_expr_invalid(): for f, e in [ - (lambda: GtxnExpr(Assert(Int(1)), TxnField.sender), TealTypeError), + ( + lambda: pt.GtxnExpr(pt.Assert(pt.Int(1)), pt.TxnField.sender), + pt.TealTypeError, + ), ]: with pytest.raises(e): f() @@ -28,18 +31,30 @@ def test_gtxn_expr_valid(): [ e.__teal__(teal6Options) for e in [ - GtxnExpr(1, TxnField.sender), - GtxnExpr(Int(1), TxnField.sender), + pt.GtxnExpr(1, pt.TxnField.sender), + pt.GtxnExpr(pt.Int(1), pt.TxnField.sender), ] ] def test_gtxna_expr_invalid(): for f, e in [ - (lambda: GtxnaExpr("Invalid_type", TxnField.assets, 1), TealInputError), - (lambda: GtxnaExpr(1, TxnField.assets, "Invalid_type"), TealInputError), - (lambda: GtxnaExpr(Assert(Int(1)), TxnField.assets, 1), TealTypeError), - (lambda: GtxnaExpr(1, TxnField.assets, Assert(Int(1))), TealTypeError), + ( + lambda: pt.GtxnaExpr("Invalid_type", pt.TxnField.assets, 1), + pt.TealInputError, + ), + ( + lambda: pt.GtxnaExpr(1, pt.TxnField.assets, "Invalid_type"), + pt.TealInputError, + ), + ( + lambda: pt.GtxnaExpr(pt.Assert(pt.Int(1)), pt.TxnField.assets, 1), + pt.TealTypeError, + ), + ( + lambda: pt.GtxnaExpr(1, pt.TxnField.assets, pt.Assert(pt.Int(1))), + pt.TealTypeError, + ), ]: with pytest.raises(e): f() @@ -49,8 +64,8 @@ def test_gtxna_expr_valid(): [ e.__teal__(teal6Options) for e in [ - GtxnaExpr(1, TxnField.assets, 1), - GtxnaExpr(Int(1), TxnField.assets, Int(1)), + pt.GtxnaExpr(1, pt.TxnField.assets, 1), + pt.GtxnaExpr(pt.Int(1), pt.TxnField.assets, pt.Int(1)), ] ] diff --git a/pyteal/ast/if_.py b/pyteal/ast/if_.py index 2bb60ab86..605eb89e5 100644 --- a/pyteal/ast/if_.py +++ b/pyteal/ast/if_.py @@ -1,12 +1,12 @@ from typing import TYPE_CHECKING -from ..errors import TealCompileError, TealInputError -from ..types import TealType, require_type, types_match -from ..ir import TealSimpleBlock, TealConditionalBlock -from .expr import Expr +from pyteal.errors import TealCompileError, TealInputError +from pyteal.types import TealType, require_type +from pyteal.ir import TealSimpleBlock, TealConditionalBlock +from pyteal.ast.expr import Expr if TYPE_CHECKING: - from ..compiler import CompileOptions + from pyteal.compiler import CompileOptions class If(Expr): diff --git a/pyteal/ast/if_test.py b/pyteal/ast/if_test.py index d174731f8..32d8f2f92 100644 --- a/pyteal/ast/if_test.py +++ b/pyteal/ast/if_test.py @@ -1,39 +1,38 @@ import pytest -from .. import * +import pyteal as pt -# this is not necessary but mypy complains if it's not included -from .. import CompileOptions - -options = CompileOptions() +options = pt.CompileOptions() def test_if_has_return(): - exprWithReturn = If(Int(1), Return(Int(1)), Return(Int(0))) + exprWithReturn = pt.If(pt.Int(1), pt.Return(pt.Int(1)), pt.Return(pt.Int(0))) assert exprWithReturn.has_return() - exprWithoutReturn = If(Int(1), Int(1), Int(0)) + exprWithoutReturn = pt.If(pt.Int(1), pt.Int(1), pt.Int(0)) assert not exprWithoutReturn.has_return() - exprSemiReturn = If( - Int(1), Return(Int(1)), App.globalPut(Bytes("key"), Bytes("value")) + exprSemiReturn = pt.If( + pt.Int(1), + pt.Return(pt.Int(1)), + pt.App.globalPut(pt.Bytes("key"), pt.Bytes("value")), ) assert not exprSemiReturn.has_return() def test_if_int(): - args = [Int(0), Int(1), Int(2)] - expr = If(args[0], args[1], args[2]) - assert expr.type_of() == TealType.uint64 + args = [pt.Int(0), pt.Int(1), pt.Int(2)] + expr = pt.If(args[0], args[1], args[2]) + assert expr.type_of() == pt.TealType.uint64 expected, _ = args[0].__teal__(options) thenBlock, _ = args[1].__teal__(options) elseBlock, _ = args[2].__teal__(options) - expectedBranch = TealConditionalBlock([]) + expectedBranch = pt.TealConditionalBlock([]) expectedBranch.setTrueBlock(thenBlock) expectedBranch.setFalseBlock(elseBlock) expected.setNextBlock(expectedBranch) - end = TealSimpleBlock([]) + end = pt.TealSimpleBlock([]) thenBlock.setNextBlock(end) elseBlock.setNextBlock(end) @@ -43,18 +42,18 @@ def test_if_int(): def test_if_bytes(): - args = [Int(1), Txn.sender(), Txn.receiver()] - expr = If(args[0], args[1], args[2]) - assert expr.type_of() == TealType.bytes + args = [pt.Int(1), pt.Txn.sender(), pt.Txn.receiver()] + expr = pt.If(args[0], args[1], args[2]) + assert expr.type_of() == pt.TealType.bytes expected, _ = args[0].__teal__(options) thenBlock, _ = args[1].__teal__(options) elseBlock, _ = args[2].__teal__(options) - expectedBranch = TealConditionalBlock([]) + expectedBranch = pt.TealConditionalBlock([]) expectedBranch.setTrueBlock(thenBlock) expectedBranch.setFalseBlock(elseBlock) expected.setNextBlock(expectedBranch) - end = TealSimpleBlock([]) + end = pt.TealSimpleBlock([]) thenBlock.setNextBlock(end) elseBlock.setNextBlock(end) @@ -64,18 +63,18 @@ def test_if_bytes(): def test_if_none(): - args = [Int(0), Pop(Txn.sender()), Pop(Txn.receiver())] - expr = If(args[0], args[1], args[2]) - assert expr.type_of() == TealType.none + args = [pt.Int(0), pt.Pop(pt.Txn.sender()), pt.Pop(pt.Txn.receiver())] + expr = pt.If(args[0], args[1], args[2]) + assert expr.type_of() == pt.TealType.none expected, _ = args[0].__teal__(options) thenBlockStart, thenBlockEnd = args[1].__teal__(options) elseBlockStart, elseBlockEnd = args[2].__teal__(options) - expectedBranch = TealConditionalBlock([]) + expectedBranch = pt.TealConditionalBlock([]) expectedBranch.setTrueBlock(thenBlockStart) expectedBranch.setFalseBlock(elseBlockStart) expected.setNextBlock(expectedBranch) - end = TealSimpleBlock([]) + end = pt.TealSimpleBlock([]) thenBlockEnd.setNextBlock(end) elseBlockEnd.setNextBlock(end) @@ -85,14 +84,14 @@ def test_if_none(): def test_if_single(): - args = [Int(1), Pop(Int(1))] - expr = If(args[0], args[1]) - assert expr.type_of() == TealType.none + args = [pt.Int(1), pt.Pop(pt.Int(1))] + expr = pt.If(args[0], args[1]) + assert expr.type_of() == pt.TealType.none expected, _ = args[0].__teal__(options) thenBlockStart, thenBlockEnd = args[1].__teal__(options) - end = TealSimpleBlock([]) - expectedBranch = TealConditionalBlock([]) + end = pt.TealSimpleBlock([]) + expectedBranch = pt.TealConditionalBlock([]) expectedBranch.setTrueBlock(thenBlockStart) expectedBranch.setFalseBlock(end) expected.setNextBlock(expectedBranch) @@ -104,36 +103,36 @@ def test_if_single(): def test_if_invalid(): - with pytest.raises(TealTypeError): - If(Int(0), Txn.amount(), Txn.sender()) + with pytest.raises(pt.TealTypeError): + pt.If(pt.Int(0), pt.Txn.amount(), pt.Txn.sender()) - with pytest.raises(TealTypeError): - If(Txn.sender(), Int(1), Int(0)) + with pytest.raises(pt.TealTypeError): + pt.If(pt.Txn.sender(), pt.Int(1), pt.Int(0)) - with pytest.raises(TealTypeError): - If(Int(0), Txn.sender()) + with pytest.raises(pt.TealTypeError): + pt.If(pt.Int(0), pt.Txn.sender()) - with pytest.raises(TealTypeError): - If(Int(0), Int(2)) + with pytest.raises(pt.TealTypeError): + pt.If(pt.Int(0), pt.Int(2)) - with pytest.raises(TealCompileError): - expr = If(Int(0)) + with pytest.raises(pt.TealCompileError): + expr = pt.If(pt.Int(0)) expr.__teal__(options) def test_if_alt_int(): - args = [Int(0), Int(1), Int(2)] - expr = If(args[0]).Then(args[1]).Else(args[2]) - assert expr.type_of() == TealType.uint64 + args = [pt.Int(0), pt.Int(1), pt.Int(2)] + expr = pt.If(args[0]).Then(args[1]).Else(args[2]) + assert expr.type_of() == pt.TealType.uint64 expected, _ = args[0].__teal__(options) thenBlock, _ = args[1].__teal__(options) elseBlock, _ = args[2].__teal__(options) - expectedBranch = TealConditionalBlock([]) + expectedBranch = pt.TealConditionalBlock([]) expectedBranch.setTrueBlock(thenBlock) expectedBranch.setFalseBlock(elseBlock) expected.setNextBlock(expectedBranch) - end = TealSimpleBlock([]) + end = pt.TealSimpleBlock([]) thenBlock.setNextBlock(end) elseBlock.setNextBlock(end) @@ -143,18 +142,18 @@ def test_if_alt_int(): def test_if_alt_bytes(): - args = [Int(1), Txn.sender(), Txn.receiver()] - expr = If(args[0]).Then(args[1]).Else(args[2]) - assert expr.type_of() == TealType.bytes + args = [pt.Int(1), pt.Txn.sender(), pt.Txn.receiver()] + expr = pt.If(args[0]).Then(args[1]).Else(args[2]) + assert expr.type_of() == pt.TealType.bytes expected, _ = args[0].__teal__(options) thenBlock, _ = args[1].__teal__(options) elseBlock, _ = args[2].__teal__(options) - expectedBranch = TealConditionalBlock([]) + expectedBranch = pt.TealConditionalBlock([]) expectedBranch.setTrueBlock(thenBlock) expectedBranch.setFalseBlock(elseBlock) expected.setNextBlock(expectedBranch) - end = TealSimpleBlock([]) + end = pt.TealSimpleBlock([]) thenBlock.setNextBlock(end) elseBlock.setNextBlock(end) @@ -164,18 +163,18 @@ def test_if_alt_bytes(): def test_if_alt_none(): - args = [Int(0), Pop(Txn.sender()), Pop(Txn.receiver())] - expr = If(args[0]).Then(args[1]).Else(args[2]) - assert expr.type_of() == TealType.none + args = [pt.Int(0), pt.Pop(pt.Txn.sender()), pt.Pop(pt.Txn.receiver())] + expr = pt.If(args[0]).Then(args[1]).Else(args[2]) + assert expr.type_of() == pt.TealType.none expected, _ = args[0].__teal__(options) thenBlockStart, thenBlockEnd = args[1].__teal__(options) elseBlockStart, elseBlockEnd = args[2].__teal__(options) - expectedBranch = TealConditionalBlock([]) + expectedBranch = pt.TealConditionalBlock([]) expectedBranch.setTrueBlock(thenBlockStart) expectedBranch.setFalseBlock(elseBlockStart) expected.setNextBlock(expectedBranch) - end = TealSimpleBlock([]) + end = pt.TealSimpleBlock([]) thenBlockEnd.setNextBlock(end) elseBlockEnd.setNextBlock(end) @@ -185,19 +184,19 @@ def test_if_alt_none(): def test_elseif_syntax(): - args = [Int(0), Int(1), Int(2), Int(3), Int(4)] - expr = If(args[0]).Then(args[1]).ElseIf(args[2]).Then(args[3]).Else(args[4]) - assert expr.type_of() == TealType.uint64 + args = [pt.Int(0), pt.Int(1), pt.Int(2), pt.Int(3), pt.Int(4)] + expr = pt.If(args[0]).Then(args[1]).ElseIf(args[2]).Then(args[3]).Else(args[4]) + assert expr.type_of() == pt.TealType.uint64 - elseExpr = If(args[2]).Then(args[3]).Else(args[4]) + elseExpr = pt.If(args[2]).Then(args[3]).Else(args[4]) expected, _ = args[0].__teal__(options) thenBlock, _ = args[1].__teal__(options) elseStart, elseEnd = elseExpr.__teal__(options) - expectedBranch = TealConditionalBlock([]) + expectedBranch = pt.TealConditionalBlock([]) expectedBranch.setTrueBlock(thenBlock) expectedBranch.setFalseBlock(elseStart) expected.setNextBlock(expectedBranch) - end = TealSimpleBlock([]) + end = pt.TealSimpleBlock([]) thenBlock.setNextBlock(end) elseEnd.setNextBlock(end) @@ -207,9 +206,9 @@ def test_elseif_syntax(): def test_elseif_multiple(): - args = [Int(0), Int(1), Int(2), Int(3), Int(4), Int(5), Int(6)] + args = [pt.Int(0), pt.Int(1), pt.Int(2), pt.Int(3), pt.Int(4), pt.Int(5), pt.Int(6)] expr = ( - If(args[0]) + pt.If(args[0]) .Then(args[1]) .ElseIf(args[2]) .Then(args[3]) @@ -217,17 +216,17 @@ def test_elseif_multiple(): .Then(args[5]) .Else(args[6]) ) - assert expr.type_of() == TealType.uint64 + assert expr.type_of() == pt.TealType.uint64 - elseIfExpr = If(args[2], args[3], If(args[4], args[5], args[6])) + elseIfExpr = pt.If(args[2], args[3], pt.If(args[4], args[5], args[6])) expected, _ = args[0].__teal__(options) thenBlock, _ = args[1].__teal__(options) elseStart, elseEnd = elseIfExpr.__teal__(options) - expectedBranch = TealConditionalBlock([]) + expectedBranch = pt.TealConditionalBlock([]) expectedBranch.setTrueBlock(thenBlock) expectedBranch.setFalseBlock(elseStart) expected.setNextBlock(expectedBranch) - end = TealSimpleBlock([]) + end = pt.TealSimpleBlock([]) thenBlock.setNextBlock(end) elseEnd.setNextBlock(end) @@ -237,31 +236,31 @@ def test_elseif_multiple(): def test_if_invalid_alt_syntax(): - with pytest.raises(TealCompileError): - expr = If(Int(0)).ElseIf(Int(1)) + with pytest.raises(pt.TealCompileError): + expr = pt.If(pt.Int(0)).ElseIf(pt.Int(1)) expr.__teal__(options) - with pytest.raises(TealCompileError): - expr = If(Int(0)).ElseIf(Int(1)).Then(Int(2)) + with pytest.raises(pt.TealCompileError): + expr = pt.If(pt.Int(0)).ElseIf(pt.Int(1)).Then(pt.Int(2)) expr.__teal__(options) - with pytest.raises(TealCompileError): - expr = If(Int(0)).Then(Int(1)).ElseIf(Int(2)) + with pytest.raises(pt.TealCompileError): + expr = pt.If(pt.Int(0)).Then(pt.Int(1)).ElseIf(pt.Int(2)) expr.__teal__(options) - with pytest.raises(TealCompileError): - expr = If(Int(0)).Then(Int(1)).ElseIf(Int(2)) + with pytest.raises(pt.TealCompileError): + expr = pt.If(pt.Int(0)).Then(pt.Int(1)).ElseIf(pt.Int(2)) expr.__teal__(options) - with pytest.raises(TealCompileError): - expr = If(Int(0)).Else(Int(1)) + with pytest.raises(pt.TealCompileError): + expr = pt.If(pt.Int(0)).Else(pt.Int(1)) expr.__teal__(options) - with pytest.raises(TealInputError): - expr = If(Int(0)).Else(Int(1)).Then(Int(2)) + with pytest.raises(pt.TealInputError): + expr = pt.If(pt.Int(0)).Else(pt.Int(1)).Then(pt.Int(2)) - with pytest.raises(TealInputError): - expr = If(Int(0)).Else(Int(1)).Else(Int(2)) + with pytest.raises(pt.TealInputError): + expr = pt.If(pt.Int(0)).Else(pt.Int(1)).Else(pt.Int(2)) - with pytest.raises(TealInputError): - expr = If(Int(0), Pop(Int(1))).Else(Int(2)) + with pytest.raises(pt.TealInputError): + expr = pt.If(pt.Int(0), pt.Pop(pt.Int(1))).Else(pt.Int(2)) diff --git a/pyteal/ast/int.py b/pyteal/ast/int.py index 6a590f3b5..26ef8cff5 100644 --- a/pyteal/ast/int.py +++ b/pyteal/ast/int.py @@ -1,12 +1,12 @@ from typing import TYPE_CHECKING -from ..types import TealType -from ..ir import TealOp, Op, TealBlock -from ..errors import TealInputError -from .leafexpr import LeafExpr +from pyteal.types import TealType +from pyteal.ir import TealOp, Op, TealBlock +from pyteal.errors import TealInputError +from pyteal.ast.leafexpr import LeafExpr if TYPE_CHECKING: - from ..compiler import CompileOptions + from pyteal.compiler import CompileOptions class Int(LeafExpr): diff --git a/pyteal/ast/int_test.py b/pyteal/ast/int_test.py index e11a13da5..8b65033bc 100644 --- a/pyteal/ast/int_test.py +++ b/pyteal/ast/int_test.py @@ -1,21 +1,18 @@ import pytest -from .. import * +import pyteal as pt -# this is not necessary but mypy complains if it's not included -from .. import CompileOptions - -options = CompileOptions() +options = pt.CompileOptions() def test_int(): values = [0, 1, 8, 232323, 2**64 - 1] for value in values: - expr = Int(value) - assert expr.type_of() == TealType.uint64 + expr = pt.Int(value) + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock([TealOp(expr, Op.int, value)]) + expected = pt.TealSimpleBlock([pt.TealOp(expr, pt.Op.int, value)]) actual, _ = expr.__teal__(options) @@ -23,24 +20,24 @@ def test_int(): def test_int_invalid(): - with pytest.raises(TealInputError): - Int(6.7) + with pytest.raises(pt.TealInputError): + pt.Int(6.7) - with pytest.raises(TealInputError): - Int(-1) + with pytest.raises(pt.TealInputError): + pt.Int(-1) - with pytest.raises(TealInputError): - Int(2**64) + with pytest.raises(pt.TealInputError): + pt.Int(2**64) - with pytest.raises(TealInputError): - Int("0") + with pytest.raises(pt.TealInputError): + pt.Int("0") def test_enum_int(): - expr = EnumInt("OptIn") - assert expr.type_of() == TealType.uint64 + expr = pt.EnumInt("OptIn") + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock([TealOp(expr, Op.int, "OptIn")]) + expected = pt.TealSimpleBlock([pt.TealOp(expr, pt.Op.int, "OptIn")]) actual, _ = expr.__teal__(options) diff --git a/pyteal/ast/itxn.py b/pyteal/ast/itxn.py index ff857c808..af7704198 100644 --- a/pyteal/ast/itxn.py +++ b/pyteal/ast/itxn.py @@ -1,15 +1,15 @@ from enum import Enum from typing import Dict, TYPE_CHECKING, List, Union, cast -from ..types import TealType, require_type -from ..errors import TealInputError, verifyTealVersion -from ..ir import TealOp, Op, TealBlock -from .expr import Expr -from .txn import TxnField, TxnExprBuilder, TxnaExprBuilder, TxnObject -from .seq import Seq +from pyteal.types import TealType, require_type +from pyteal.errors import TealInputError, verifyTealVersion +from pyteal.ir import TealOp, Op, TealBlock +from pyteal.ast.expr import Expr +from pyteal.ast.txn import TxnField, TxnExprBuilder, TxnaExprBuilder, TxnObject +from pyteal.ast.seq import Seq if TYPE_CHECKING: - from ..compiler import CompileOptions + from pyteal.compiler import CompileOptions class InnerTxnAction(Enum): diff --git a/pyteal/ast/itxn_test.py b/pyteal/ast/itxn_test.py index 7f5c06e5a..af9c3a79c 100644 --- a/pyteal/ast/itxn_test.py +++ b/pyteal/ast/itxn_test.py @@ -1,131 +1,136 @@ import pytest -from .. import * -from ..types import types_match +import pyteal as pt +from pyteal.types import types_match -# this is not necessary but mypy complains if it's not included -from .. import MAX_GROUP_SIZE, CompileOptions - -teal4Options = CompileOptions(version=4) -teal5Options = CompileOptions(version=5) -teal6Options = CompileOptions(version=6) +teal4Options = pt.CompileOptions(version=4) +teal5Options = pt.CompileOptions(version=5) +teal6Options = pt.CompileOptions(version=6) def test_InnerTxnBuilder_Begin(): - expr = InnerTxnBuilder.Begin() - assert expr.type_of() == TealType.none + expr = pt.InnerTxnBuilder.Begin() + assert expr.type_of() == pt.TealType.none assert not expr.has_return() - expected = TealSimpleBlock([TealOp(expr, Op.itxn_begin)]) + expected = pt.TealSimpleBlock([pt.TealOp(expr, pt.Op.itxn_begin)]) actual, _ = expr.__teal__(teal5Options) assert actual == expected - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): expr.__teal__(teal4Options) def test_InnerTxnBuilder_Submit(): - expr = InnerTxnBuilder.Submit() - assert expr.type_of() == TealType.none + expr = pt.InnerTxnBuilder.Submit() + assert expr.type_of() == pt.TealType.none assert not expr.has_return() - expected = TealSimpleBlock([TealOp(expr, Op.itxn_submit)]) + expected = pt.TealSimpleBlock([pt.TealOp(expr, pt.Op.itxn_submit)]) actual, _ = expr.__teal__(teal5Options) assert actual == expected - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): expr.__teal__(teal4Options) def test_InnerTxnBuilder_Next(): - expr = InnerTxnBuilder.Next() - assert expr.type_of() == TealType.none + expr = pt.InnerTxnBuilder.Next() + assert expr.type_of() == pt.TealType.none assert not expr.has_return() - expected = TealSimpleBlock([TealOp(expr, Op.itxn_next)]) + expected = pt.TealSimpleBlock([pt.TealOp(expr, pt.Op.itxn_next)]) actual, _ = expr.__teal__(teal6Options) assert actual == expected - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): expr.__teal__(teal5Options) def test_InnerTxnBuilder_SetField(): - for field in TxnField: + for field in pt.TxnField: if field.is_array: - with pytest.raises(TealInputError): - InnerTxnBuilder.SetField(field, Int(0)) + with pytest.raises(pt.TealInputError): + pt.InnerTxnBuilder.SetField(field, pt.Int(0)) continue for value, opArgs in ( - (Int(0), (Op.int, 0)), - (Bytes("value"), (Op.byte, '"value"')), + (pt.Int(0), (pt.Op.int, 0)), + (pt.Bytes("value"), (pt.Op.byte, '"value"')), ): - assert field.type_of() in (TealType.uint64, TealType.bytes) + assert field.type_of() in (pt.TealType.uint64, pt.TealType.bytes) if not types_match(field.type_of(), value.type_of()): - with pytest.raises(TealTypeError): - InnerTxnBuilder.SetField(field, value) + with pytest.raises(pt.TealTypeError): + pt.InnerTxnBuilder.SetField(field, value) continue - expr = InnerTxnBuilder.SetField(field, value) - assert expr.type_of() == TealType.none + expr = pt.InnerTxnBuilder.SetField(field, value) + assert expr.type_of() == pt.TealType.none assert not expr.has_return() - expected = TealSimpleBlock( - [TealOp(value, *opArgs), TealOp(expr, Op.itxn_field, field.arg_name)] + expected = pt.TealSimpleBlock( + [ + pt.TealOp(value, *opArgs), + pt.TealOp(expr, pt.Op.itxn_field, field.arg_name), + ] ) actual, _ = expr.__teal__(teal5Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): expr.__teal__(teal4Options) def test_InnerTxnBuilder_SetFields(): cases = ( - ({}, Seq()), - ({TxnField.amount: Int(5)}, InnerTxnBuilder.SetField(TxnField.amount, Int(5))), + ({}, pt.Seq()), + ( + {pt.TxnField.amount: pt.Int(5)}, + pt.InnerTxnBuilder.SetField(pt.TxnField.amount, pt.Int(5)), + ), ( { - TxnField.type_enum: TxnType.Payment, - TxnField.close_remainder_to: Txn.sender(), + pt.TxnField.type_enum: pt.TxnType.Payment, + pt.TxnField.close_remainder_to: pt.Txn.sender(), }, - Seq( - InnerTxnBuilder.SetField(TxnField.type_enum, TxnType.Payment), - InnerTxnBuilder.SetField(TxnField.close_remainder_to, Txn.sender()), + pt.Seq( + pt.InnerTxnBuilder.SetField(pt.TxnField.type_enum, pt.TxnType.Payment), + pt.InnerTxnBuilder.SetField( + pt.TxnField.close_remainder_to, pt.Txn.sender() + ), ), ), ) for fields, expectedExpr in cases: - expr = InnerTxnBuilder.SetFields(fields) - assert expr.type_of() == TealType.none + expr = pt.InnerTxnBuilder.SetFields(fields) + assert expr.type_of() == pt.TealType.none assert not expr.has_return() expected, _ = expectedExpr.__teal__(teal5Options) expected.addIncoming() - expected = TealBlock.NormalizeBlocks(expected) + expected = pt.TealBlock.NormalizeBlocks(expected) actual, _ = expr.__teal__(teal5Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected if len(fields) != 0: - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): expr.__teal__(teal4Options) diff --git a/pyteal/ast/leafexpr.py b/pyteal/ast/leafexpr.py index 6a39b39d4..e03905060 100644 --- a/pyteal/ast/leafexpr.py +++ b/pyteal/ast/leafexpr.py @@ -1,4 +1,4 @@ -from .expr import Expr +from pyteal.ast.expr import Expr class LeafExpr(Expr): diff --git a/pyteal/ast/maybe.py b/pyteal/ast/maybe.py index e394b4682..43846384d 100644 --- a/pyteal/ast/maybe.py +++ b/pyteal/ast/maybe.py @@ -1,14 +1,11 @@ -from typing import List, Union, TYPE_CHECKING +from typing import List, Union from pyteal.ast.multi import MultiValue -from ..types import TealType -from ..ir import Op -from .expr import Expr -from .scratch import ScratchLoad, ScratchSlot - -if TYPE_CHECKING: - from ..compiler import CompileOptions +from pyteal.types import TealType +from pyteal.ir import Op +from pyteal.ast.expr import Expr +from pyteal.ast.scratch import ScratchLoad, ScratchSlot class MaybeValue(MultiValue): diff --git a/pyteal/ast/maybe_test.py b/pyteal/ast/maybe_test.py index 53daf7977..fa57ba5ca 100644 --- a/pyteal/ast/maybe_test.py +++ b/pyteal/ast/maybe_test.py @@ -1,52 +1,47 @@ -import pytest +import pyteal as pt -from .. import * - -# this is not necessary but mypy complains if it's not included -from .. import CompileOptions - -options = CompileOptions() +options = pt.CompileOptions() def test_maybe_value(): ops = ( - Op.app_global_get_ex, - Op.app_local_get_ex, - Op.asset_holding_get, - Op.asset_params_get, + pt.Op.app_global_get_ex, + pt.Op.app_local_get_ex, + pt.Op.asset_holding_get, + pt.Op.asset_params_get, ) - types = (TealType.uint64, TealType.bytes, TealType.anytype) + types = (pt.TealType.uint64, pt.TealType.bytes, pt.TealType.anytype) immedate_argv = ([], ["AssetFrozen"]) - argv = ([], [Int(0)], [Int(1), Int(2)]) + argv = ([], [pt.Int(0)], [pt.Int(1), pt.Int(2)]) for op in ops: for type in types: for iargs in immedate_argv: for args in argv: - expr = MaybeValue(op, type, immediate_args=iargs, args=args) + expr = pt.MaybeValue(op, type, immediate_args=iargs, args=args) assert expr.slotOk != expr.slotValue assert expr.output_slots == [expr.slotValue, expr.slotOk] - assert expr.hasValue().type_of() == TealType.uint64 - with TealComponent.Context.ignoreExprEquality(): - assert expr.hasValue().__teal__(options) == ScratchLoad( + assert expr.hasValue().type_of() == pt.TealType.uint64 + with pt.TealComponent.Context.ignoreExprEquality(): + assert expr.hasValue().__teal__(options) == pt.ScratchLoad( expr.slotOk ).__teal__(options) assert expr.value().type_of() == type - with TealComponent.Context.ignoreExprEquality(): - assert expr.value().__teal__(options) == ScratchLoad( + with pt.TealComponent.Context.ignoreExprEquality(): + assert expr.value().__teal__(options) == pt.ScratchLoad( expr.slotValue ).__teal__(options) - assert expr.type_of() == TealType.none + assert expr.type_of() == pt.TealType.none - expected_call = TealSimpleBlock( + expected_call = pt.TealSimpleBlock( [ - TealOp(expr, op, *iargs), - TealOp(None, Op.store, expr.slotOk), - TealOp(None, Op.store, expr.slotValue), + pt.TealOp(expr, op, *iargs), + pt.TealOp(None, pt.Op.store, expr.slotOk), + pt.TealOp(None, pt.Op.store, expr.slotValue), ] ) @@ -62,11 +57,11 @@ def test_maybe_value(): after_arg_2.setNextBlock(expected_call) expected.addIncoming() - expected = TealBlock.NormalizeBlocks(expected) + expected = pt.TealBlock.NormalizeBlocks(expected) actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected diff --git a/pyteal/ast/methodsig.py b/pyteal/ast/methodsig.py index de9bd9748..9417890de 100644 --- a/pyteal/ast/methodsig.py +++ b/pyteal/ast/methodsig.py @@ -1,14 +1,13 @@ from typing import TYPE_CHECKING -from pyteal.errors import TealInputError +from pyteal.errors import TealInputError from pyteal.types import TealType +from pyteal.ir import TealOp, Op, TealBlock -from ..types import TealType -from ..ir import TealOp, Op, TealBlock -from .leafexpr import LeafExpr +from pyteal.ast.leafexpr import LeafExpr if TYPE_CHECKING: - from ..compiler import CompileOptions + from pyteal.compiler import CompileOptions class MethodSignature(LeafExpr): diff --git a/pyteal/ast/methodsig_test.py b/pyteal/ast/methodsig_test.py index 6a941ea27..bec9d2cac 100644 --- a/pyteal/ast/methodsig_test.py +++ b/pyteal/ast/methodsig_test.py @@ -2,26 +2,26 @@ from pyteal.ast.methodsig import MethodSignature -from .. import * +import pyteal as pt def test_method(): expr = MethodSignature("add(uint64,uint64)uint64") - assert expr.type_of() == TealType.bytes + assert expr.type_of() == pt.TealType.bytes - expected = TealSimpleBlock( - [TealOp(expr, Op.method_signature, '"add(uint64,uint64)uint64"')] + expected = pt.TealSimpleBlock( + [pt.TealOp(expr, pt.Op.method_signature, '"add(uint64,uint64)uint64"')] ) - actual, _ = expr.__teal__(CompileOptions()) + actual, _ = expr.__teal__(pt.CompileOptions()) assert expected == actual def test_method_invalid(): - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): MethodSignature(114514) - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): MethodSignature(['"m0()void"', '"m1()uint64"']) - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): MethodSignature("") diff --git a/pyteal/ast/multi.py b/pyteal/ast/multi.py index de8aef39d..63a4dec11 100644 --- a/pyteal/ast/multi.py +++ b/pyteal/ast/multi.py @@ -1,14 +1,14 @@ from typing import Callable, List, Union, TYPE_CHECKING -from ..types import TealType -from ..ir import TealOp, Op, TealBlock -from .expr import Expr -from .leafexpr import LeafExpr -from .scratch import ScratchSlot -from .seq import Seq +from pyteal.types import TealType +from pyteal.ir import TealOp, Op, TealBlock +from pyteal.ast.expr import Expr +from pyteal.ast.leafexpr import LeafExpr +from pyteal.ast.scratch import ScratchSlot +from pyteal.ast.seq import Seq if TYPE_CHECKING: - from ..compiler import CompileOptions + from pyteal.compiler import CompileOptions class MultiValue(LeafExpr): diff --git a/pyteal/ast/multi_test.py b/pyteal/ast/multi_test.py index b710a027a..08c1c4093 100644 --- a/pyteal/ast/multi_test.py +++ b/pyteal/ast/multi_test.py @@ -1,48 +1,50 @@ from typing import List -from .. import * +import pyteal as pt -options = CompileOptions() +options = pt.CompileOptions() -def __test_single(expr: MultiValue): +def __test_single(expr: pt.MultiValue): assert expr.output_slots[0] != expr.output_slots[1] - with TealComponent.Context.ignoreExprEquality(): - assert expr.output_slots[0].load().__teal__(options) == ScratchLoad( + with pt.TealComponent.Context.ignoreExprEquality(): + assert expr.output_slots[0].load().__teal__(options) == pt.ScratchLoad( expr.output_slots[0] ).__teal__(options) - with TealComponent.Context.ignoreExprEquality(): - assert expr.output_slots[1].load().__teal__(options) == ScratchLoad( + with pt.TealComponent.Context.ignoreExprEquality(): + assert expr.output_slots[1].load().__teal__(options) == pt.ScratchLoad( expr.output_slots[1] ).__teal__(options) - assert expr.type_of() == TealType.none + assert expr.type_of() == pt.TealType.none -def __test_single_conditional(expr: MultiValue, op, args: List[Expr], iargs, reducer): +def __test_single_conditional( + expr: pt.MultiValue, op, args: List[pt.Expr], iargs, reducer +): __test_single(expr) - expected_call = TealSimpleBlock( + expected_call = pt.TealSimpleBlock( [ - TealOp(expr, op, *iargs), - TealOp(expr.output_slots[1].store(), Op.store, expr.output_slots[1]), - TealOp(expr.output_slots[0].store(), Op.store, expr.output_slots[0]), + pt.TealOp(expr, op, *iargs), + pt.TealOp(expr.output_slots[1].store(), pt.Op.store, expr.output_slots[1]), + pt.TealOp(expr.output_slots[0].store(), pt.Op.store, expr.output_slots[0]), ] ) ifExpr = ( - If(expr.output_slots[1].load()) + pt.If(expr.output_slots[1].load()) .Then(expr.output_slots[0].load()) - .Else(Bytes("None")) + .Else(pt.Bytes("None")) ) ifBlockStart, _ = ifExpr.__teal__(options) expected_call.setNextBlock(ifBlockStart) if len(args) == 0: - expected: TealBlock = expected_call + expected: pt.TealBlock = expected_call elif len(args) == 1: expected, after_arg = args[0].__teal__(options) after_arg.setNextBlock(expected_call) @@ -53,34 +55,36 @@ def __test_single_conditional(expr: MultiValue, op, args: List[Expr], iargs, red after_arg_2.setNextBlock(expected_call) expected.addIncoming() - expected = TealBlock.NormalizeBlocks(expected) + expected = pt.TealBlock.NormalizeBlocks(expected) actual, _ = expr.outputReducer(reducer).__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected -def __test_single_assert(expr: MultiValue, op, args: List[Expr], iargs, reducer): +def __test_single_assert(expr: pt.MultiValue, op, args: List[pt.Expr], iargs, reducer): __test_single(expr) - expected_call = TealSimpleBlock( + expected_call = pt.TealSimpleBlock( [ - TealOp(expr, op, *iargs), - TealOp(expr.output_slots[1].store(), Op.store, expr.output_slots[1]), - TealOp(expr.output_slots[0].store(), Op.store, expr.output_slots[0]), + pt.TealOp(expr, op, *iargs), + pt.TealOp(expr.output_slots[1].store(), pt.Op.store, expr.output_slots[1]), + pt.TealOp(expr.output_slots[0].store(), pt.Op.store, expr.output_slots[0]), ] ) - assertExpr = Seq(Assert(expr.output_slots[1].load()), expr.output_slots[0].load()) + assertExpr = pt.Seq( + pt.Assert(expr.output_slots[1].load()), expr.output_slots[0].load() + ) assertBlockStart, _ = assertExpr.__teal__(options) expected_call.setNextBlock(assertBlockStart) if len(args) == 0: - expected: TealBlock = expected_call + expected: pt.TealBlock = expected_call elif len(args) == 1: expected, after_arg = args[0].__teal__(options) after_arg.setNextBlock(expected_call) @@ -91,30 +95,30 @@ def __test_single_assert(expr: MultiValue, op, args: List[Expr], iargs, reducer) after_arg_2.setNextBlock(expected_call) expected.addIncoming() - expected = TealBlock.NormalizeBlocks(expected) + expected = pt.TealBlock.NormalizeBlocks(expected) actual, _ = expr.outputReducer(reducer).__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected def __test_single_with_vars( - expr: MultiValue, op, args: List[Expr], iargs, var1, var2, reducer + expr: pt.MultiValue, op, args: List[pt.Expr], iargs, var1, var2, reducer ): __test_single(expr) - expected_call = TealSimpleBlock( + expected_call = pt.TealSimpleBlock( [ - TealOp(expr, op, *iargs), - TealOp(expr.output_slots[1].store(), Op.store, expr.output_slots[1]), - TealOp(expr.output_slots[0].store(), Op.store, expr.output_slots[0]), + pt.TealOp(expr, op, *iargs), + pt.TealOp(expr.output_slots[1].store(), pt.Op.store, expr.output_slots[1]), + pt.TealOp(expr.output_slots[0].store(), pt.Op.store, expr.output_slots[0]), ] ) - varExpr = Seq( + varExpr = pt.Seq( var1.store(expr.output_slots[1].load()), var2.store(expr.output_slots[0].load()) ) varBlockStart, _ = varExpr.__teal__(options) @@ -122,7 +126,7 @@ def __test_single_with_vars( expected_call.setNextBlock(varBlockStart) if len(args) == 0: - expected: TealBlock = expected_call + expected: pt.TealBlock = expected_call elif len(args) == 1: expected, after_arg = args[0].__teal__(options) after_arg.setNextBlock(expected_call) @@ -133,54 +137,56 @@ def __test_single_with_vars( after_arg_2.setNextBlock(expected_call) expected.addIncoming() - expected = TealBlock.NormalizeBlocks(expected) + expected = pt.TealBlock.NormalizeBlocks(expected) actual, _ = expr.outputReducer(reducer).__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected def test_multi_value(): ops = ( - Op.app_global_get_ex, - Op.app_local_get_ex, - Op.asset_holding_get, - Op.asset_params_get, + pt.Op.app_global_get_ex, + pt.Op.app_local_get_ex, + pt.Op.asset_holding_get, + pt.Op.asset_params_get, ) - types = (TealType.uint64, TealType.bytes, TealType.anytype) + types = (pt.TealType.uint64, pt.TealType.bytes, pt.TealType.anytype) immedate_argv = ([], ["AssetFrozen"]) - argv = ([], [Int(0)], [Int(1), Int(2)]) + argv = ([], [pt.Int(0)], [pt.Int(1), pt.Int(2)]) for op in ops: for type in types: for iargs in immedate_argv: for args in argv: reducer = ( - lambda value, hasValue: If(hasValue) + lambda value, hasValue: pt.If(hasValue) .Then(value) - .Else(Bytes("None")) + .Else(pt.Bytes("None")) ) - expr = MultiValue( - op, [type, TealType.uint64], immediate_args=iargs, args=args + expr = pt.MultiValue( + op, [type, pt.TealType.uint64], immediate_args=iargs, args=args ) __test_single_conditional(expr, op, args, iargs, reducer) - reducer = lambda value, hasValue: Seq(Assert(hasValue), value) - expr = MultiValue( - op, [type, TealType.uint64], immediate_args=iargs, args=args + reducer = lambda value, hasValue: pt.Seq( # noqa: E731 + pt.Assert(hasValue), value + ) + expr = pt.MultiValue( + op, [type, pt.TealType.uint64], immediate_args=iargs, args=args ) __test_single_assert(expr, op, args, iargs, reducer) - hasValueVar = ScratchVar(TealType.uint64) - valueVar = ScratchVar(type) - reducer = lambda value, hasValue: Seq( + hasValueVar = pt.ScratchVar(pt.TealType.uint64) + valueVar = pt.ScratchVar(type) + reducer = lambda value, hasValue: pt.Seq( # noqa: E731 hasValueVar.store(hasValue), valueVar.store(value) ) - expr = MultiValue( - op, [type, TealType.uint64], immediate_args=iargs, args=args + expr = pt.MultiValue( + op, [type, pt.TealType.uint64], immediate_args=iargs, args=args ) __test_single_with_vars( expr, op, args, iargs, hasValueVar, valueVar, reducer diff --git a/pyteal/ast/naryexpr.py b/pyteal/ast/naryexpr.py index cd7d01452..f0aa564e8 100644 --- a/pyteal/ast/naryexpr.py +++ b/pyteal/ast/naryexpr.py @@ -1,12 +1,12 @@ from typing import Sequence, cast, TYPE_CHECKING -from ..types import TealType, require_type -from ..errors import TealInputError -from ..ir import TealOp, Op, TealSimpleBlock -from .expr import Expr +from pyteal.types import TealType, require_type +from pyteal.errors import TealInputError +from pyteal.ir import TealOp, Op, TealSimpleBlock +from pyteal.ast.expr import Expr if TYPE_CHECKING: - from ..compiler import CompileOptions + from pyteal.compiler import CompileOptions class NaryExpr(Expr): diff --git a/pyteal/ast/naryexpr_test.py b/pyteal/ast/naryexpr_test.py index 674c6d119..c9b740175 100644 --- a/pyteal/ast/naryexpr_test.py +++ b/pyteal/ast/naryexpr_test.py @@ -1,19 +1,16 @@ import pytest -from .. import * +import pyteal as pt -# this is not necessary but mypy complains if it's not included -from .. import CompileOptions - -options = CompileOptions() +options = pt.CompileOptions() def test_and_one(): - arg = Int(1) - expr = And(arg) - assert expr.type_of() == TealType.uint64 + arg = pt.Int(1) + expr = pt.And(arg) + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock([TealOp(arg, Op.int, 1)]) + expected = pt.TealSimpleBlock([pt.TealOp(arg, pt.Op.int, 1)]) actual, _ = expr.__teal__(options) @@ -21,87 +18,87 @@ def test_and_one(): def test_and_two(): - args = [Int(1), Int(2)] - expr = And(args[0], args[1]) - assert expr.type_of() == TealType.uint64 + args = [pt.Int(1), pt.Int(2)] + expr = pt.And(args[0], args[1]) + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(args[0], Op.int, 1), - TealOp(args[1], Op.int, 2), - TealOp(expr, Op.logic_and), + pt.TealOp(args[0], pt.Op.int, 1), + pt.TealOp(args[1], pt.Op.int, 2), + pt.TealOp(expr, pt.Op.logic_and), ] ) actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected def test_and_three(): - args = [Int(1), Int(2), Int(3)] - expr = And(args[0], args[1], args[2]) - assert expr.type_of() == TealType.uint64 + args = [pt.Int(1), pt.Int(2), pt.Int(3)] + expr = pt.And(args[0], args[1], args[2]) + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(args[0], Op.int, 1), - TealOp(args[1], Op.int, 2), - TealOp(expr, Op.logic_and), - TealOp(args[2], Op.int, 3), - TealOp(expr, Op.logic_and), + pt.TealOp(args[0], pt.Op.int, 1), + pt.TealOp(args[1], pt.Op.int, 2), + pt.TealOp(expr, pt.Op.logic_and), + pt.TealOp(args[2], pt.Op.int, 3), + pt.TealOp(expr, pt.Op.logic_and), ] ) actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected def test_and_overload(): - args = [Int(1), Int(2)] + args = [pt.Int(1), pt.Int(2)] expr = args[0].And(args[1]) - assert expr.type_of() == TealType.uint64 + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(args[0], Op.int, 1), - TealOp(args[1], Op.int, 2), - TealOp(expr, Op.logic_and), + pt.TealOp(args[0], pt.Op.int, 1), + pt.TealOp(args[1], pt.Op.int, 2), + pt.TealOp(expr, pt.Op.logic_and), ] ) actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected def test_and_invalid(): - with pytest.raises(TealInputError): - And() + with pytest.raises(pt.TealInputError): + pt.And() - with pytest.raises(TealTypeError): - And(Int(1), Txn.receiver()) + with pytest.raises(pt.TealTypeError): + pt.And(pt.Int(1), pt.Txn.receiver()) - with pytest.raises(TealTypeError): - And(Txn.receiver(), Int(1)) + with pytest.raises(pt.TealTypeError): + pt.And(pt.Txn.receiver(), pt.Int(1)) - with pytest.raises(TealTypeError): - And(Txn.receiver(), Txn.receiver()) + with pytest.raises(pt.TealTypeError): + pt.And(pt.Txn.receiver(), pt.Txn.receiver()) def test_or_one(): - arg = Int(1) - expr = Or(arg) - assert expr.type_of() == TealType.uint64 + arg = pt.Int(1) + expr = pt.Or(arg) + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock([TealOp(arg, Op.int, 1)]) + expected = pt.TealSimpleBlock([pt.TealOp(arg, pt.Op.int, 1)]) actual, _ = expr.__teal__(options) @@ -109,87 +106,87 @@ def test_or_one(): def test_or_two(): - args = [Int(1), Int(0)] - expr = Or(args[0], args[1]) - assert expr.type_of() == TealType.uint64 + args = [pt.Int(1), pt.Int(0)] + expr = pt.Or(args[0], args[1]) + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(args[0], Op.int, 1), - TealOp(args[1], Op.int, 0), - TealOp(expr, Op.logic_or), + pt.TealOp(args[0], pt.Op.int, 1), + pt.TealOp(args[1], pt.Op.int, 0), + pt.TealOp(expr, pt.Op.logic_or), ] ) actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected def test_or_three(): - args = [Int(0), Int(1), Int(2)] - expr = Or(args[0], args[1], args[2]) - assert expr.type_of() == TealType.uint64 + args = [pt.Int(0), pt.Int(1), pt.Int(2)] + expr = pt.Or(args[0], args[1], args[2]) + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(args[0], Op.int, 0), - TealOp(args[1], Op.int, 1), - TealOp(expr, Op.logic_or), - TealOp(args[2], Op.int, 2), - TealOp(expr, Op.logic_or), + pt.TealOp(args[0], pt.Op.int, 0), + pt.TealOp(args[1], pt.Op.int, 1), + pt.TealOp(expr, pt.Op.logic_or), + pt.TealOp(args[2], pt.Op.int, 2), + pt.TealOp(expr, pt.Op.logic_or), ] ) actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected def test_or_overload(): - args = [Int(1), Int(0)] + args = [pt.Int(1), pt.Int(0)] expr = args[0].Or(args[1]) - assert expr.type_of() == TealType.uint64 + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(args[0], Op.int, 1), - TealOp(args[1], Op.int, 0), - TealOp(expr, Op.logic_or), + pt.TealOp(args[0], pt.Op.int, 1), + pt.TealOp(args[1], pt.Op.int, 0), + pt.TealOp(expr, pt.Op.logic_or), ] ) actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected def test_or_invalid(): - with pytest.raises(TealInputError): - Or() + with pytest.raises(pt.TealInputError): + pt.Or() - with pytest.raises(TealTypeError): - Or(Int(1), Txn.receiver()) + with pytest.raises(pt.TealTypeError): + pt.Or(pt.Int(1), pt.Txn.receiver()) - with pytest.raises(TealTypeError): - Or(Txn.receiver(), Int(1)) + with pytest.raises(pt.TealTypeError): + pt.Or(pt.Txn.receiver(), pt.Int(1)) - with pytest.raises(TealTypeError): - Or(Txn.receiver(), Txn.receiver()) + with pytest.raises(pt.TealTypeError): + pt.Or(pt.Txn.receiver(), pt.Txn.receiver()) def test_concat_one(): - arg = Bytes("a") - expr = Concat(arg) - assert expr.type_of() == TealType.bytes + arg = pt.Bytes("a") + expr = pt.Concat(arg) + assert expr.type_of() == pt.TealType.bytes - expected = TealSimpleBlock([TealOp(arg, Op.byte, '"a"')]) + expected = pt.TealSimpleBlock([pt.TealOp(arg, pt.Op.byte, '"a"')]) actual, _ = expr.__teal__(options) @@ -197,56 +194,56 @@ def test_concat_one(): def test_concat_two(): - args = [Bytes("a"), Bytes("b")] - expr = Concat(args[0], args[1]) - assert expr.type_of() == TealType.bytes + args = [pt.Bytes("a"), pt.Bytes("b")] + expr = pt.Concat(args[0], args[1]) + assert expr.type_of() == pt.TealType.bytes - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(args[0], Op.byte, '"a"'), - TealOp(args[1], Op.byte, '"b"'), - TealOp(expr, Op.concat), + pt.TealOp(args[0], pt.Op.byte, '"a"'), + pt.TealOp(args[1], pt.Op.byte, '"b"'), + pt.TealOp(expr, pt.Op.concat), ] ) actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected def test_concat_three(): - args = [Bytes("a"), Bytes("b"), Bytes("c")] - expr = Concat(args[0], args[1], args[2]) - assert expr.type_of() == TealType.bytes + args = [pt.Bytes("a"), pt.Bytes("b"), pt.Bytes("c")] + expr = pt.Concat(args[0], args[1], args[2]) + assert expr.type_of() == pt.TealType.bytes - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(args[0], Op.byte, '"a"'), - TealOp(args[1], Op.byte, '"b"'), - TealOp(expr, Op.concat), - TealOp(args[2], Op.byte, '"c"'), - TealOp(expr, Op.concat), + pt.TealOp(args[0], pt.Op.byte, '"a"'), + pt.TealOp(args[1], pt.Op.byte, '"b"'), + pt.TealOp(expr, pt.Op.concat), + pt.TealOp(args[2], pt.Op.byte, '"c"'), + pt.TealOp(expr, pt.Op.concat), ] ) actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected def test_concat_invalid(): - with pytest.raises(TealInputError): - Concat() + with pytest.raises(pt.TealInputError): + pt.Concat() - with pytest.raises(TealTypeError): - Concat(Int(1), Txn.receiver()) + with pytest.raises(pt.TealTypeError): + pt.Concat(pt.Int(1), pt.Txn.receiver()) - with pytest.raises(TealTypeError): - Concat(Txn.receiver(), Int(1)) + with pytest.raises(pt.TealTypeError): + pt.Concat(pt.Txn.receiver(), pt.Int(1)) - with pytest.raises(TealTypeError): - Concat(Int(1), Int(2)) + with pytest.raises(pt.TealTypeError): + pt.Concat(pt.Int(1), pt.Int(2)) diff --git a/pyteal/ast/nonce.py b/pyteal/ast/nonce.py index ff61f5b30..ce007f322 100644 --- a/pyteal/ast/nonce.py +++ b/pyteal/ast/nonce.py @@ -1,13 +1,13 @@ from typing import TYPE_CHECKING -from ..errors import TealInputError -from .expr import Expr -from .seq import Seq -from .bytes import Bytes -from .unaryexpr import Pop +from pyteal.errors import TealInputError +from pyteal.ast.expr import Expr +from pyteal.ast.seq import Seq +from pyteal.ast.bytes import Bytes +from pyteal.ast.unaryexpr import Pop if TYPE_CHECKING: - from ..compiler import CompileOptions + from pyteal.compiler import CompileOptions class Nonce(Expr): diff --git a/pyteal/ast/nonce_test.py b/pyteal/ast/nonce_test.py index f5f7c16f7..21d165c02 100644 --- a/pyteal/ast/nonce_test.py +++ b/pyteal/ast/nonce_test.py @@ -1,48 +1,47 @@ import pytest -from .. import * +import pyteal as pt -# this is not necessary but mypy complains if it's not included -from .. import CompileOptions - -options = CompileOptions() +options = pt.CompileOptions() def test_nonce_has_return(): - exprWithReturn = Nonce( + exprWithReturn = pt.Nonce( "base32", "7Z5PWO2C6LFNQFGHWKSK5H47IQP5OJW2M3HA2QPXTY3WTNP5NU2MHBW27M", - Return(Int(1)), + pt.Return(pt.Int(1)), ) assert exprWithReturn.has_return() - exprWithoutReturn = Nonce( - "base32", "7Z5PWO2C6LFNQFGHWKSK5H47IQP5OJW2M3HA2QPXTY3WTNP5NU2MHBW27M", Int(1) + exprWithoutReturn = pt.Nonce( + "base32", + "7Z5PWO2C6LFNQFGHWKSK5H47IQP5OJW2M3HA2QPXTY3WTNP5NU2MHBW27M", + pt.Int(1), ) assert not exprWithoutReturn.has_return() def test_nonce_base32(): - arg = Int(1) - expr = Nonce( + arg = pt.Int(1) + expr = pt.Nonce( "base32", "7Z5PWO2C6LFNQFGHWKSK5H47IQP5OJW2M3HA2QPXTY3WTNP5NU2MHBW27M", arg ) - assert expr.type_of() == TealType.uint64 + assert expr.type_of() == pt.TealType.uint64 actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) # copying expr from actual.ops[0] and actual.ops[1] because they can't be determined from outside code. - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp( + pt.TealOp( actual.ops[0].expr, - Op.byte, + pt.Op.byte, "base32(7Z5PWO2C6LFNQFGHWKSK5H47IQP5OJW2M3HA2QPXTY3WTNP5NU2MHBW27M)", ), - TealOp(actual.ops[1].expr, Op.pop), - TealOp(arg, Op.int, 1), + pt.TealOp(actual.ops[1].expr, pt.Op.pop), + pt.TealOp(arg, pt.Op.int, 1), ] ) @@ -50,20 +49,20 @@ def test_nonce_base32(): def test_nonce_base32_empty(): - arg = Int(1) - expr = Nonce("base32", "", arg) - assert expr.type_of() == TealType.uint64 + arg = pt.Int(1) + expr = pt.Nonce("base32", "", arg) + assert expr.type_of() == pt.TealType.uint64 actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) # copying expr from actual.ops[0] and actual.ops[1] because they can't be determined from outside code. - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(actual.ops[0].expr, Op.byte, "base32()"), - TealOp(actual.ops[1].expr, Op.pop), - TealOp(arg, Op.int, 1), + pt.TealOp(actual.ops[0].expr, pt.Op.byte, "base32()"), + pt.TealOp(actual.ops[1].expr, pt.Op.pop), + pt.TealOp(arg, pt.Op.int, 1), ] ) @@ -71,20 +70,20 @@ def test_nonce_base32_empty(): def test_nonce_base64(): - arg = Txn.sender() - expr = Nonce("base64", "Zm9vYmE=", arg) - assert expr.type_of() == TealType.bytes + arg = pt.Txn.sender() + expr = pt.Nonce("base64", "Zm9vYmE=", arg) + assert expr.type_of() == pt.TealType.bytes actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) # copying expr from actual.ops[0] and actual.ops[1] because they can't be determined from outside code. - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(actual.ops[0].expr, Op.byte, "base64(Zm9vYmE=)"), - TealOp(actual.ops[1].expr, Op.pop), - TealOp(arg, Op.txn, "Sender"), + pt.TealOp(actual.ops[0].expr, pt.Op.byte, "base64(Zm9vYmE=)"), + pt.TealOp(actual.ops[1].expr, pt.Op.pop), + pt.TealOp(arg, pt.Op.txn, "Sender"), ] ) @@ -92,20 +91,20 @@ def test_nonce_base64(): def test_nonce_base64_empty(): - arg = Int(1) - expr = Nonce("base64", "", arg) - assert expr.type_of() == TealType.uint64 + arg = pt.Int(1) + expr = pt.Nonce("base64", "", arg) + assert expr.type_of() == pt.TealType.uint64 actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) # copying expr from actual.ops[0] and actual.ops[1] because they can't be determined from outside code. - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(actual.ops[0].expr, Op.byte, "base64()"), - TealOp(actual.ops[1].expr, Op.pop), - TealOp(arg, Op.int, 1), + pt.TealOp(actual.ops[0].expr, pt.Op.byte, "base64()"), + pt.TealOp(actual.ops[1].expr, pt.Op.pop), + pt.TealOp(arg, pt.Op.int, 1), ] ) @@ -113,20 +112,20 @@ def test_nonce_base64_empty(): def test_nonce_base16(): - arg = Int(1) - expr = Nonce("base16", "A21212EF", arg) - assert expr.type_of() == TealType.uint64 + arg = pt.Int(1) + expr = pt.Nonce("base16", "A21212EF", arg) + assert expr.type_of() == pt.TealType.uint64 actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) # copying expr from actual.ops[0] and actual.ops[1] because they can't be determined from outside code. - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(actual.ops[0].expr, Op.byte, "0xA21212EF"), - TealOp(actual.ops[1].expr, Op.pop), - TealOp(arg, Op.int, 1), + pt.TealOp(actual.ops[0].expr, pt.Op.byte, "0xA21212EF"), + pt.TealOp(actual.ops[1].expr, pt.Op.pop), + pt.TealOp(arg, pt.Op.int, 1), ] ) @@ -134,20 +133,20 @@ def test_nonce_base16(): def test_nonce_base16_prefix(): - arg = Int(1) - expr = Nonce("base16", "0xA21212EF", arg) - assert expr.type_of() == TealType.uint64 + arg = pt.Int(1) + expr = pt.Nonce("base16", "0xA21212EF", arg) + assert expr.type_of() == pt.TealType.uint64 actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) # copying expr from actual.ops[0] and actual.ops[1] because they can't be determined from outside code. - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(actual.ops[0].expr, Op.byte, "0xA21212EF"), - TealOp(actual.ops[1].expr, Op.pop), - TealOp(arg, Op.int, 1), + pt.TealOp(actual.ops[0].expr, pt.Op.byte, "0xA21212EF"), + pt.TealOp(actual.ops[1].expr, pt.Op.pop), + pt.TealOp(arg, pt.Op.int, 1), ] ) @@ -155,20 +154,20 @@ def test_nonce_base16_prefix(): def test_nonce_base16_empty(): - arg = Int(6) - expr = Nonce("base16", "", arg) - assert expr.type_of() == TealType.uint64 + arg = pt.Int(6) + expr = pt.Nonce("base16", "", arg) + assert expr.type_of() == pt.TealType.uint64 actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) # copying expr from actual.ops[0] and actual.ops[1] because they can't be determined from outside code. - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(actual.ops[0].expr, Op.byte, "0x"), - TealOp(actual.ops[1].expr, Op.pop), - TealOp(arg, Op.int, 6), + pt.TealOp(actual.ops[0].expr, pt.Op.byte, "0x"), + pt.TealOp(actual.ops[1].expr, pt.Op.pop), + pt.TealOp(arg, pt.Op.int, 6), ] ) @@ -176,18 +175,18 @@ def test_nonce_base16_empty(): def test_nonce_invalid(): - with pytest.raises(TealInputError): - Nonce("base23", "", Int(1)) + with pytest.raises(pt.TealInputError): + pt.Nonce("base23", "", pt.Int(1)) - with pytest.raises(TealInputError): - Nonce("base32", "Zm9vYmE=", Int(1)) + with pytest.raises(pt.TealInputError): + pt.Nonce("base32", "Zm9vYmE=", pt.Int(1)) - with pytest.raises(TealInputError): - Nonce("base64", "?????", Int(1)) + with pytest.raises(pt.TealInputError): + pt.Nonce("base64", "?????", pt.Int(1)) - with pytest.raises(TealInputError): - Nonce( + with pytest.raises(pt.TealInputError): + pt.Nonce( "base16", "7Z5PWO2C6LFNQFGHWKSK5H47IQP5OJW2M3HA2QPXTY3WTNP5NU2MHBW27M", - Int(1), + pt.Int(1), ) diff --git a/pyteal/ast/return_.py b/pyteal/ast/return_.py index fe10fcdd7..a19d894f0 100644 --- a/pyteal/ast/return_.py +++ b/pyteal/ast/return_.py @@ -1,13 +1,13 @@ from typing import TYPE_CHECKING -from ..types import TealType, require_type, types_match -from ..errors import verifyTealVersion, TealCompileError -from ..ir import TealOp, Op, TealBlock -from .expr import Expr -from .int import Int +from pyteal.types import TealType, require_type, types_match +from pyteal.errors import verifyTealVersion, TealCompileError +from pyteal.ir import TealOp, Op, TealBlock +from pyteal.ast.expr import Expr +from pyteal.ast.int import Int if TYPE_CHECKING: - from ..compiler import CompileOptions + from pyteal.compiler import CompileOptions class Return(Expr): diff --git a/pyteal/ast/return_test.py b/pyteal/ast/return_test.py index 8d8761ffc..85b1013b4 100644 --- a/pyteal/ast/return_test.py +++ b/pyteal/ast/return_test.py @@ -1,57 +1,56 @@ import pytest -from .. import * +import pyteal as pt -# this is not necessary but mypy complains if it's not included -from .. import CompileOptions - -options = CompileOptions(version=4) +options = pt.CompileOptions(version=4) def test_main_return(): - arg = Int(1) - expr = Return(arg) - assert expr.type_of() == TealType.none + arg = pt.Int(1) + expr = pt.Return(arg) + assert expr.type_of() == pt.TealType.none assert expr.has_return() - expected = TealSimpleBlock([TealOp(arg, Op.int, 1), TealOp(expr, Op.return_)]) + expected = pt.TealSimpleBlock( + [pt.TealOp(arg, pt.Op.int, 1), pt.TealOp(expr, pt.Op.return_)] + ) actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected def test_main_return_invalid(): - with pytest.raises(TealCompileError): - Return(Txn.receiver()).__teal__(options) + with pytest.raises(pt.TealCompileError): + pt.Return(pt.Txn.receiver()).__teal__(options) - with pytest.raises(TealCompileError): - Return().__teal__(options) + with pytest.raises(pt.TealCompileError): + pt.Return().__teal__(options) def test_subroutine_return_value(): cases = ( - (TealType.uint64, Int(1), Op.int, 1), - (TealType.bytes, Bytes("value"), Op.byte, '"value"'), - (TealType.anytype, Int(1), Op.int, 1), - (TealType.anytype, Bytes("value"), Op.byte, '"value"'), + (pt.TealType.uint64, pt.Int(1), pt.Op.int, 1), + (pt.TealType.bytes, pt.Bytes("value"), pt.Op.byte, '"value"'), + (pt.TealType.anytype, pt.Int(1), pt.Op.int, 1), + (pt.TealType.anytype, pt.Bytes("value"), pt.Op.byte, '"value"'), ) for (tealType, value, op, opValue) in cases: - expr = Return(value) + expr = pt.Return(value) def mySubroutine(): return expr - subroutine = SubroutineDefinition(mySubroutine, tealType) + subroutine = pt.SubroutineDefinition(mySubroutine, tealType) - assert expr.type_of() == TealType.none + assert expr.type_of() == pt.TealType.none assert expr.has_return() - expected = TealSimpleBlock( - [TealOp(value, op, opValue), TealOp(expr, Op.retsub)] + expected = pt.TealSimpleBlock( + [pt.TealOp(value, op, opValue), pt.TealOp(expr, pt.Op.retsub)] ) options.setSubroutine(subroutine) @@ -59,64 +58,64 @@ def mySubroutine(): options.setSubroutine(None) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected def test_subroutine_return_value_invalid(): cases = ( - (TealType.bytes, Int(1)), - (TealType.uint64, Bytes("value")), + (pt.TealType.bytes, pt.Int(1)), + (pt.TealType.uint64, pt.Bytes("value")), ) for (tealType, value) in cases: - expr = Return(value) + expr = pt.Return(value) def mySubroutine(): return expr - subroutine = SubroutineDefinition(mySubroutine, tealType) + subroutine = pt.SubroutineDefinition(mySubroutine, tealType) options.setSubroutine(subroutine) - with pytest.raises(TealCompileError): + with pytest.raises(pt.TealCompileError): expr.__teal__(options) options.setSubroutine(None) def test_subroutine_return_none(): - expr = Return() + expr = pt.Return() def mySubroutine(): return expr - subroutine = SubroutineDefinition(mySubroutine, TealType.none) + subroutine = pt.SubroutineDefinition(mySubroutine, pt.TealType.none) - assert expr.type_of() == TealType.none + assert expr.type_of() == pt.TealType.none assert expr.has_return() - expected = TealSimpleBlock([TealOp(expr, Op.retsub)]) + expected = pt.TealSimpleBlock([pt.TealOp(expr, pt.Op.retsub)]) options.setSubroutine(subroutine) actual, _ = expr.__teal__(options) options.setSubroutine(None) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected def test_subroutine_return_none_invalid(): - for value in (Int(1), Bytes("value")): - expr = Return(value) + for value in (pt.Int(1), pt.Bytes("value")): + expr = pt.Return(value) def mySubroutine(): return expr - subroutine = SubroutineDefinition(mySubroutine, TealType.none) + subroutine = pt.SubroutineDefinition(mySubroutine, pt.TealType.none) options.setSubroutine(subroutine) - with pytest.raises(TealCompileError): + with pytest.raises(pt.TealCompileError): expr.__teal__(options) options.setSubroutine(None) diff --git a/pyteal/ast/scratch.py b/pyteal/ast/scratch.py index ba738f64c..02c109fd0 100644 --- a/pyteal/ast/scratch.py +++ b/pyteal/ast/scratch.py @@ -1,12 +1,12 @@ from typing import cast, TYPE_CHECKING, Optional -from ..types import TealType, require_type -from ..config import NUM_SLOTS -from ..errors import TealInputError, TealInternalError -from .expr import Expr +from pyteal.types import TealType, require_type +from pyteal.config import NUM_SLOTS +from pyteal.errors import TealInputError, TealInternalError +from pyteal.ast.expr import Expr if TYPE_CHECKING: - from ..compiler import CompileOptions + from pyteal.compiler import CompileOptions class ScratchSlot: @@ -87,7 +87,7 @@ def has_return(self): return False def __teal__(self, options: "CompileOptions"): - from ..ir import TealOp, Op, TealBlock + from pyteal.ir import TealOp, Op, TealBlock op = TealOp(self, Op.int, self.slot) return TealBlock.FromOp(options, op) @@ -143,7 +143,7 @@ def __str__(self): return "(Load {})".format(self.slot if self.slot else self.index_expression) def __teal__(self, options: "CompileOptions"): - from ..ir import TealOp, Op, TealBlock + from pyteal.ir import TealOp, Op, TealBlock if self.index_expression is not None: op = TealOp(self, Op.loads) @@ -203,7 +203,7 @@ def __str__(self): ) def __teal__(self, options: "CompileOptions"): - from ..ir import TealOp, Op, TealBlock + from pyteal.ir import TealOp, Op, TealBlock if self.index_expression is not None: op = TealOp(self, Op.stores) @@ -246,7 +246,7 @@ def __str__(self): return "(StackStore {})".format(self.slot) def __teal__(self, options: "CompileOptions"): - from ..ir import TealOp, Op, TealBlock + from pyteal.ir import TealOp, Op, TealBlock op = TealOp(self, Op.store, self.slot) return TealBlock.FromOp(options, op) diff --git a/pyteal/ast/scratch_test.py b/pyteal/ast/scratch_test.py index 6379efb1c..6aee2c41c 100644 --- a/pyteal/ast/scratch_test.py +++ b/pyteal/ast/scratch_test.py @@ -1,42 +1,40 @@ import pytest -from .. import * +import pyteal as pt -# this is not necessary but mypy complains if it's not included -from .. import CompileOptions - -options = CompileOptions() +options = pt.CompileOptions() def test_scratch_slot(): - slot = ScratchSlot() + slot = pt.ScratchSlot() assert slot == slot assert slot.__hash__() == slot.__hash__() - assert slot != ScratchSlot() + assert slot != pt.ScratchSlot() - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert ( slot.store().__teal__(options)[0] - == ScratchStackStore(slot).__teal__(options)[0] + == pt.ScratchStackStore(slot).__teal__(options)[0] ) assert ( - slot.store(Int(1)).__teal__(options)[0] - == ScratchStore(slot, Int(1)).__teal__(options)[0] + slot.store(pt.Int(1)).__teal__(options)[0] + == pt.ScratchStore(slot, pt.Int(1)).__teal__(options)[0] ) - assert slot.load().type_of() == TealType.anytype - assert slot.load(TealType.uint64).type_of() == TealType.uint64 + assert slot.load().type_of() == pt.TealType.anytype + assert slot.load(pt.TealType.uint64).type_of() == pt.TealType.uint64 assert ( - slot.load().__teal__(options)[0] == ScratchLoad(slot).__teal__(options)[0] + slot.load().__teal__(options)[0] + == pt.ScratchLoad(slot).__teal__(options)[0] ) def test_scratch_load_default(): - slot = ScratchSlot() - expr = ScratchLoad(slot) - assert expr.type_of() == TealType.anytype + slot = pt.ScratchSlot() + expr = pt.ScratchLoad(slot) + assert expr.type_of() == pt.TealType.anytype - expected = TealSimpleBlock([TealOp(expr, Op.load, slot)]) + expected = pt.TealSimpleBlock([pt.TealOp(expr, pt.Op.load, slot)]) actual, _ = expr.__teal__(options) @@ -44,25 +42,25 @@ def test_scratch_load_default(): def test_scratch_load_index_expression(): - expr = ScratchLoad(slot=None, index_expression=Int(1337)) - assert expr.type_of() == TealType.anytype + expr = pt.ScratchLoad(slot=None, index_expression=pt.Int(1337)) + assert expr.type_of() == pt.TealType.anytype - expected = TealSimpleBlock([TealOp(Int(1337), Op.int, 1337)]) - expected.setNextBlock(TealSimpleBlock([TealOp(None, Op.loads)])) + expected = pt.TealSimpleBlock([pt.TealOp(pt.Int(1337), pt.Op.int, 1337)]) + expected.setNextBlock(pt.TealSimpleBlock([pt.TealOp(None, pt.Op.loads)])) actual, _ = expr.__teal__(options) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected def test_scratch_load_type(): - for type in (TealType.uint64, TealType.bytes, TealType.anytype): - slot = ScratchSlot() - expr = ScratchLoad(slot, type) + for type in (pt.TealType.uint64, pt.TealType.bytes, pt.TealType.anytype): + slot = pt.ScratchSlot() + expr = pt.ScratchLoad(slot, type) assert expr.type_of() == type - expected = TealSimpleBlock([TealOp(expr, Op.load, slot)]) + expected = pt.TealSimpleBlock([pt.TealOp(expr, pt.Op.load, slot)]) actual, _ = expr.__teal__(options) @@ -71,17 +69,17 @@ def test_scratch_load_type(): def test_scratch_store(): for value in ( - Int(1), - Bytes("test"), - App.globalGet(Bytes("key")), - If(Int(1), Int(2), Int(3)), + pt.Int(1), + pt.Bytes("test"), + pt.App.globalGet(pt.Bytes("key")), + pt.If(pt.Int(1), pt.Int(2), pt.Int(3)), ): - slot = ScratchSlot() - expr = ScratchStore(slot, value) - assert expr.type_of() == TealType.none + slot = pt.ScratchSlot() + expr = pt.ScratchStore(slot, value) + assert expr.type_of() == pt.TealType.none expected, valueEnd = value.__teal__(options) - storeBlock = TealSimpleBlock([TealOp(expr, Op.store, slot)]) + storeBlock = pt.TealSimpleBlock([pt.TealOp(expr, pt.Op.store, slot)]) valueEnd.setNextBlock(storeBlock) actual, _ = expr.__teal__(options) @@ -91,33 +89,33 @@ def test_scratch_store(): def test_scratch_store_index_expression(): for value in ( - Int(1), - Bytes("test"), - App.globalGet(Bytes("key")), - If(Int(1), Int(2), Int(3)), + pt.Int(1), + pt.Bytes("test"), + pt.App.globalGet(pt.Bytes("key")), + pt.If(pt.Int(1), pt.Int(2), pt.Int(3)), ): - expr = ScratchStore(slot=None, value=value, index_expression=Int(1337)) - assert expr.type_of() == TealType.none + expr = pt.ScratchStore(slot=None, value=value, index_expression=pt.Int(1337)) + assert expr.type_of() == pt.TealType.none - expected = TealSimpleBlock([TealOp(None, Op.int, 1337)]) + expected = pt.TealSimpleBlock([pt.TealOp(None, pt.Op.int, 1337)]) valueStart, valueEnd = value.__teal__(options) expected.setNextBlock(valueStart) - storeBlock = TealSimpleBlock([TealOp(expr, Op.stores)]) + storeBlock = pt.TealSimpleBlock([pt.TealOp(expr, pt.Op.stores)]) valueEnd.setNextBlock(storeBlock) actual, _ = expr.__teal__(options) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected def test_scratch_stack_store(): - slot = ScratchSlot() - expr = ScratchStackStore(slot) - assert expr.type_of() == TealType.none + slot = pt.ScratchSlot() + expr = pt.ScratchStackStore(slot) + assert expr.type_of() == pt.TealType.none - expected = TealSimpleBlock([TealOp(expr, Op.store, slot)]) + expected = pt.TealSimpleBlock([pt.TealOp(expr, pt.Op.store, slot)]) actual, _ = expr.__teal__(options) @@ -125,11 +123,11 @@ def test_scratch_stack_store(): def test_scratch_assign_id(): - slot = ScratchSlot(255) - expr = ScratchStackStore(slot) - assert expr.type_of() == TealType.none + slot = pt.ScratchSlot(255) + expr = pt.ScratchStackStore(slot) + assert expr.type_of() == pt.TealType.none - expected = TealSimpleBlock([TealOp(expr, Op.store, slot)]) + expected = pt.TealSimpleBlock([pt.TealOp(expr, pt.Op.store, slot)]) actual, _ = expr.__teal__(options) @@ -137,26 +135,26 @@ def test_scratch_assign_id(): def test_scratch_assign_id_invalid(): - with pytest.raises(TealInputError): - slot = ScratchSlot(-1) + with pytest.raises(pt.TealInputError): + pt.ScratchSlot(-1) - with pytest.raises(TealInputError): - slot = ScratchSlot(NUM_SLOTS) + with pytest.raises(pt.TealInputError): + pt.ScratchSlot(pt.NUM_SLOTS) def test_scratch_index(): - slot = ScratchSlot() + slot = pt.ScratchSlot() - index = ScratchIndex(slot) + index = pt.ScratchIndex(slot) assert index.slot is slot assert str(index) == "(ScratchIndex " + str(slot) + ")" - assert index.type_of() == TealType.uint64 + assert index.type_of() == pt.TealType.uint64 assert not index.has_return() - expected = TealSimpleBlock([TealOp(index, Op.int, slot)]) + expected = pt.TealSimpleBlock([pt.TealOp(index, pt.Op.int, slot)]) actual, _ = index.__teal__(options) assert actual == expected diff --git a/pyteal/ast/scratchvar.py b/pyteal/ast/scratchvar.py index 9b335e6bf..81ce27fff 100644 --- a/pyteal/ast/scratchvar.py +++ b/pyteal/ast/scratchvar.py @@ -1,8 +1,8 @@ -from ..errors import TealInputError -from ..types import TealType, require_type +from pyteal.errors import TealInputError +from pyteal.types import TealType, require_type -from .expr import Expr -from .scratch import ScratchSlot, ScratchLoad, ScratchStore +from pyteal.ast.expr import Expr +from pyteal.ast.scratch import ScratchSlot, ScratchLoad, ScratchStore class ScratchVar: diff --git a/pyteal/ast/scratchvar_test.py b/pyteal/ast/scratchvar_test.py index b757db862..e513f51a1 100644 --- a/pyteal/ast/scratchvar_test.py +++ b/pyteal/ast/scratchvar_test.py @@ -1,217 +1,214 @@ import pytest -from .. import * +import pyteal as pt -# this is not necessary but mypy complains if it's not included -from .. import CompileOptions - -options = CompileOptions() +options = pt.CompileOptions() def test_scratchvar_type(): - myvar_default = ScratchVar() - assert myvar_default.storage_type() == TealType.anytype - assert myvar_default.store(Bytes("value")).type_of() == TealType.none - assert myvar_default.load().type_of() == TealType.anytype + myvar_default = pt.ScratchVar() + assert myvar_default.storage_type() == pt.TealType.anytype + assert myvar_default.store(pt.Bytes("value")).type_of() == pt.TealType.none + assert myvar_default.load().type_of() == pt.TealType.anytype - with pytest.raises(TealTypeError): - myvar_default.store(Pop(Int(1))) + with pytest.raises(pt.TealTypeError): + myvar_default.store(pt.Pop(pt.Int(1))) - myvar_int = ScratchVar(TealType.uint64) - assert myvar_int.storage_type() == TealType.uint64 - assert myvar_int.store(Int(1)).type_of() == TealType.none - assert myvar_int.load().type_of() == TealType.uint64 + myvar_int = pt.ScratchVar(pt.TealType.uint64) + assert myvar_int.storage_type() == pt.TealType.uint64 + assert myvar_int.store(pt.Int(1)).type_of() == pt.TealType.none + assert myvar_int.load().type_of() == pt.TealType.uint64 - with pytest.raises(TealTypeError): - myvar_int.store(Bytes("value")) + with pytest.raises(pt.TealTypeError): + myvar_int.store(pt.Bytes("value")) - with pytest.raises(TealTypeError): - myvar_int.store(Pop(Int(1))) + with pytest.raises(pt.TealTypeError): + myvar_int.store(pt.Pop(pt.Int(1))) - myvar_bytes = ScratchVar(TealType.bytes) - assert myvar_bytes.storage_type() == TealType.bytes - assert myvar_bytes.store(Bytes("value")).type_of() == TealType.none - assert myvar_bytes.load().type_of() == TealType.bytes + myvar_bytes = pt.ScratchVar(pt.TealType.bytes) + assert myvar_bytes.storage_type() == pt.TealType.bytes + assert myvar_bytes.store(pt.Bytes("value")).type_of() == pt.TealType.none + assert myvar_bytes.load().type_of() == pt.TealType.bytes - with pytest.raises(TealTypeError): - myvar_bytes.store(Int(0)) + with pytest.raises(pt.TealTypeError): + myvar_bytes.store(pt.Int(0)) - with pytest.raises(TealTypeError): - myvar_bytes.store(Pop(Int(1))) + with pytest.raises(pt.TealTypeError): + myvar_bytes.store(pt.Pop(pt.Int(1))) def test_scratchvar_store(): - myvar = ScratchVar(TealType.bytes) - arg = Bytes("value") + myvar = pt.ScratchVar(pt.TealType.bytes) + arg = pt.Bytes("value") expr = myvar.store(arg) - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(arg, Op.byte, '"value"'), - TealOp(expr, Op.store, myvar.slot), + pt.TealOp(arg, pt.Op.byte, '"value"'), + pt.TealOp(expr, pt.Op.store, myvar.slot), ] ) actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected def test_scratchvar_load(): - myvar = ScratchVar() + myvar = pt.ScratchVar() expr = myvar.load() - expected = TealSimpleBlock([TealOp(expr, Op.load, myvar.slot)]) + expected = pt.TealSimpleBlock([pt.TealOp(expr, pt.Op.load, myvar.slot)]) actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected def test_scratchvar_index(): - myvar = ScratchVar() + myvar = pt.ScratchVar() expr = myvar.index() - expected = TealSimpleBlock([TealOp(expr, Op.int, myvar.slot)]) + expected = pt.TealSimpleBlock([pt.TealOp(expr, pt.Op.int, myvar.slot)]) actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected def test_scratchvar_assign_store(): slotId = 2 - myvar = ScratchVar(TealType.uint64, slotId) - arg = Int(10) + myvar = pt.ScratchVar(pt.TealType.uint64, slotId) + arg = pt.Int(10) expr = myvar.store(arg) - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(arg, Op.int, 10), - TealOp(expr, Op.store, myvar.slot), + pt.TealOp(arg, pt.Op.int, 10), + pt.TealOp(expr, pt.Op.store, myvar.slot), ] ) actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected def test_scratchvar_assign_load(): slotId = 5 - myvar = ScratchVar(slotId=slotId) + myvar = pt.ScratchVar(slotId=slotId) expr = myvar.load() - expected = TealSimpleBlock([TealOp(expr, Op.load, myvar.slot)]) + expected = pt.TealSimpleBlock([pt.TealOp(expr, pt.Op.load, myvar.slot)]) actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected def test_dynamic_scratchvar_type(): - myvar_default = DynamicScratchVar() - assert myvar_default.storage_type() == TealType.anytype - assert myvar_default.store(Bytes("value")).type_of() == TealType.none - assert myvar_default.load().type_of() == TealType.anytype + myvar_default = pt.DynamicScratchVar() + assert myvar_default.storage_type() == pt.TealType.anytype + assert myvar_default.store(pt.Bytes("value")).type_of() == pt.TealType.none + assert myvar_default.load().type_of() == pt.TealType.anytype - with pytest.raises(TealTypeError): - myvar_default.store(Pop(Int(1))) + with pytest.raises(pt.TealTypeError): + myvar_default.store(pt.Pop(pt.Int(1))) - myvar_int = DynamicScratchVar(TealType.uint64) - assert myvar_int.storage_type() == TealType.uint64 - assert myvar_int.store(Int(1)).type_of() == TealType.none - assert myvar_int.load().type_of() == TealType.uint64 + myvar_int = pt.DynamicScratchVar(pt.TealType.uint64) + assert myvar_int.storage_type() == pt.TealType.uint64 + assert myvar_int.store(pt.Int(1)).type_of() == pt.TealType.none + assert myvar_int.load().type_of() == pt.TealType.uint64 - with pytest.raises(TealTypeError): - myvar_int.store(Bytes("value")) + with pytest.raises(pt.TealTypeError): + myvar_int.store(pt.Bytes("value")) - with pytest.raises(TealTypeError): - myvar_int.store(Pop(Int(1))) + with pytest.raises(pt.TealTypeError): + myvar_int.store(pt.Pop(pt.Int(1))) - myvar_bytes = DynamicScratchVar(TealType.bytes) - assert myvar_bytes.storage_type() == TealType.bytes - assert myvar_bytes.store(Bytes("value")).type_of() == TealType.none - assert myvar_bytes.load().type_of() == TealType.bytes + myvar_bytes = pt.DynamicScratchVar(pt.TealType.bytes) + assert myvar_bytes.storage_type() == pt.TealType.bytes + assert myvar_bytes.store(pt.Bytes("value")).type_of() == pt.TealType.none + assert myvar_bytes.load().type_of() == pt.TealType.bytes - with pytest.raises(TealTypeError): - myvar_bytes.store(Int(0)) + with pytest.raises(pt.TealTypeError): + myvar_bytes.store(pt.Int(0)) - with pytest.raises(TealTypeError): - myvar_bytes.store(Pop(Int(1))) + with pytest.raises(pt.TealTypeError): + myvar_bytes.store(pt.Pop(pt.Int(1))) def test_dynamic_scratchvar_load(): - myvar = DynamicScratchVar() + myvar = pt.DynamicScratchVar() expr = myvar.load() - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(ScratchLoad(myvar.slot), Op.load, myvar.slot), - TealOp(expr, Op.loads), + pt.TealOp(pt.ScratchLoad(myvar.slot), pt.Op.load, myvar.slot), + pt.TealOp(expr, pt.Op.loads), ] ) actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected def test_dynamic_scratchvar_store(): - myvar = DynamicScratchVar(TealType.bytes) - arg = Bytes("value") + myvar = pt.DynamicScratchVar(pt.TealType.bytes) + arg = pt.Bytes("value") expr = myvar.store(arg) - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(ScratchLoad(myvar.slot), Op.load, myvar.slot), - TealOp(arg, Op.byte, '"value"'), - TealOp(expr, Op.stores), + pt.TealOp(pt.ScratchLoad(myvar.slot), pt.Op.load, myvar.slot), + pt.TealOp(arg, pt.Op.byte, '"value"'), + pt.TealOp(expr, pt.Op.stores), ] ) actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected def test_dynamic_scratchvar_index(): - myvar = DynamicScratchVar() + myvar = pt.DynamicScratchVar() expr = myvar.index() - expected = TealSimpleBlock([TealOp(expr, Op.load, myvar.slot)]) + expected = pt.TealSimpleBlock([pt.TealOp(expr, pt.Op.load, myvar.slot)]) actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected def test_dynamic_scratchvar_cannot_set_index_to_another_dynamic(): - myvar = DynamicScratchVar() + myvar = pt.DynamicScratchVar() myvar.load() - regvar = ScratchVar() + regvar = pt.ScratchVar() myvar.set_index(regvar) - dynvar = DynamicScratchVar() + dynvar = pt.DynamicScratchVar() - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): myvar.set_index(dynvar) diff --git a/pyteal/ast/seq.py b/pyteal/ast/seq.py index 15ca8efc1..4185cd5cc 100644 --- a/pyteal/ast/seq.py +++ b/pyteal/ast/seq.py @@ -1,12 +1,12 @@ from typing import List, TYPE_CHECKING, overload -from ..types import TealType, require_type -from ..errors import TealInputError -from ..ir import TealSimpleBlock -from .expr import Expr +from pyteal.types import TealType, require_type +from pyteal.errors import TealInputError +from pyteal.ir import TealSimpleBlock +from pyteal.ast.expr import Expr if TYPE_CHECKING: - from ..compiler import CompileOptions + from pyteal.compiler import CompileOptions class Seq(Expr): diff --git a/pyteal/ast/seq_test.py b/pyteal/ast/seq_test.py index 0487a558f..be0a84198 100644 --- a/pyteal/ast/seq_test.py +++ b/pyteal/ast/seq_test.py @@ -1,19 +1,16 @@ import pytest -from .. import * +import pyteal as pt -# this is not necessary but mypy complains if it's not included -from .. import CompileOptions - -options = CompileOptions() +options = pt.CompileOptions() def test_seq_zero(): - for expr in (Seq(), Seq([])): - assert expr.type_of() == TealType.none + for expr in (pt.Seq(), pt.Seq([])): + assert expr.type_of() == pt.TealType.none assert not expr.has_return() - expected = TealSimpleBlock([]) + expected = pt.TealSimpleBlock([]) actual, _ = expr.__teal__(options) @@ -21,43 +18,43 @@ def test_seq_zero(): def test_seq_one(): - items = [Int(0)] - expr = Seq(items) - assert expr.type_of() == TealType.uint64 + items = [pt.Int(0)] + expr = pt.Seq(items) + assert expr.type_of() == pt.TealType.uint64 expected, _ = items[0].__teal__(options) actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected def test_seq_two(): - items = [App.localPut(Int(0), Bytes("key"), Int(1)), Int(7)] - expr = Seq(items) + items = [pt.App.localPut(pt.Int(0), pt.Bytes("key"), pt.Int(1)), pt.Int(7)] + expr = pt.Seq(items) assert expr.type_of() == items[-1].type_of() expected, first_end = items[0].__teal__(options) first_end.setNextBlock(items[1].__teal__(options)[0]) expected.addIncoming() - expected = TealBlock.NormalizeBlocks(expected) + expected = pt.TealBlock.NormalizeBlocks(expected) actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected def test_seq_three(): items = [ - App.localPut(Int(0), Bytes("key1"), Int(1)), - App.localPut(Int(1), Bytes("key2"), Bytes("value2")), - Pop(Bytes("end")), + pt.App.localPut(pt.Int(0), pt.Bytes("key1"), pt.Int(1)), + pt.App.localPut(pt.Int(1), pt.Bytes("key2"), pt.Bytes("value2")), + pt.Pop(pt.Bytes("end")), ] - expr = Seq(items) + expr = pt.Seq(items) assert expr.type_of() == items[-1].type_of() expected, first_end = items[0].__teal__(options) @@ -67,42 +64,49 @@ def test_seq_three(): second_end.setNextBlock(third_start) expected.addIncoming() - expected = TealBlock.NormalizeBlocks(expected) + expected = pt.TealBlock.NormalizeBlocks(expected) actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected def test_seq_has_return(): - exprWithReturn = Seq([App.localPut(Int(0), Bytes("key1"), Int(1)), Return(Int(1))]) + exprWithReturn = pt.Seq( + [ + pt.App.localPut(pt.Int(0), pt.Bytes("key1"), pt.Int(1)), + pt.Return(pt.Int(1)), + ] + ) assert exprWithReturn.has_return() - exprWithoutReturn = Seq([App.localPut(Int(0), Bytes("key1"), Int(1)), Int(1)]) + exprWithoutReturn = pt.Seq( + [pt.App.localPut(pt.Int(0), pt.Bytes("key1"), pt.Int(1)), pt.Int(1)] + ) assert not exprWithoutReturn.has_return() def test_seq_invalid(): - with pytest.raises(TealTypeError): - Seq([Int(1), Pop(Int(2))]) + with pytest.raises(pt.TealTypeError): + pt.Seq([pt.Int(1), pt.Pop(pt.Int(2))]) - with pytest.raises(TealTypeError): - Seq([Int(1), Int(2)]) + with pytest.raises(pt.TealTypeError): + pt.Seq([pt.Int(1), pt.Int(2)]) - with pytest.raises(TealTypeError): - Seq([Seq([Pop(Int(1)), Int(2)]), Int(3)]) + with pytest.raises(pt.TealTypeError): + pt.Seq([pt.Seq([pt.Pop(pt.Int(1)), pt.Int(2)]), pt.Int(3)]) def test_seq_overloads_equivalence(): items = [ - App.localPut(Int(0), Bytes("key1"), Int(1)), - App.localPut(Int(1), Bytes("key2"), Bytes("value2")), - Pop(Bytes("end")), + pt.App.localPut(pt.Int(0), pt.Bytes("key1"), pt.Int(1)), + pt.App.localPut(pt.Int(1), pt.Bytes("key2"), pt.Bytes("value2")), + pt.Pop(pt.Bytes("end")), ] - expr1 = Seq(items) - expr2 = Seq(*items) + expr1 = pt.Seq(items) + expr2 = pt.Seq(*items) expected = expr1.__teal__(options) actual = expr2.__teal__(options) diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index baa7d3db4..c92dc4625 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -14,17 +14,17 @@ Any, ) -from ..errors import TealInputError, verifyTealVersion -from ..ir import TealOp, Op, TealBlock -from ..types import TealType +from pyteal.errors import TealInputError, verifyTealVersion +from pyteal.ir import TealOp, Op, TealBlock +from pyteal.types import TealType -from .expr import Expr -from .seq import Seq -from .scratchvar import DynamicScratchVar, ScratchVar -from . import abi +from pyteal.ast import abi +from pyteal.ast.expr import Expr +from pyteal.ast.seq import Seq +from pyteal.ast.scratchvar import DynamicScratchVar, ScratchVar if TYPE_CHECKING: - from ..compiler import CompileOptions + from pyteal.compiler import CompileOptions class SubroutineDefinition: diff --git a/pyteal/ast/subroutine_test.py b/pyteal/ast/subroutine_test.py index f93abf75e..9f1f91c7b 100644 --- a/pyteal/ast/subroutine_test.py +++ b/pyteal/ast/subroutine_test.py @@ -1,44 +1,43 @@ from typing import List, Literal import pytest -from .. import * -from .subroutine import evaluateSubroutine +import pyteal as pt +from pyteal.ast.subroutine import evaluateSubroutine -# this is not necessary but mypy complains if it's not included -from .. import CompileOptions, Return - -options = CompileOptions(version=6) +options = pt.CompileOptions(version=4) def test_subroutine_definition(): def fn0Args(): - return Return() + return pt.Return() def fn1Args(a1): - return Return() + return pt.Return() def fn2Args(a1, a2): - return Return() + return pt.Return() def fn10Args(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10): - return Return() + return pt.Return() - lam0Args = lambda: Return() - lam1Args = lambda a1: Return() - lam2Args = lambda a1, a2: Return() - lam10Args = lambda a1, a2, a3, a4, a5, a6, a7, a8, a9, a10: Return() + lam0Args = lambda: pt.Return() # noqa: E731 + lam1Args = lambda a1: pt.Return() # noqa: E731 + lam2Args = lambda a1, a2: pt.Return() # noqa: E731 + lam10Args = ( + lambda a1, a2, a3, a4, a5, a6, a7, a8, a9, a10: pt.Return() + ) # noqa: E731 - def fnWithExprAnnotations(a: Expr, b: Expr) -> Expr: - return Return() + def fnWithExprAnnotations(a: pt.Expr, b: pt.Expr) -> pt.Expr: + return pt.Return() - def fnWithOnlyReturnExprAnnotations(a, b) -> Expr: - return Return() + def fnWithOnlyReturnExprAnnotations(a, b) -> pt.Expr: + return pt.Return() - def fnWithOnlyArgExprAnnotations(a: Expr, b: Expr): - return Return() + def fnWithOnlyArgExprAnnotations(a: pt.Expr, b: pt.Expr): + return pt.Return() - def fnWithPartialExprAnnotations(a, b: Expr) -> Expr: - return Return() + def fnWithPartialExprAnnotations(a, b: pt.Expr) -> pt.Expr: + return pt.Return() cases = ( (fn0Args, 0, "fn0Args"), @@ -56,76 +55,76 @@ def fnWithPartialExprAnnotations(a, b: Expr) -> Expr: ) for (fn, numArgs, name) in cases: - definition = SubroutineDefinition(fn, TealType.none) + definition = pt.SubroutineDefinition(fn, pt.TealType.none) assert definition.argumentCount() == numArgs assert definition.name() == name if numArgs > 0: - with pytest.raises(TealInputError): - definition.invoke([Int(1)] * (numArgs - 1)) + with pytest.raises(pt.TealInputError): + definition.invoke([pt.Int(1)] * (numArgs - 1)) - with pytest.raises(TealInputError): - definition.invoke([Int(1)] * (numArgs + 1)) + with pytest.raises(pt.TealInputError): + definition.invoke([pt.Int(1)] * (numArgs + 1)) if numArgs > 0: - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): definition.invoke([1] * numArgs) - args = [Int(1)] * numArgs + args = [pt.Int(1)] * numArgs invocation = definition.invoke(args) - assert isinstance(invocation, SubroutineCall) + assert isinstance(invocation, pt.SubroutineCall) assert invocation.subroutine is definition assert invocation.args == args def test_subroutine_invocation_param_types(): def fnWithNoAnnotations(a, b): - return Return() + return pt.Return() - def fnWithExprAnnotations(a: Expr, b: Expr) -> Expr: - return Return() + def fnWithExprAnnotations(a: pt.Expr, b: pt.Expr) -> pt.Expr: + return pt.Return() - def fnWithSVAnnotations(a: ScratchVar, b: ScratchVar): - return Return() + def fnWithSVAnnotations(a: pt.ScratchVar, b: pt.ScratchVar): + return pt.Return() def fnWithABIAnnotations( - a: abi.Byte, - b: abi.StaticArray[abi.Uint32, Literal[10]], - c: abi.DynamicArray[abi.Bool], + a: pt.abi.Byte, + b: pt.abi.StaticArray[pt.abi.Uint32, Literal[10]], + c: pt.abi.DynamicArray[pt.abi.Bool], ): - return Return() + return pt.Return() - def fnWithMixedAnns1(a: ScratchVar, b: Expr) -> Expr: - return Return() + def fnWithMixedAnns1(a: pt.ScratchVar, b: pt.Expr) -> pt.Expr: + return pt.Return() - def fnWithMixedAnns2(a: ScratchVar, b) -> Expr: - return Return() + def fnWithMixedAnns2(a: pt.ScratchVar, b) -> pt.Expr: + return pt.Return() - def fnWithMixedAnns3(a: Expr, b: ScratchVar): - return Return() + def fnWithMixedAnns3(a: pt.Expr, b: pt.ScratchVar): + return pt.Return() - def fnWithMixedAnns4(a: ScratchVar, b, c: abi.Uint16) -> Expr: - return Return() + def fnWithMixedAnns4(a: pt.ScratchVar, b, c: pt.abi.Uint16) -> pt.Expr: + return pt.Return() - sv = ScratchVar() - x = Int(42) - s = Bytes("hello") - av_u16 = abi.Uint16() - av_bool_dym_arr = abi.DynamicArray(abi.BoolTypeSpec()) - av_u32_static_arr = abi.StaticArray(abi.Uint32TypeSpec(), 10) - av_bool = abi.Bool() - av_byte = abi.Byte() + sv = pt.ScratchVar() + x = pt.Int(42) + s = pt.Bytes("hello") + av_u16 = pt.abi.Uint16() + av_bool_dym_arr = pt.abi.DynamicArray(pt.abi.BoolTypeSpec()) + av_u32_static_arr = pt.abi.StaticArray(pt.abi.Uint32TypeSpec(), 10) + av_bool = pt.abi.Bool() + av_byte = pt.abi.Byte() cases = [ ("vanilla 1", fnWithNoAnnotations, [x, s], None), ("vanilla 2", fnWithNoAnnotations, [x, x], None), - ("vanilla no sv's allowed 1", fnWithNoAnnotations, [x, sv], TealInputError), + ("vanilla no sv's allowed 1", fnWithNoAnnotations, [x, sv], pt.TealInputError), ("exprs 1", fnWithExprAnnotations, [x, s], None), ("exprs 2", fnWithExprAnnotations, [x, x], None), - ("exprs no sv's allowed 1", fnWithExprAnnotations, [x, sv], TealInputError), + ("exprs no sv's allowed 1", fnWithExprAnnotations, [x, sv], pt.TealInputError), ("all sv's 1", fnWithSVAnnotations, [sv, sv], None), - ("all sv's but strings", fnWithSVAnnotations, [s, s], TealInputError), - ("all sv's but ints", fnWithSVAnnotations, [x, x], TealInputError), + ("all sv's but strings", fnWithSVAnnotations, [s, s], pt.TealInputError), + ("all sv's but ints", fnWithSVAnnotations, [x, x], pt.TealInputError), ( "all abi's 1", fnWithABIAnnotations, @@ -136,94 +135,94 @@ def fnWithMixedAnns4(a: ScratchVar, b, c: abi.Uint16) -> Expr: "all abi's but ints 1", fnWithABIAnnotations, [x, av_u32_static_arr, av_bool_dym_arr], - TealInputError, + pt.TealInputError, ), ( "all abi's but ints 2", fnWithABIAnnotations, [x, av_u32_static_arr, x], - TealInputError, + pt.TealInputError, ), - ("all abi's but ints 3", fnWithABIAnnotations, [x, x, x], TealInputError), + ("all abi's but ints 3", fnWithABIAnnotations, [x, x, x], pt.TealInputError), ( "all abi's but sv's 1", fnWithABIAnnotations, [sv, av_u32_static_arr, av_bool_dym_arr], - TealInputError, + pt.TealInputError, ), ( "all abi's but sv's 2", fnWithABIAnnotations, [av_byte, av_u32_static_arr, sv], - TealInputError, + pt.TealInputError, ), ( "all abi's but sv's 3", fnWithABIAnnotations, [av_byte, sv, av_u32_static_arr], - TealInputError, + pt.TealInputError, ), ( "all abi's but wrong typed 1", fnWithABIAnnotations, [av_u32_static_arr, av_u32_static_arr, av_bool_dym_arr], - TealInputError, + pt.TealInputError, ), ( "all abi's but wrong typed 2", fnWithABIAnnotations, [av_bool, av_bool_dym_arr, av_u16], - TealInputError, + pt.TealInputError, ), ( "all abi's but wrong typed 3", fnWithABIAnnotations, [av_u16, av_bool, av_byte], - TealInputError, + pt.TealInputError, ), ("mixed1 copacetic", fnWithMixedAnns1, [sv, x], None), - ("mixed1 flipped", fnWithMixedAnns1, [x, sv], TealInputError), - ("mixed1 missing the sv", fnWithMixedAnns1, [x, s], TealInputError), - ("mixed1 missing the non-sv", fnWithMixedAnns1, [sv, sv], TealInputError), + ("mixed1 flipped", fnWithMixedAnns1, [x, sv], pt.TealInputError), + ("mixed1 missing the sv", fnWithMixedAnns1, [x, s], pt.TealInputError), + ("mixed1 missing the non-sv", fnWithMixedAnns1, [sv, sv], pt.TealInputError), ("mixed2 copacetic", fnWithMixedAnns2, [sv, x], None), - ("mixed2 flipped", fnWithMixedAnns2, [x, sv], TealInputError), - ("mixed2 missing the sv", fnWithMixedAnns2, [x, s], TealInputError), - ("mixed2 missing the non-sv", fnWithMixedAnns2, [sv, sv], TealInputError), + ("mixed2 flipped", fnWithMixedAnns2, [x, sv], pt.TealInputError), + ("mixed2 missing the sv", fnWithMixedAnns2, [x, s], pt.TealInputError), + ("mixed2 missing the non-sv", fnWithMixedAnns2, [sv, sv], pt.TealInputError), ("mixed3 copacetic", fnWithMixedAnns3, [s, sv], None), - ("mixed3 flipped", fnWithMixedAnns3, [sv, x], TealInputError), - ("mixed3 missing the sv", fnWithMixedAnns3, [x, s], TealInputError), + ("mixed3 flipped", fnWithMixedAnns3, [sv, x], pt.TealInputError), + ("mixed3 missing the sv", fnWithMixedAnns3, [x, s], pt.TealInputError), ("mixed anno", fnWithMixedAnns4, [sv, x, av_u16], None), ( "mixed anno but wrong typed 1", fnWithMixedAnns4, [av_byte, x, av_u16], - TealInputError, + pt.TealInputError, ), ( "mixed anno but wrong typed 2", fnWithMixedAnns4, [sv, av_byte, sv], - TealInputError, + pt.TealInputError, ), ( "mixed anno but wrong typed 3", fnWithMixedAnns4, [sv, x, av_byte], - TealInputError, + pt.TealInputError, ), ] for case_name, fn, args, err in cases: - definition = SubroutineDefinition(fn, TealType.none) + definition = pt.SubroutineDefinition(fn, pt.TealType.none) assert definition.argumentCount() == len(args), case_name assert definition.name() == fn.__name__, case_name if err is None: assert len(definition.by_ref_args) == len( - [x for x in args if isinstance(x, ScratchVar)] + [x for x in args if isinstance(x, pt.ScratchVar)] ), case_name invocation = definition.invoke(args) - assert isinstance(invocation, SubroutineCall), case_name + assert isinstance(invocation, pt.SubroutineCall), case_name assert invocation.subroutine is definition, case_name assert invocation.args == args, case_name assert invocation.has_return() is False, case_name @@ -240,36 +239,38 @@ def fnWithMixedAnns4(a: ScratchVar, b, c: abi.Uint16) -> Expr: def test_subroutine_definition_invalid(): def fnWithDefaults(a, b=None): - return Return() + return pt.Return() def fnWithKeywordArgs(a, *, b): - return Return() + return pt.Return() def fnWithVariableArgs(a, *b): - return Return() + return pt.Return() - def fnWithNonExprReturnAnnotation(a, b) -> TealType.uint64: - return Return() + def fnWithNonExprReturnAnnotation(a, b) -> pt.TealType.uint64: + return pt.Return() - def fnWithNonExprParamAnnotation(a, b: TealType.uint64): - return Return() + def fnWithNonExprParamAnnotation(a, b: pt.TealType.uint64): + return pt.Return() - def fnWithScratchVarSubclass(a, b: DynamicScratchVar): - return Return() + def fnWithScratchVarSubclass(a, b: pt.DynamicScratchVar): + return pt.Return() - def fnReturningExprSubclass(a: ScratchVar, b: Expr) -> Return: - return Return() + def fnReturningExprSubclass(a: pt.ScratchVar, b: pt.Expr) -> pt.Return: + return pt.Return() - def fnWithMixedAnns4AndBytesReturn(a: Expr, b: ScratchVar) -> Bytes: - return Bytes("hello uwu") + def fnWithMixedAnns4AndBytesReturn(a: pt.Expr, b: pt.ScratchVar) -> pt.Bytes: + return pt.Bytes("hello uwu") def fnWithMixedAnnsABIRet1( - a: Expr, b: ScratchVar, c: abi.Uint16 - ) -> abi.StaticArray[abi.Uint32, Literal[10]]: - return abi.StaticArray(abi.Uint32TypeSpec(), 10) + a: pt.Expr, b: pt.ScratchVar, c: pt.abi.Uint16 + ) -> pt.abi.StaticArray[pt.abi.Uint32, Literal[10]]: + return pt.abi.StaticArray(pt.abi.Uint32TypeSpec(), 10) - def fnWithMixedAnnsABIRet2(a: Expr, b: abi.Byte, c: ScratchVar) -> abi.Uint64: - return abi.Uint64() + def fnWithMixedAnnsABIRet2( + a: pt.Expr, b: pt.abi.Byte, c: pt.ScratchVar + ) -> pt.abi.Uint64: + return pt.abi.Uint64() cases = ( (1, "TealInputError('Input to SubroutineDefinition is not callable'"), @@ -318,20 +319,20 @@ def fnWithMixedAnnsABIRet2(a: Expr, b: abi.Byte, c: ScratchVar) -> abi.Uint64: ) for fn, msg in cases: - with pytest.raises(TealInputError) as e: + with pytest.raises(pt.TealInputError) as e: print(f"case=[{msg}]") - SubroutineDefinition(fn, TealType.none) + pt.SubroutineDefinition(fn, pt.TealType.none) assert msg in str(e), "failed for case [{}]".format(fn.__name__) def test_subroutine_declaration(): cases = ( - (TealType.none, Return()), - (TealType.uint64, Return(Int(1))), - (TealType.uint64, Int(1)), - (TealType.bytes, Bytes("value")), - (TealType.anytype, App.globalGet(Bytes("key"))), + (pt.TealType.none, pt.Return()), + (pt.TealType.uint64, pt.Return(pt.Int(1))), + (pt.TealType.uint64, pt.Int(1)), + (pt.TealType.bytes, pt.Bytes("value")), + (pt.TealType.anytype, pt.App.globalGet(pt.Bytes("key"))), ) for (returnType, value) in cases: @@ -339,9 +340,9 @@ def test_subroutine_declaration(): def mySubroutine(): return value - definition = SubroutineDefinition(mySubroutine, returnType) + definition = pt.SubroutineDefinition(mySubroutine, returnType) - declaration = SubroutineDeclaration(definition, value) + declaration = pt.SubroutineDeclaration(definition, value) assert declaration.type_of() == value.type_of() assert declaration.has_return() == value.has_return() @@ -352,27 +353,32 @@ def mySubroutine(): def test_subroutine_call(): def mySubroutine(): - return Return() + return pt.Return() - returnTypes = (TealType.uint64, TealType.bytes, TealType.anytype, TealType.none) + returnTypes = ( + pt.TealType.uint64, + pt.TealType.bytes, + pt.TealType.anytype, + pt.TealType.none, + ) argCases = ( [], - [Int(1)], - [Int(1), Bytes("value")], + [pt.Int(1)], + [pt.Int(1), pt.Bytes("value")], ) for returnType in returnTypes: - definition = SubroutineDefinition(mySubroutine, returnType) + definition = pt.SubroutineDefinition(mySubroutine, returnType) for args in argCases: - expr = SubroutineCall(definition, args) + expr = pt.SubroutineCall(definition, args) assert expr.type_of() == returnType assert not expr.has_return() - expected, _ = TealBlock.FromOp( - options, TealOp(expr, Op.callsub, definition), *args + expected, _ = pt.TealBlock.FromOp( + options, pt.TealOp(expr, pt.Op.callsub, definition), *args ) actual, _ = expr.__teal__(options) @@ -381,41 +387,41 @@ def mySubroutine(): def test_decorator(): - assert callable(Subroutine) - assert callable(Subroutine(TealType.anytype)) + assert callable(pt.Subroutine) + assert callable(pt.Subroutine(pt.TealType.anytype)) - @Subroutine(TealType.none) + @pt.Subroutine(pt.TealType.none) def mySubroutine(a): - return Return() + return pt.Return() - assert isinstance(mySubroutine, SubroutineFnWrapper) + assert isinstance(mySubroutine, pt.SubroutineFnWrapper) - invocation = mySubroutine(Int(1)) - assert isinstance(invocation, SubroutineCall) + invocation = mySubroutine(pt.Int(1)) + assert isinstance(invocation, pt.SubroutineCall) - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): mySubroutine() - with pytest.raises(TealInputError): - mySubroutine(Int(1), Int(2)) + with pytest.raises(pt.TealInputError): + mySubroutine(pt.Int(1), pt.Int(2)) - with pytest.raises(TealInputError): - mySubroutine(Pop(Int(1))) + with pytest.raises(pt.TealInputError): + mySubroutine(pt.Pop(pt.Int(1))) - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): mySubroutine(1) - with pytest.raises(TealInputError): - mySubroutine(a=Int(1)) + with pytest.raises(pt.TealInputError): + mySubroutine(a=pt.Int(1)) def test_evaluate_subroutine_no_args(): cases = ( - (TealType.none, Return()), - (TealType.uint64, Int(1) + Int(2)), - (TealType.uint64, Return(Int(1) + Int(2))), - (TealType.bytes, Bytes("value")), - (TealType.bytes, Return(Bytes("value"))), + (pt.TealType.none, pt.Return()), + (pt.TealType.uint64, pt.Int(1) + pt.Int(2)), + (pt.TealType.uint64, pt.Return(pt.Int(1) + pt.Int(2))), + (pt.TealType.bytes, pt.Bytes("value")), + (pt.TealType.bytes, pt.Return(pt.Bytes("value"))), ) for (returnType, returnValue) in cases: @@ -423,17 +429,17 @@ def test_evaluate_subroutine_no_args(): def mySubroutine(): return returnValue - definition = SubroutineDefinition(mySubroutine, returnType) + definition = pt.SubroutineDefinition(mySubroutine, returnType) declaration = evaluateSubroutine(definition) - assert isinstance(declaration, SubroutineDeclaration) + assert isinstance(declaration, pt.SubroutineDeclaration) assert declaration.subroutine is definition assert declaration.type_of() == returnValue.type_of() assert declaration.has_return() == returnValue.has_return() options.setSubroutine(definition) - expected, _ = Seq([returnValue]).__teal__(options) + expected, _ = pt.Seq([returnValue]).__teal__(options) actual, _ = declaration.__teal__(options) options.setSubroutine(None) @@ -442,39 +448,39 @@ def mySubroutine(): def test_evaluate_subroutine_1_arg(): cases = ( - (TealType.none, Return()), - (TealType.uint64, Int(1) + Int(2)), - (TealType.uint64, Return(Int(1) + Int(2))), - (TealType.bytes, Bytes("value")), - (TealType.bytes, Return(Bytes("value"))), + (pt.TealType.none, pt.Return()), + (pt.TealType.uint64, pt.Int(1) + pt.Int(2)), + (pt.TealType.uint64, pt.Return(pt.Int(1) + pt.Int(2))), + (pt.TealType.bytes, pt.Bytes("value")), + (pt.TealType.bytes, pt.Return(pt.Bytes("value"))), ) for (returnType, returnValue) in cases: - argSlots: List[ScratchSlot] = [] + argSlots: List[pt.ScratchSlot] = [] def mySubroutine(a1): - assert isinstance(a1, ScratchLoad) + assert isinstance(a1, pt.ScratchLoad) argSlots.append(a1.slot) return returnValue - definition = SubroutineDefinition(mySubroutine, returnType) + definition = pt.SubroutineDefinition(mySubroutine, returnType) declaration = evaluateSubroutine(definition) - assert isinstance(declaration, SubroutineDeclaration) + assert isinstance(declaration, pt.SubroutineDeclaration) assert declaration.subroutine is definition assert declaration.type_of() == returnValue.type_of() assert declaration.has_return() == returnValue.has_return() - assert isinstance(declaration.body, Seq) + assert isinstance(declaration.body, pt.Seq) assert len(declaration.body.args) == 2 - assert isinstance(declaration.body.args[0], ScratchStackStore) + assert isinstance(declaration.body.args[0], pt.ScratchStackStore) assert declaration.body.args[0].slot is argSlots[-1] options.setSubroutine(definition) - expected, _ = Seq([declaration.body.args[0], returnValue]).__teal__(options) + expected, _ = pt.Seq([declaration.body.args[0], returnValue]).__teal__(options) actual, _ = declaration.__teal__(options) options.setSubroutine(None) @@ -483,43 +489,43 @@ def mySubroutine(a1): def test_evaluate_subroutine_2_args(): cases = ( - (TealType.none, Return()), - (TealType.uint64, Int(1) + Int(2)), - (TealType.uint64, Return(Int(1) + Int(2))), - (TealType.bytes, Bytes("value")), - (TealType.bytes, Return(Bytes("value"))), + (pt.TealType.none, pt.Return()), + (pt.TealType.uint64, pt.Int(1) + pt.Int(2)), + (pt.TealType.uint64, pt.Return(pt.Int(1) + pt.Int(2))), + (pt.TealType.bytes, pt.Bytes("value")), + (pt.TealType.bytes, pt.Return(pt.Bytes("value"))), ) for (returnType, returnValue) in cases: - argSlots: List[ScratchSlot] = [] + argSlots: List[pt.ScratchSlot] = [] def mySubroutine(a1, a2): - assert isinstance(a1, ScratchLoad) + assert isinstance(a1, pt.ScratchLoad) argSlots.append(a1.slot) - assert isinstance(a2, ScratchLoad) + assert isinstance(a2, pt.ScratchLoad) argSlots.append(a2.slot) return returnValue - definition = SubroutineDefinition(mySubroutine, returnType) + definition = pt.SubroutineDefinition(mySubroutine, returnType) declaration = evaluateSubroutine(definition) - assert isinstance(declaration, SubroutineDeclaration) + assert isinstance(declaration, pt.SubroutineDeclaration) assert declaration.subroutine is definition assert declaration.type_of() == returnValue.type_of() assert declaration.has_return() == returnValue.has_return() - assert isinstance(declaration.body, Seq) + assert isinstance(declaration.body, pt.Seq) assert len(declaration.body.args) == 3 - assert isinstance(declaration.body.args[0], ScratchStackStore) - assert isinstance(declaration.body.args[1], ScratchStackStore) + assert isinstance(declaration.body.args[0], pt.ScratchStackStore) + assert isinstance(declaration.body.args[1], pt.ScratchStackStore) assert declaration.body.args[0].slot is argSlots[-1] assert declaration.body.args[1].slot is argSlots[-2] options.setSubroutine(definition) - expected, _ = Seq( + expected, _ = pt.Seq( [declaration.body.args[0], declaration.body.args[1], returnValue] ).__teal__(options) @@ -530,42 +536,44 @@ def mySubroutine(a1, a2): def test_evaluate_subroutine_10_args(): cases = ( - (TealType.none, Return()), - (TealType.uint64, Int(1) + Int(2)), - (TealType.uint64, Return(Int(1) + Int(2))), - (TealType.bytes, Bytes("value")), - (TealType.bytes, Return(Bytes("value"))), + (pt.TealType.none, pt.Return()), + (pt.TealType.uint64, pt.Int(1) + pt.Int(2)), + (pt.TealType.uint64, pt.Return(pt.Int(1) + pt.Int(2))), + (pt.TealType.bytes, pt.Bytes("value")), + (pt.TealType.bytes, pt.Return(pt.Bytes("value"))), ) for (returnType, returnValue) in cases: - argSlots: List[ScratchSlot] = [] + argSlots: List[pt.ScratchSlot] = [] def mySubroutine(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10): for a in (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10): - assert isinstance(a, ScratchLoad) + assert isinstance(a, pt.ScratchLoad) argSlots.append(a.slot) return returnValue - definition = SubroutineDefinition(mySubroutine, returnType) + definition = pt.SubroutineDefinition(mySubroutine, returnType) declaration = evaluateSubroutine(definition) - assert isinstance(declaration, SubroutineDeclaration) + assert isinstance(declaration, pt.SubroutineDeclaration) assert declaration.subroutine is definition assert declaration.type_of() == returnValue.type_of() assert declaration.has_return() == returnValue.has_return() - assert isinstance(declaration.body, Seq) + assert isinstance(declaration.body, pt.Seq) assert len(declaration.body.args) == 11 for i in range(10): - assert isinstance(declaration.body.args[i], ScratchStackStore) + assert isinstance(declaration.body.args[i], pt.ScratchStackStore) for i in range(10): assert declaration.body.args[i].slot is argSlots[-i - 1] options.setSubroutine(definition) - expected, _ = Seq(declaration.body.args[:10] + [returnValue]).__teal__(options) + expected, _ = pt.Seq(declaration.body.args[:10] + [returnValue]).__teal__( + options + ) actual, _ = declaration.__teal__(options) options.setSubroutine(None) diff --git a/pyteal/ast/substring.py b/pyteal/ast/substring.py index 3167484a7..438b40933 100644 --- a/pyteal/ast/substring.py +++ b/pyteal/ast/substring.py @@ -1,15 +1,14 @@ -from enum import Enum -from typing import cast, Tuple, TYPE_CHECKING +from typing import cast, TYPE_CHECKING -from ..types import TealType, require_type -from ..errors import TealCompileError, verifyTealVersion -from ..ir import TealOp, Op, TealBlock, TealSimpleBlock -from .expr import Expr -from .int import Int -from .ternaryexpr import TernaryExpr +from pyteal.types import TealType, require_type +from pyteal.errors import TealCompileError, verifyTealVersion +from pyteal.ir import TealOp, Op, TealBlock, TealSimpleBlock +from pyteal.ast.expr import Expr +from pyteal.ast.int import Int +from pyteal.ast.ternaryexpr import TernaryExpr if TYPE_CHECKING: - from ..compiler import CompileOptions + from pyteal.compiler import CompileOptions class SubstringExpr(Expr): diff --git a/pyteal/ast/substring_test.py b/pyteal/ast/substring_test.py index c532f299f..2aafeabfd 100644 --- a/pyteal/ast/substring_test.py +++ b/pyteal/ast/substring_test.py @@ -1,283 +1,280 @@ import pytest -from .. import * +import pyteal as pt -# this is not necessary but mypy complains if it's not included -from .. import CompileOptions - -teal2Options = CompileOptions(version=2) -teal3Options = CompileOptions(version=3) -teal4Options = CompileOptions(version=4) -teal5Options = CompileOptions(version=5) +teal2Options = pt.CompileOptions(version=2) +teal3Options = pt.CompileOptions(version=3) +teal4Options = pt.CompileOptions(version=4) +teal5Options = pt.CompileOptions(version=5) def test_substring_immediate_v2(): - args = [Bytes("my string"), Int(0), Int(2)] - expr = Substring(args[0], args[1], args[2]) - assert expr.type_of() == TealType.bytes + args = [pt.Bytes("my string"), pt.Int(0), pt.Int(2)] + expr = pt.Substring(args[0], args[1], args[2]) + assert expr.type_of() == pt.TealType.bytes - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(args[0], Op.byte, '"my string"'), - TealOp(expr, Op.substring, 0, 2), + pt.TealOp(args[0], pt.Op.byte, '"my string"'), + pt.TealOp(expr, pt.Op.substring, 0, 2), ] ) actual, _ = expr.__teal__(teal2Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected def test_substring_immediate_v5(): - args = [Bytes("my string"), Int(1), Int(2)] - expr = Substring(args[0], args[1], args[2]) - assert expr.type_of() == TealType.bytes + args = [pt.Bytes("my string"), pt.Int(1), pt.Int(2)] + expr = pt.Substring(args[0], args[1], args[2]) + assert expr.type_of() == pt.TealType.bytes - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(args[0], Op.byte, '"my string"'), - TealOp(expr, Op.extract, 1, 1), + pt.TealOp(args[0], pt.Op.byte, '"my string"'), + pt.TealOp(expr, pt.Op.extract, 1, 1), ] ) actual, _ = expr.__teal__(teal5Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected def test_substring_to_extract(): my_string = "a" * 257 - args = [Bytes(my_string), Int(255), Int(257)] - expr = Substring(args[0], args[1], args[2]) - assert expr.type_of() == TealType.bytes + args = [pt.Bytes(my_string), pt.Int(255), pt.Int(257)] + expr = pt.Substring(args[0], args[1], args[2]) + assert expr.type_of() == pt.TealType.bytes - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(args[0], Op.byte, '"{my_string}"'.format(my_string=my_string)), - TealOp(expr, Op.extract, 255, 2), + pt.TealOp(args[0], pt.Op.byte, '"{my_string}"'.format(my_string=my_string)), + pt.TealOp(expr, pt.Op.extract, 255, 2), ] ) actual, _ = expr.__teal__(teal5Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected def test_substring_stack_v2(): my_string = "a" * 257 - args = [Bytes(my_string), Int(256), Int(257)] - expr = Substring(args[0], args[1], args[2]) - assert expr.type_of() == TealType.bytes + args = [pt.Bytes(my_string), pt.Int(256), pt.Int(257)] + expr = pt.Substring(args[0], args[1], args[2]) + assert expr.type_of() == pt.TealType.bytes - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(args[0], Op.byte, '"{my_string}"'.format(my_string=my_string)), - TealOp(args[1], Op.int, 256), - TealOp(args[2], Op.int, 257), - TealOp(expr, Op.substring3), + pt.TealOp(args[0], pt.Op.byte, '"{my_string}"'.format(my_string=my_string)), + pt.TealOp(args[1], pt.Op.int, 256), + pt.TealOp(args[2], pt.Op.int, 257), + pt.TealOp(expr, pt.Op.substring3), ] ) actual, _ = expr.__teal__(teal2Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected def test_substring_stack_v5(): my_string = "a" * 257 - args = [Bytes(my_string), Int(256), Int(257)] - expr = Substring(args[0], args[1], args[2]) - assert expr.type_of() == TealType.bytes + args = [pt.Bytes(my_string), pt.Int(256), pt.Int(257)] + expr = pt.Substring(args[0], args[1], args[2]) + assert expr.type_of() == pt.TealType.bytes - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(args[0], Op.byte, '"{my_string}"'.format(my_string=my_string)), - TealOp(args[1], Op.int, 256), - TealOp(Int(1), Op.int, 1), - TealOp(expr, Op.extract3), + pt.TealOp(args[0], pt.Op.byte, '"{my_string}"'.format(my_string=my_string)), + pt.TealOp(args[1], pt.Op.int, 256), + pt.TealOp(pt.Int(1), pt.Op.int, 1), + pt.TealOp(expr, pt.Op.extract3), ] ) actual, _ = expr.__teal__(teal5Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected def test_zero_length_substring_immediate(): my_string = "a" * 257 - args = [Bytes(my_string), Int(1), Int(1)] - expr = Substring(args[0], args[1], args[2]) - assert expr.type_of() == TealType.bytes + args = [pt.Bytes(my_string), pt.Int(1), pt.Int(1)] + expr = pt.Substring(args[0], args[1], args[2]) + assert expr.type_of() == pt.TealType.bytes - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(args[0], Op.byte, '"{my_string}"'.format(my_string=my_string)), - TealOp(expr, Op.substring, 1, 1), + pt.TealOp(args[0], pt.Op.byte, '"{my_string}"'.format(my_string=my_string)), + pt.TealOp(expr, pt.Op.substring, 1, 1), ] ) actual, _ = expr.__teal__(teal5Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected def test_substring_invalid(): - with pytest.raises(TealTypeError): - Substring(Int(0), Int(0), Int(2)) + with pytest.raises(pt.TealTypeError): + pt.Substring(pt.Int(0), pt.Int(0), pt.Int(2)) - with pytest.raises(TealTypeError): - Substring(Bytes("my string"), Txn.sender(), Int(2)) + with pytest.raises(pt.TealTypeError): + pt.Substring(pt.Bytes("my string"), pt.Txn.sender(), pt.Int(2)) - with pytest.raises(TealTypeError): - Substring(Bytes("my string"), Int(0), Txn.sender()) + with pytest.raises(pt.TealTypeError): + pt.Substring(pt.Bytes("my string"), pt.Int(0), pt.Txn.sender()) with pytest.raises(Exception): - Substring(Bytes("my string"), Int(1), Int(0)).__teal__(teal5Options) + pt.Substring(pt.Bytes("my string"), pt.Int(1), pt.Int(0)).__teal__(teal5Options) def test_extract_immediate(): - args = [Bytes("my string"), Int(0), Int(2)] - expr = Extract(args[0], args[1], args[2]) - assert expr.type_of() == TealType.bytes + args = [pt.Bytes("my string"), pt.Int(0), pt.Int(2)] + expr = pt.Extract(args[0], args[1], args[2]) + assert expr.type_of() == pt.TealType.bytes - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(args[0], Op.byte, '"my string"'), - TealOp(expr, Op.extract, 0, 2), + pt.TealOp(args[0], pt.Op.byte, '"my string"'), + pt.TealOp(expr, pt.Op.extract, 0, 2), ] ) actual, _ = expr.__teal__(teal5Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): expr.__teal__(teal4Options) def test_extract_zero(): - args = [Bytes("my string"), Int(1), Int(0)] - expr = Extract(args[0], args[1], args[2]) - assert expr.type_of() == TealType.bytes + args = [pt.Bytes("my string"), pt.Int(1), pt.Int(0)] + expr = pt.Extract(args[0], args[1], args[2]) + assert expr.type_of() == pt.TealType.bytes - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(args[0], Op.byte, '"my string"'), - TealOp(args[1], Op.int, 1), - TealOp(args[2], Op.int, 0), - TealOp(expr, Op.extract3), + pt.TealOp(args[0], pt.Op.byte, '"my string"'), + pt.TealOp(args[1], pt.Op.int, 1), + pt.TealOp(args[2], pt.Op.int, 0), + pt.TealOp(expr, pt.Op.extract3), ] ) actual, _ = expr.__teal__(teal5Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): expr.__teal__(teal4Options) def test_extract_stack(): my_string = "*" * 257 - args = [Bytes(my_string), Int(256), Int(257)] - expr = Extract(args[0], args[1], args[2]) - assert expr.type_of() == TealType.bytes + args = [pt.Bytes(my_string), pt.Int(256), pt.Int(257)] + expr = pt.Extract(args[0], args[1], args[2]) + assert expr.type_of() == pt.TealType.bytes - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(args[0], Op.byte, '"{my_string}"'.format(my_string=my_string)), - TealOp(args[1], Op.int, 256), - TealOp(args[2], Op.int, 257), - TealOp(expr, Op.extract3), + pt.TealOp(args[0], pt.Op.byte, '"{my_string}"'.format(my_string=my_string)), + pt.TealOp(args[1], pt.Op.int, 256), + pt.TealOp(args[2], pt.Op.int, 257), + pt.TealOp(expr, pt.Op.extract3), ] ) actual, _ = expr.__teal__(teal5Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): expr.__teal__(teal4Options) def test_extract_invalid(): - with pytest.raises(TealTypeError): - Extract(Int(0), Int(0), Int(2)) + with pytest.raises(pt.TealTypeError): + pt.Extract(pt.Int(0), pt.Int(0), pt.Int(2)) - with pytest.raises(TealTypeError): - Extract(Bytes("my string"), Txn.sender(), Int(2)) + with pytest.raises(pt.TealTypeError): + pt.Extract(pt.Bytes("my string"), pt.Txn.sender(), pt.Int(2)) - with pytest.raises(TealTypeError): - Extract(Bytes("my string"), Int(0), Txn.sender()) + with pytest.raises(pt.TealTypeError): + pt.Extract(pt.Bytes("my string"), pt.Int(0), pt.Txn.sender()) def test_suffix_immediate(): - args = [Bytes("my string"), Int(1)] - expr = Suffix(args[0], args[1]) - assert expr.type_of() == TealType.bytes + args = [pt.Bytes("my string"), pt.Int(1)] + expr = pt.Suffix(args[0], args[1]) + assert expr.type_of() == pt.TealType.bytes - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(args[0], Op.byte, '"my string"'), - TealOp(expr, Op.extract, 1, 0), + pt.TealOp(args[0], pt.Op.byte, '"my string"'), + pt.TealOp(expr, pt.Op.extract, 1, 0), ] ) actual, _ = expr.__teal__(teal5Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected def test_suffix_stack(): my_string = "*" * 1024 - args = [Bytes(my_string), Int(257)] - expr = Suffix(args[0], args[1]) - assert expr.type_of() == TealType.bytes + args = [pt.Bytes(my_string), pt.Int(257)] + expr = pt.Suffix(args[0], args[1]) + assert expr.type_of() == pt.TealType.bytes - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(args[0], Op.byte, '"{my_string}"'.format(my_string=my_string)), - TealOp(args[1], Op.int, 257), - TealOp(expr, Op.dig, 1), - TealOp(expr, Op.len), - TealOp(expr, Op.substring3), + pt.TealOp(args[0], pt.Op.byte, '"{my_string}"'.format(my_string=my_string)), + pt.TealOp(args[1], pt.Op.int, 257), + pt.TealOp(expr, pt.Op.dig, 1), + pt.TealOp(expr, pt.Op.len), + pt.TealOp(expr, pt.Op.substring3), ] ) actual, _ = expr.__teal__(teal2Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected def test_suffix_invalid(): - with pytest.raises(TealTypeError): - Suffix(Int(0), Int(0)) + with pytest.raises(pt.TealTypeError): + pt.Suffix(pt.Int(0), pt.Int(0)) - with pytest.raises(TealTypeError): - Suffix(Bytes("my string"), Txn.sender()) + with pytest.raises(pt.TealTypeError): + pt.Suffix(pt.Bytes("my string"), pt.Txn.sender()) diff --git a/pyteal/ast/ternaryexpr.py b/pyteal/ast/ternaryexpr.py index 94776e800..b329165de 100644 --- a/pyteal/ast/ternaryexpr.py +++ b/pyteal/ast/ternaryexpr.py @@ -1,14 +1,12 @@ from typing import Tuple, TYPE_CHECKING -from ..types import TealType, require_type -from ..errors import verifyTealVersion -from ..ir import TealOp, Op, TealBlock -from .expr import Expr -from .int import Int -from .unaryexpr import Len +from pyteal.types import TealType, require_type +from pyteal.errors import verifyTealVersion +from pyteal.ir import TealOp, Op, TealBlock +from pyteal.ast.expr import Expr if TYPE_CHECKING: - from ..compiler import CompileOptions + from pyteal.compiler import CompileOptions class TernaryExpr(Expr): diff --git a/pyteal/ast/ternaryexpr_test.py b/pyteal/ast/ternaryexpr_test.py index 48b35585e..996c58635 100644 --- a/pyteal/ast/ternaryexpr_test.py +++ b/pyteal/ast/ternaryexpr_test.py @@ -1,173 +1,170 @@ import pytest -from .. import * +import pyteal as pt -# this is not necessary but mypy complains if it's not included -from .. import CompileOptions - -teal2Options = CompileOptions(version=2) -teal3Options = CompileOptions(version=3) -teal4Options = CompileOptions(version=4) -teal5Options = CompileOptions(version=5) -teal6Options = CompileOptions(version=6) +teal2Options = pt.CompileOptions(version=2) +teal3Options = pt.CompileOptions(version=3) +teal4Options = pt.CompileOptions(version=4) +teal5Options = pt.CompileOptions(version=5) +teal6Options = pt.CompileOptions(version=6) def test_ed25519verify(): - args = [Bytes("data"), Bytes("sig"), Bytes("key")] - expr = Ed25519Verify(args[0], args[1], args[2]) - assert expr.type_of() == TealType.uint64 + args = [pt.Bytes("data"), pt.Bytes("sig"), pt.Bytes("key")] + expr = pt.Ed25519Verify(args[0], args[1], args[2]) + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(args[0], Op.byte, '"data"'), - TealOp(args[1], Op.byte, '"sig"'), - TealOp(args[2], Op.byte, '"key"'), - TealOp(expr, Op.ed25519verify), + pt.TealOp(args[0], pt.Op.byte, '"data"'), + pt.TealOp(args[1], pt.Op.byte, '"sig"'), + pt.TealOp(args[2], pt.Op.byte, '"key"'), + pt.TealOp(expr, pt.Op.ed25519verify), ] ) actual, _ = expr.__teal__(teal2Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected def test_ed25519verify_invalid(): - with pytest.raises(TealTypeError): - Ed25519Verify(Int(0), Bytes("sig"), Bytes("key")) + with pytest.raises(pt.TealTypeError): + pt.Ed25519Verify(pt.Int(0), pt.Bytes("sig"), pt.Bytes("key")) - with pytest.raises(TealTypeError): - Ed25519Verify(Bytes("data"), Int(0), Bytes("key")) + with pytest.raises(pt.TealTypeError): + pt.Ed25519Verify(pt.Bytes("data"), pt.Int(0), pt.Bytes("key")) - with pytest.raises(TealTypeError): - Ed25519Verify(Bytes("data"), Bytes("sig"), Int(0)) + with pytest.raises(pt.TealTypeError): + pt.Ed25519Verify(pt.Bytes("data"), pt.Bytes("sig"), pt.Int(0)) def test_set_bit_int(): - args = [Int(0), Int(2), Int(1)] - expr = SetBit(args[0], args[1], args[2]) - assert expr.type_of() == TealType.uint64 + args = [pt.Int(0), pt.Int(2), pt.Int(1)] + expr = pt.SetBit(args[0], args[1], args[2]) + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(args[0], Op.int, 0), - TealOp(args[1], Op.int, 2), - TealOp(args[2], Op.int, 1), - TealOp(expr, Op.setbit), + pt.TealOp(args[0], pt.Op.int, 0), + pt.TealOp(args[1], pt.Op.int, 2), + pt.TealOp(args[2], pt.Op.int, 1), + pt.TealOp(expr, pt.Op.setbit), ] ) actual, _ = expr.__teal__(teal3Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): expr.__teal__(teal2Options) def test_set_bit_bytes(): - args = [Bytes("base16", "0x0000"), Int(0), Int(1)] - expr = SetBit(args[0], args[1], args[2]) - assert expr.type_of() == TealType.bytes + args = [pt.Bytes("base16", "0x0000"), pt.Int(0), pt.Int(1)] + expr = pt.SetBit(args[0], args[1], args[2]) + assert expr.type_of() == pt.TealType.bytes - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(args[0], Op.byte, "0x0000"), - TealOp(args[1], Op.int, 0), - TealOp(args[2], Op.int, 1), - TealOp(expr, Op.setbit), + pt.TealOp(args[0], pt.Op.byte, "0x0000"), + pt.TealOp(args[1], pt.Op.int, 0), + pt.TealOp(args[2], pt.Op.int, 1), + pt.TealOp(expr, pt.Op.setbit), ] ) actual, _ = expr.__teal__(teal3Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): expr.__teal__(teal2Options) def test_set_bit_invalid(): - with pytest.raises(TealTypeError): - SetBit(Int(3), Bytes("index"), Int(1)) + with pytest.raises(pt.TealTypeError): + pt.SetBit(pt.Int(3), pt.Bytes("index"), pt.Int(1)) - with pytest.raises(TealTypeError): - SetBit(Int(3), Int(0), Bytes("one")) + with pytest.raises(pt.TealTypeError): + pt.SetBit(pt.Int(3), pt.Int(0), pt.Bytes("one")) - with pytest.raises(TealTypeError): - SetBit(Bytes("base16", "0xFF"), Bytes("index"), Int(1)) + with pytest.raises(pt.TealTypeError): + pt.SetBit(pt.Bytes("base16", "0xFF"), pt.Bytes("index"), pt.Int(1)) - with pytest.raises(TealTypeError): - SetBit(Bytes("base16", "0xFF"), Int(0), Bytes("one")) + with pytest.raises(pt.TealTypeError): + pt.SetBit(pt.Bytes("base16", "0xFF"), pt.Int(0), pt.Bytes("one")) def test_set_byte(): - args = [Bytes("base16", "0xFF"), Int(0), Int(3)] - expr = SetByte(args[0], args[1], args[2]) - assert expr.type_of() == TealType.bytes + args = [pt.Bytes("base16", "0xFF"), pt.Int(0), pt.Int(3)] + expr = pt.SetByte(args[0], args[1], args[2]) + assert expr.type_of() == pt.TealType.bytes - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(args[0], Op.byte, "0xFF"), - TealOp(args[1], Op.int, 0), - TealOp(args[2], Op.int, 3), - TealOp(expr, Op.setbyte), + pt.TealOp(args[0], pt.Op.byte, "0xFF"), + pt.TealOp(args[1], pt.Op.int, 0), + pt.TealOp(args[2], pt.Op.int, 3), + pt.TealOp(expr, pt.Op.setbyte), ] ) actual, _ = expr.__teal__(teal3Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): expr.__teal__(teal2Options) def test_set_byte_invalid(): - with pytest.raises(TealTypeError): - SetByte(Int(3), Int(0), Int(1)) + with pytest.raises(pt.TealTypeError): + pt.SetByte(pt.Int(3), pt.Int(0), pt.Int(1)) - with pytest.raises(TealTypeError): - SetByte(Bytes("base16", "0xFF"), Bytes("index"), Int(1)) + with pytest.raises(pt.TealTypeError): + pt.SetByte(pt.Bytes("base16", "0xFF"), pt.Bytes("index"), pt.Int(1)) - with pytest.raises(TealTypeError): - SetByte(Bytes("base16", "0xFF"), Int(0), Bytes("one")) + with pytest.raises(pt.TealTypeError): + pt.SetByte(pt.Bytes("base16", "0xFF"), pt.Int(0), pt.Bytes("one")) def test_divw(): - args = [Int(0), Int(90), Int(30)] - expr = Divw(args[0], args[1], args[2]) - assert expr.type_of() == TealType.uint64 + args = [pt.Int(0), pt.Int(90), pt.Int(30)] + expr = pt.Divw(args[0], args[1], args[2]) + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(args[0], Op.int, args[0].value), - TealOp(args[1], Op.int, args[1].value), - TealOp(args[2], Op.int, args[2].value), - TealOp(expr, Op.divw), + pt.TealOp(args[0], pt.Op.int, args[0].value), + pt.TealOp(args[1], pt.Op.int, args[1].value), + pt.TealOp(args[2], pt.Op.int, args[2].value), + pt.TealOp(expr, pt.Op.divw), ] ) actual, _ = expr.__teal__(teal6Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected def test_divw_invalid(): - with pytest.raises(TealTypeError): - Divw(Bytes("10"), Int(0), Int(1)) + with pytest.raises(pt.TealTypeError): + pt.Divw(pt.Bytes("10"), pt.Int(0), pt.Int(1)) - with pytest.raises(TealTypeError): - Divw(Int(10), Bytes("0"), Int(1)) + with pytest.raises(pt.TealTypeError): + pt.Divw(pt.Int(10), pt.Bytes("0"), pt.Int(1)) - with pytest.raises(TealTypeError): - Divw(Int(10), Int(0), Bytes("1")) + with pytest.raises(pt.TealTypeError): + pt.Divw(pt.Int(10), pt.Int(0), pt.Bytes("1")) diff --git a/pyteal/ast/tmpl.py b/pyteal/ast/tmpl.py index b311dc448..6d7061094 100644 --- a/pyteal/ast/tmpl.py +++ b/pyteal/ast/tmpl.py @@ -1,12 +1,11 @@ from typing import TYPE_CHECKING -from ..types import TealType, valid_tmpl -from ..ir import TealOp, Op, TealBlock -from ..errors import TealInternalError -from .leafexpr import LeafExpr +from pyteal.types import TealType, valid_tmpl +from pyteal.ir import TealOp, Op, TealBlock +from pyteal.ast.leafexpr import LeafExpr if TYPE_CHECKING: - from ..compiler import CompileOptions + from pyteal.compiler import CompileOptions class Tmpl(LeafExpr): diff --git a/pyteal/ast/tmpl_test.py b/pyteal/ast/tmpl_test.py index 5eb372df2..12973f07a 100644 --- a/pyteal/ast/tmpl_test.py +++ b/pyteal/ast/tmpl_test.py @@ -1,18 +1,15 @@ import pytest -from .. import * +import pyteal as pt -# this is not necessary but mypy complains if it's not included -from .. import CompileOptions - -options = CompileOptions() +options = pt.CompileOptions() def test_tmpl_int(): - expr = Tmpl.Int("TMPL_AMNT") - assert expr.type_of() == TealType.uint64 + expr = pt.Tmpl.Int("TMPL_AMNT") + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock([TealOp(expr, Op.int, "TMPL_AMNT")]) + expected = pt.TealSimpleBlock([pt.TealOp(expr, pt.Op.int, "TMPL_AMNT")]) actual, _ = expr.__teal__(options) @@ -20,15 +17,15 @@ def test_tmpl_int(): def test_tmpl_int_invalid(): - with pytest.raises(TealInputError): - Tmpl.Int("whatever") + with pytest.raises(pt.TealInputError): + pt.Tmpl.Int("whatever") def test_tmpl_bytes(): - expr = Tmpl.Bytes("TMPL_NOTE") - assert expr.type_of() == TealType.bytes + expr = pt.Tmpl.Bytes("TMPL_NOTE") + assert expr.type_of() == pt.TealType.bytes - expected = TealSimpleBlock([TealOp(expr, Op.byte, "TMPL_NOTE")]) + expected = pt.TealSimpleBlock([pt.TealOp(expr, pt.Op.byte, "TMPL_NOTE")]) actual, _ = expr.__teal__(options) @@ -36,15 +33,15 @@ def test_tmpl_bytes(): def test_tmpl_bytes_invalid(): - with pytest.raises(TealInputError): - Tmpl.Bytes("whatever") + with pytest.raises(pt.TealInputError): + pt.Tmpl.Bytes("whatever") def test_tmpl_addr(): - expr = Tmpl.Addr("TMPL_RECEIVER0") - assert expr.type_of() == TealType.bytes + expr = pt.Tmpl.Addr("TMPL_RECEIVER0") + assert expr.type_of() == pt.TealType.bytes - expected = TealSimpleBlock([TealOp(expr, Op.addr, "TMPL_RECEIVER0")]) + expected = pt.TealSimpleBlock([pt.TealOp(expr, pt.Op.addr, "TMPL_RECEIVER0")]) actual, _ = expr.__teal__(options) @@ -52,5 +49,5 @@ def test_tmpl_addr(): def test_tmpl_addr_invalid(): - with pytest.raises(TealInputError): - Tmpl.Addr("whatever") + with pytest.raises(pt.TealInputError): + pt.Tmpl.Addr("whatever") diff --git a/pyteal/ast/txn.py b/pyteal/ast/txn.py index d619ef0bc..e9182b74f 100644 --- a/pyteal/ast/txn.py +++ b/pyteal/ast/txn.py @@ -1,21 +1,21 @@ from enum import Enum from typing import Callable, Optional, Union, cast, TYPE_CHECKING -from ..types import TealType, require_type -from ..errors import ( +from pyteal.types import TealType, require_type +from pyteal.errors import ( TealInputError, TealCompileError, verifyFieldVersion, verifyTealVersion, ) -from ..ir import TealOp, Op, TealBlock -from .leafexpr import LeafExpr -from .expr import Expr -from .int import EnumInt -from .array import Array +from pyteal.ir import TealOp, Op, TealBlock +from pyteal.ast.leafexpr import LeafExpr +from pyteal.ast.expr import Expr +from pyteal.ast.int import EnumInt +from pyteal.ast.array import Array if TYPE_CHECKING: - from ..compiler import CompileOptions + from pyteal.compiler import CompileOptions class TxnType: diff --git a/pyteal/ast/txn_test.py b/pyteal/ast/txn_test.py index 3b7f6d741..eaaf01464 100644 --- a/pyteal/ast/txn_test.py +++ b/pyteal/ast/txn_test.py @@ -2,105 +2,102 @@ import pytest -from .. import * - -# this is not necessary but mypy complains if it's not included -from .. import Expr, TxnField, TxnObject, TxnArray, CompileOptions - -fieldToMethod: Dict[TxnField, Callable[[TxnObject], Expr]] = { - TxnField.sender: lambda txn: txn.sender(), - TxnField.fee: lambda txn: txn.fee(), - TxnField.first_valid: lambda txn: txn.first_valid(), - TxnField.last_valid: lambda txn: txn.last_valid(), - TxnField.note: lambda txn: txn.note(), - TxnField.lease: lambda txn: txn.lease(), - TxnField.receiver: lambda txn: txn.receiver(), - TxnField.amount: lambda txn: txn.amount(), - TxnField.close_remainder_to: lambda txn: txn.close_remainder_to(), - TxnField.vote_pk: lambda txn: txn.vote_pk(), - TxnField.selection_pk: lambda txn: txn.selection_pk(), - TxnField.vote_first: lambda txn: txn.vote_first(), - TxnField.vote_last: lambda txn: txn.vote_last(), - TxnField.vote_key_dilution: lambda txn: txn.vote_key_dilution(), - TxnField.type: lambda txn: txn.type(), - TxnField.type_enum: lambda txn: txn.type_enum(), - TxnField.xfer_asset: lambda txn: txn.xfer_asset(), - TxnField.asset_amount: lambda txn: txn.asset_amount(), - TxnField.asset_sender: lambda txn: txn.asset_sender(), - TxnField.asset_receiver: lambda txn: txn.asset_receiver(), - TxnField.asset_close_to: lambda txn: txn.asset_close_to(), - TxnField.group_index: lambda txn: txn.group_index(), - TxnField.tx_id: lambda txn: txn.tx_id(), - TxnField.application_id: lambda txn: txn.application_id(), - TxnField.on_completion: lambda txn: txn.on_completion(), - TxnField.approval_program: lambda txn: txn.approval_program(), - TxnField.clear_state_program: lambda txn: txn.clear_state_program(), - TxnField.rekey_to: lambda txn: txn.rekey_to(), - TxnField.config_asset: lambda txn: txn.config_asset(), - TxnField.config_asset_total: lambda txn: txn.config_asset_total(), - TxnField.config_asset_decimals: lambda txn: txn.config_asset_decimals(), - TxnField.config_asset_default_frozen: lambda txn: txn.config_asset_default_frozen(), - TxnField.config_asset_unit_name: lambda txn: txn.config_asset_unit_name(), - TxnField.config_asset_name: lambda txn: txn.config_asset_name(), - TxnField.config_asset_url: lambda txn: txn.config_asset_url(), - TxnField.config_asset_metadata_hash: lambda txn: txn.config_asset_metadata_hash(), - TxnField.config_asset_manager: lambda txn: txn.config_asset_manager(), - TxnField.config_asset_reserve: lambda txn: txn.config_asset_reserve(), - TxnField.config_asset_freeze: lambda txn: txn.config_asset_freeze(), - TxnField.config_asset_clawback: lambda txn: txn.config_asset_clawback(), - TxnField.freeze_asset: lambda txn: txn.freeze_asset(), - TxnField.freeze_asset_account: lambda txn: txn.freeze_asset_account(), - TxnField.freeze_asset_frozen: lambda txn: txn.freeze_asset_frozen(), - TxnField.global_num_uints: lambda txn: txn.global_num_uints(), - TxnField.global_num_byte_slices: lambda txn: txn.global_num_byte_slices(), - TxnField.local_num_uints: lambda txn: txn.local_num_uints(), - TxnField.local_num_byte_slices: lambda txn: txn.local_num_byte_slices(), - TxnField.extra_program_pages: lambda txn: txn.extra_program_pages(), - TxnField.nonparticipation: lambda txn: txn.nonparticipation(), - TxnField.created_asset_id: lambda txn: txn.created_asset_id(), - TxnField.created_application_id: lambda txn: txn.created_application_id(), - TxnField.last_log: lambda txn: txn.last_log(), - TxnField.state_proof_pk: lambda txn: txn.state_proof_pk(), +import pyteal as pt + +fieldToMethod: Dict[pt.TxnField, Callable[[pt.TxnObject], pt.Expr]] = { + pt.TxnField.sender: lambda txn: txn.sender(), + pt.TxnField.fee: lambda txn: txn.fee(), + pt.TxnField.first_valid: lambda txn: txn.first_valid(), + pt.TxnField.last_valid: lambda txn: txn.last_valid(), + pt.TxnField.note: lambda txn: txn.note(), + pt.TxnField.lease: lambda txn: txn.lease(), + pt.TxnField.receiver: lambda txn: txn.receiver(), + pt.TxnField.amount: lambda txn: txn.amount(), + pt.TxnField.close_remainder_to: lambda txn: txn.close_remainder_to(), + pt.TxnField.vote_pk: lambda txn: txn.vote_pk(), + pt.TxnField.selection_pk: lambda txn: txn.selection_pk(), + pt.TxnField.vote_first: lambda txn: txn.vote_first(), + pt.TxnField.vote_last: lambda txn: txn.vote_last(), + pt.TxnField.vote_key_dilution: lambda txn: txn.vote_key_dilution(), + pt.TxnField.type: lambda txn: txn.type(), + pt.TxnField.type_enum: lambda txn: txn.type_enum(), + pt.TxnField.xfer_asset: lambda txn: txn.xfer_asset(), + pt.TxnField.asset_amount: lambda txn: txn.asset_amount(), + pt.TxnField.asset_sender: lambda txn: txn.asset_sender(), + pt.TxnField.asset_receiver: lambda txn: txn.asset_receiver(), + pt.TxnField.asset_close_to: lambda txn: txn.asset_close_to(), + pt.TxnField.group_index: lambda txn: txn.group_index(), + pt.TxnField.tx_id: lambda txn: txn.tx_id(), + pt.TxnField.application_id: lambda txn: txn.application_id(), + pt.TxnField.on_completion: lambda txn: txn.on_completion(), + pt.TxnField.approval_program: lambda txn: txn.approval_program(), + pt.TxnField.clear_state_program: lambda txn: txn.clear_state_program(), + pt.TxnField.rekey_to: lambda txn: txn.rekey_to(), + pt.TxnField.config_asset: lambda txn: txn.config_asset(), + pt.TxnField.config_asset_total: lambda txn: txn.config_asset_total(), + pt.TxnField.config_asset_decimals: lambda txn: txn.config_asset_decimals(), + pt.TxnField.config_asset_default_frozen: lambda txn: txn.config_asset_default_frozen(), + pt.TxnField.config_asset_unit_name: lambda txn: txn.config_asset_unit_name(), + pt.TxnField.config_asset_name: lambda txn: txn.config_asset_name(), + pt.TxnField.config_asset_url: lambda txn: txn.config_asset_url(), + pt.TxnField.config_asset_metadata_hash: lambda txn: txn.config_asset_metadata_hash(), + pt.TxnField.config_asset_manager: lambda txn: txn.config_asset_manager(), + pt.TxnField.config_asset_reserve: lambda txn: txn.config_asset_reserve(), + pt.TxnField.config_asset_freeze: lambda txn: txn.config_asset_freeze(), + pt.TxnField.config_asset_clawback: lambda txn: txn.config_asset_clawback(), + pt.TxnField.freeze_asset: lambda txn: txn.freeze_asset(), + pt.TxnField.freeze_asset_account: lambda txn: txn.freeze_asset_account(), + pt.TxnField.freeze_asset_frozen: lambda txn: txn.freeze_asset_frozen(), + pt.TxnField.global_num_uints: lambda txn: txn.global_num_uints(), + pt.TxnField.global_num_byte_slices: lambda txn: txn.global_num_byte_slices(), + pt.TxnField.local_num_uints: lambda txn: txn.local_num_uints(), + pt.TxnField.local_num_byte_slices: lambda txn: txn.local_num_byte_slices(), + pt.TxnField.extra_program_pages: lambda txn: txn.extra_program_pages(), + pt.TxnField.nonparticipation: lambda txn: txn.nonparticipation(), + pt.TxnField.created_asset_id: lambda txn: txn.created_asset_id(), + pt.TxnField.created_application_id: lambda txn: txn.created_application_id(), + pt.TxnField.last_log: lambda txn: txn.last_log(), + pt.TxnField.state_proof_pk: lambda txn: txn.state_proof_pk(), } -arrayFieldToProperty: Dict[TxnField, Callable[[TxnObject], TxnArray]] = { - TxnField.application_args: lambda txn: txn.application_args, - TxnField.accounts: lambda txn: txn.accounts, - TxnField.assets: lambda txn: txn.assets, - TxnField.applications: lambda txn: txn.applications, - TxnField.logs: lambda txn: txn.logs, +arrayFieldToProperty: Dict[pt.TxnField, Callable[[pt.TxnObject], pt.TxnArray]] = { + pt.TxnField.application_args: lambda txn: txn.application_args, + pt.TxnField.accounts: lambda txn: txn.accounts, + pt.TxnField.assets: lambda txn: txn.assets, + pt.TxnField.applications: lambda txn: txn.applications, + pt.TxnField.logs: lambda txn: txn.logs, } -arrayFieldToLengthField: Dict[TxnField, TxnField] = { - TxnField.application_args: TxnField.num_app_args, - TxnField.accounts: TxnField.num_accounts, - TxnField.assets: TxnField.num_assets, - TxnField.applications: TxnField.num_applications, - TxnField.logs: TxnField.num_logs, +arrayFieldToLengthField: Dict[pt.TxnField, pt.TxnField] = { + pt.TxnField.application_args: pt.TxnField.num_app_args, + pt.TxnField.accounts: pt.TxnField.num_accounts, + pt.TxnField.assets: pt.TxnField.num_assets, + pt.TxnField.applications: pt.TxnField.num_applications, + pt.TxnField.logs: pt.TxnField.num_logs, } def test_txn_fields(): - dynamicGtxnArg = Int(0) + dynamicGtxnArg = pt.Int(0) txnObjects = [ - (Txn, Op.txn, Op.txna, Op.txnas, [], []), + (pt.Txn, pt.Op.txn, pt.Op.txna, pt.Op.txnas, [], []), *[ - (Gtxn[i], Op.gtxn, Op.gtxna, Op.gtxnas, [i], []) - for i in range(MAX_GROUP_SIZE) + (pt.Gtxn[i], pt.Op.gtxn, pt.Op.gtxna, pt.Op.gtxnas, [i], []) + for i in range(pt.MAX_GROUP_SIZE) ], ( - Gtxn[dynamicGtxnArg], - Op.gtxns, - Op.gtxnsa, - Op.gtxnsas, + pt.Gtxn[dynamicGtxnArg], + pt.Op.gtxns, + pt.Op.gtxnsa, + pt.Op.gtxnsas, [], - [TealOp(dynamicGtxnArg, Op.int, 0)], + [pt.TealOp(dynamicGtxnArg, pt.Op.int, 0)], ), - (InnerTxn, Op.itxn, Op.itxna, Op.itxnas, [], []), + (pt.InnerTxn, pt.Op.itxn, pt.Op.itxna, pt.Op.itxnas, [], []), *[ - (Gitxn[i], Op.gitxn, Op.gitxna, Op.gitxnas, [i], []) - for i in range(MAX_GROUP_SIZE) + (pt.Gitxn[i], pt.Op.gitxn, pt.Op.gitxna, pt.Op.gitxnas, [i], []) + for i in range(pt.MAX_GROUP_SIZE) ], ] @@ -112,24 +109,24 @@ def test_txn_fields(): immediateArgsPrefix, irPrefix, ) in txnObjects: - for field in TxnField: + for field in pt.TxnField: if field.is_array: array = arrayFieldToProperty[field](txnObject) lengthExpr = array.length() lengthFieldName = arrayFieldToLengthField[field].arg_name immediateArgs = immediateArgsPrefix + [lengthFieldName] - expected = TealSimpleBlock( - irPrefix + [TealOp(lengthExpr, op, *immediateArgs)] + expected = pt.TealSimpleBlock( + irPrefix + [pt.TealOp(lengthExpr, op, *immediateArgs)] ) expected.addIncoming() - expected = TealBlock.NormalizeBlocks(expected) + expected = pt.TealBlock.NormalizeBlocks(expected) version = max(op.min_version, field.min_version) - actual, _ = lengthExpr.__teal__(CompileOptions(version=version)) + actual, _ = lengthExpr.__teal__(pt.CompileOptions(version=version)) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert ( actual == expected @@ -138,24 +135,25 @@ def test_txn_fields(): ) if version > 2: - with pytest.raises(TealInputError): - lengthExpr.__teal__(CompileOptions(version=version - 1)) + with pytest.raises(pt.TealInputError): + lengthExpr.__teal__(pt.CompileOptions(version=version - 1)) for i in range(32): # just an arbitrary large int elementExpr = array[i] immediateArgs = immediateArgsPrefix + [field.arg_name, i] - expected = TealSimpleBlock( - irPrefix + [TealOp(elementExpr, staticArrayOp, *immediateArgs)] + expected = pt.TealSimpleBlock( + irPrefix + + [pt.TealOp(elementExpr, staticArrayOp, *immediateArgs)] ) expected.addIncoming() - expected = TealBlock.NormalizeBlocks(expected) + expected = pt.TealBlock.NormalizeBlocks(expected) version = max(staticArrayOp.min_version, field.min_version) - actual, _ = elementExpr.__teal__(CompileOptions(version=version)) + actual, _ = elementExpr.__teal__(pt.CompileOptions(version=version)) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert ( actual == expected @@ -164,31 +162,33 @@ def test_txn_fields(): ) if version > 2: - with pytest.raises(TealInputError): - elementExpr.__teal__(CompileOptions(version=version - 1)) + with pytest.raises(pt.TealInputError): + elementExpr.__teal__(pt.CompileOptions(version=version - 1)) if dynamicArrayOp is not None: - dynamicIndex = Int(2) + dynamicIndex = pt.Int(2) dynamicElementExpr = array[dynamicIndex] immediateArgs = immediateArgsPrefix + [field.arg_name] - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( irPrefix + [ - TealOp(dynamicIndex, Op.int, 2), - TealOp(dynamicElementExpr, dynamicArrayOp, *immediateArgs), + pt.TealOp(dynamicIndex, pt.Op.int, 2), + pt.TealOp( + dynamicElementExpr, dynamicArrayOp, *immediateArgs + ), ] ) expected.addIncoming() - expected = TealBlock.NormalizeBlocks(expected) + expected = pt.TealBlock.NormalizeBlocks(expected) version = max(dynamicArrayOp.min_version, field.min_version) actual, _ = dynamicElementExpr.__teal__( - CompileOptions(version=version) + pt.CompileOptions(version=version) ) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert ( actual == expected @@ -197,9 +197,9 @@ def test_txn_fields(): ) if version > 2: - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): dynamicElementExpr.__teal__( - CompileOptions(version=version - 1) + pt.CompileOptions(version=version - 1) ) continue @@ -208,27 +208,29 @@ def test_txn_fields(): # ignore length fields since they are checked with their arrays continue - if field == TxnField.first_valid_time: - # ignore first_valid_time since it is not exposed on TxnObject yet + if field == pt.TxnField.first_valid_time: + # ignore first_valid_time since it is not exposed on pt.TxnObject yet continue expr = fieldToMethod[field](txnObject) immediateArgs = immediateArgsPrefix + [field.arg_name] - expected = TealSimpleBlock(irPrefix + [TealOp(expr, op, *immediateArgs)]) + expected = pt.TealSimpleBlock( + irPrefix + [pt.TealOp(expr, op, *immediateArgs)] + ) expected.addIncoming() - expected = TealBlock.NormalizeBlocks(expected) + expected = pt.TealBlock.NormalizeBlocks(expected) version = max(op.min_version, field.min_version) - actual, _ = expr.__teal__(CompileOptions(version=version)) + actual, _ = expr.__teal__(pt.CompileOptions(version=version)) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected, "{}: field {} does not match expected".format( op, field ) if version > 2: - with pytest.raises(TealInputError): - expr.__teal__(CompileOptions(version=version - 1)) + with pytest.raises(pt.TealInputError): + expr.__teal__(pt.CompileOptions(version=version - 1)) diff --git a/pyteal/ast/unaryexpr.py b/pyteal/ast/unaryexpr.py index 055339d1f..619bfab46 100644 --- a/pyteal/ast/unaryexpr.py +++ b/pyteal/ast/unaryexpr.py @@ -1,12 +1,12 @@ from typing import TYPE_CHECKING -from ..types import TealType, require_type -from ..errors import verifyTealVersion -from ..ir import TealOp, Op, TealBlock -from .expr import Expr +from pyteal.types import TealType, require_type +from pyteal.errors import verifyTealVersion +from pyteal.ir import TealOp, Op, TealBlock +from pyteal.ast.expr import Expr if TYPE_CHECKING: - from ..compiler import CompileOptions + from pyteal.compiler import CompileOptions class UnaryExpr(Expr): diff --git a/pyteal/ast/unaryexpr_test.py b/pyteal/ast/unaryexpr_test.py index 26213e3f5..50a6e2f15 100644 --- a/pyteal/ast/unaryexpr_test.py +++ b/pyteal/ast/unaryexpr_test.py @@ -1,423 +1,451 @@ import pytest -from .. import * +import pyteal as pt -# this is not necessary but mypy complains if it's not included -from .. import CompileOptions - -teal2Options = CompileOptions(version=2) -teal3Options = CompileOptions(version=3) -teal4Options = CompileOptions(version=4) -teal5Options = CompileOptions(version=5) -teal6Options = CompileOptions(version=6) +teal2Options = pt.CompileOptions(version=2) +teal3Options = pt.CompileOptions(version=3) +teal4Options = pt.CompileOptions(version=4) +teal5Options = pt.CompileOptions(version=5) +teal6Options = pt.CompileOptions(version=6) def test_btoi(): - arg = Arg(1) - expr = Btoi(arg) - assert expr.type_of() == TealType.uint64 + arg = pt.Arg(1) + expr = pt.Btoi(arg) + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock([TealOp(arg, Op.arg, 1), TealOp(expr, Op.btoi)]) + expected = pt.TealSimpleBlock( + [pt.TealOp(arg, pt.Op.arg, 1), pt.TealOp(expr, pt.Op.btoi)] + ) actual, _ = expr.__teal__(teal2Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected def test_btoi_invalid(): - with pytest.raises(TealTypeError): - Btoi(Int(1)) + with pytest.raises(pt.TealTypeError): + pt.Btoi(pt.Int(1)) def test_itob(): - arg = Int(1) - expr = Itob(arg) - assert expr.type_of() == TealType.bytes + arg = pt.Int(1) + expr = pt.Itob(arg) + assert expr.type_of() == pt.TealType.bytes - expected = TealSimpleBlock([TealOp(arg, Op.int, 1), TealOp(expr, Op.itob)]) + expected = pt.TealSimpleBlock( + [pt.TealOp(arg, pt.Op.int, 1), pt.TealOp(expr, pt.Op.itob)] + ) actual, _ = expr.__teal__(teal2Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected def test_itob_invalid(): - with pytest.raises(TealTypeError): - Itob(Arg(1)) + with pytest.raises(pt.TealTypeError): + pt.Itob(pt.Arg(1)) def test_len(): - arg = Txn.receiver() - expr = Len(arg) - assert expr.type_of() == TealType.uint64 + arg = pt.Txn.receiver() + expr = pt.Len(arg) + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock([TealOp(arg, Op.txn, "Receiver"), TealOp(expr, Op.len)]) + expected = pt.TealSimpleBlock( + [pt.TealOp(arg, pt.Op.txn, "Receiver"), pt.TealOp(expr, pt.Op.len)] + ) actual, _ = expr.__teal__(teal2Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected def test_len_invalid(): - with pytest.raises(TealTypeError): - Len(Int(1)) + with pytest.raises(pt.TealTypeError): + pt.Len(pt.Int(1)) def test_bitlen_int(): - arg = Int(7) - expr = BitLen(arg) - assert expr.type_of() == TealType.uint64 + arg = pt.Int(7) + expr = pt.BitLen(arg) + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock([TealOp(arg, Op.int, 7), TealOp(expr, Op.bitlen)]) + expected = pt.TealSimpleBlock( + [pt.TealOp(arg, pt.Op.int, 7), pt.TealOp(expr, pt.Op.bitlen)] + ) actual, _ = expr.__teal__(teal4Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected def test_bitlen_bytes(): - arg = Txn.receiver() - expr = BitLen(arg) - assert expr.type_of() == TealType.uint64 + arg = pt.Txn.receiver() + expr = pt.BitLen(arg) + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock( - [TealOp(arg, Op.txn, "Receiver"), TealOp(expr, Op.bitlen)] + expected = pt.TealSimpleBlock( + [pt.TealOp(arg, pt.Op.txn, "Receiver"), pt.TealOp(expr, pt.Op.bitlen)] ) actual, _ = expr.__teal__(teal4Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected def test_sha256(): - arg = Arg(0) - expr = Sha256(arg) - assert expr.type_of() == TealType.bytes + arg = pt.Arg(0) + expr = pt.Sha256(arg) + assert expr.type_of() == pt.TealType.bytes - expected = TealSimpleBlock([TealOp(arg, Op.arg, 0), TealOp(expr, Op.sha256)]) + expected = pt.TealSimpleBlock( + [pt.TealOp(arg, pt.Op.arg, 0), pt.TealOp(expr, pt.Op.sha256)] + ) actual, _ = expr.__teal__(teal2Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected def test_sha256_invalid(): - with pytest.raises(TealTypeError): - Sha256(Int(1)) + with pytest.raises(pt.TealTypeError): + pt.Sha256(pt.Int(1)) def test_sha512_256(): - arg = Arg(0) - expr = Sha512_256(arg) - assert expr.type_of() == TealType.bytes + arg = pt.Arg(0) + expr = pt.Sha512_256(arg) + assert expr.type_of() == pt.TealType.bytes - expected = TealSimpleBlock([TealOp(arg, Op.arg, 0), TealOp(expr, Op.sha512_256)]) + expected = pt.TealSimpleBlock( + [pt.TealOp(arg, pt.Op.arg, 0), pt.TealOp(expr, pt.Op.sha512_256)] + ) actual, _ = expr.__teal__(teal2Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected def test_sha512_256_invalid(): - with pytest.raises(TealTypeError): - Sha512_256(Int(1)) + with pytest.raises(pt.TealTypeError): + pt.Sha512_256(pt.Int(1)) def test_keccak256(): - arg = Arg(0) - expr = Keccak256(arg) - assert expr.type_of() == TealType.bytes + arg = pt.Arg(0) + expr = pt.Keccak256(arg) + assert expr.type_of() == pt.TealType.bytes - expected = TealSimpleBlock([TealOp(arg, Op.arg, 0), TealOp(expr, Op.keccak256)]) + expected = pt.TealSimpleBlock( + [pt.TealOp(arg, pt.Op.arg, 0), pt.TealOp(expr, pt.Op.keccak256)] + ) actual, _ = expr.__teal__(teal2Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected def test_keccak256_invalid(): - with pytest.raises(TealTypeError): - Keccak256(Int(1)) + with pytest.raises(pt.TealTypeError): + pt.Keccak256(pt.Int(1)) def test_not(): - arg = Int(1) - expr = Not(arg) - assert expr.type_of() == TealType.uint64 + arg = pt.Int(1) + expr = pt.Not(arg) + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock([TealOp(arg, Op.int, 1), TealOp(expr, Op.logic_not)]) + expected = pt.TealSimpleBlock( + [pt.TealOp(arg, pt.Op.int, 1), pt.TealOp(expr, pt.Op.logic_not)] + ) actual, _ = expr.__teal__(teal2Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected def test_not_invalid(): - with pytest.raises(TealTypeError): - Not(Txn.receiver()) + with pytest.raises(pt.TealTypeError): + pt.Not(pt.Txn.receiver()) def test_bitwise_not(): - arg = Int(2) - expr = BitwiseNot(arg) - assert expr.type_of() == TealType.uint64 + arg = pt.Int(2) + expr = pt.BitwiseNot(arg) + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock([TealOp(arg, Op.int, 2), TealOp(expr, Op.bitwise_not)]) + expected = pt.TealSimpleBlock( + [pt.TealOp(arg, pt.Op.int, 2), pt.TealOp(expr, pt.Op.bitwise_not)] + ) actual, _ = expr.__teal__(teal2Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected def test_bitwise_not_overload(): - arg = Int(10) + arg = pt.Int(10) expr = ~arg - assert expr.type_of() == TealType.uint64 + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock([TealOp(arg, Op.int, 10), TealOp(expr, Op.bitwise_not)]) + expected = pt.TealSimpleBlock( + [pt.TealOp(arg, pt.Op.int, 10), pt.TealOp(expr, pt.Op.bitwise_not)] + ) actual, _ = expr.__teal__(teal2Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected def test_bitwise_not_invalid(): - with pytest.raises(TealTypeError): - BitwiseNot(Txn.receiver()) + with pytest.raises(pt.TealTypeError): + pt.BitwiseNot(pt.Txn.receiver()) def test_sqrt(): - arg = Int(4) - expr = Sqrt(arg) - assert expr.type_of() == TealType.uint64 + arg = pt.Int(4) + expr = pt.Sqrt(arg) + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock([TealOp(arg, Op.int, 4), TealOp(expr, Op.sqrt)]) + expected = pt.TealSimpleBlock( + [pt.TealOp(arg, pt.Op.int, 4), pt.TealOp(expr, pt.Op.sqrt)] + ) actual, _ = expr.__teal__(teal4Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected def test_sqrt_invalid(): - with pytest.raises(TealTypeError): - Sqrt(Txn.receiver()) + with pytest.raises(pt.TealTypeError): + pt.Sqrt(pt.Txn.receiver()) def test_pop(): - arg_int = Int(3) - expr_int = Pop(arg_int) - assert expr_int.type_of() == TealType.none + arg_int = pt.Int(3) + expr_int = pt.Pop(arg_int) + assert expr_int.type_of() == pt.TealType.none - expected_int = TealSimpleBlock( - [TealOp(arg_int, Op.int, 3), TealOp(expr_int, Op.pop)] + expected_int = pt.TealSimpleBlock( + [pt.TealOp(arg_int, pt.Op.int, 3), pt.TealOp(expr_int, pt.Op.pop)] ) actual_int, _ = expr_int.__teal__(teal2Options) actual_int.addIncoming() - actual_int = TealBlock.NormalizeBlocks(actual_int) + actual_int = pt.TealBlock.NormalizeBlocks(actual_int) assert actual_int == expected_int - arg_bytes = Txn.receiver() - expr_bytes = Pop(arg_bytes) - assert expr_bytes.type_of() == TealType.none + arg_bytes = pt.Txn.receiver() + expr_bytes = pt.Pop(arg_bytes) + assert expr_bytes.type_of() == pt.TealType.none - expected_bytes = TealSimpleBlock( - [TealOp(arg_bytes, Op.txn, "Receiver"), TealOp(expr_bytes, Op.pop)] + expected_bytes = pt.TealSimpleBlock( + [pt.TealOp(arg_bytes, pt.Op.txn, "Receiver"), pt.TealOp(expr_bytes, pt.Op.pop)] ) actual_bytes, _ = expr_bytes.__teal__(teal2Options) actual_bytes.addIncoming() - actual_bytes = TealBlock.NormalizeBlocks(actual_bytes) + actual_bytes = pt.TealBlock.NormalizeBlocks(actual_bytes) assert actual_bytes == expected_bytes def test_pop_invalid(): - expr = Pop(Int(0)) - with pytest.raises(TealTypeError): - Pop(expr) + expr = pt.Pop(pt.Int(0)) + with pytest.raises(pt.TealTypeError): + pt.Pop(expr) def test_balance(): - arg = Int(0) - expr = Balance(arg) - assert expr.type_of() == TealType.uint64 + arg = pt.Int(0) + expr = pt.Balance(arg) + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock([TealOp(arg, Op.int, 0), TealOp(expr, Op.balance)]) + expected = pt.TealSimpleBlock( + [pt.TealOp(arg, pt.Op.int, 0), pt.TealOp(expr, pt.Op.balance)] + ) actual, _ = expr.__teal__(teal2Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected def test_balance_direct_ref(): - arg = Txn.sender() - expr = Balance(arg) - assert expr.type_of() == TealType.uint64 + arg = pt.Txn.sender() + expr = pt.Balance(arg) + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock( - [TealOp(arg, Op.txn, "Sender"), TealOp(expr, Op.balance)] + expected = pt.TealSimpleBlock( + [pt.TealOp(arg, pt.Op.txn, "Sender"), pt.TealOp(expr, pt.Op.balance)] ) actual, _ = expr.__teal__(teal2Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected def test_balance_invalid(): - with pytest.raises(TealTypeError): - args = [Txn.sender(), Int(17)] - expr = AssetHolding.balance(args[0], args[1]) - MinBalance(expr) + with pytest.raises(pt.TealTypeError): + args = [pt.Txn.sender(), pt.Int(17)] + expr = pt.AssetHolding.balance(args[0], args[1]) + pt.MinBalance(expr) def test_min_balance(): - arg = Int(0) - expr = MinBalance(arg) - assert expr.type_of() == TealType.uint64 + arg = pt.Int(0) + expr = pt.MinBalance(arg) + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock([TealOp(arg, Op.int, 0), TealOp(expr, Op.min_balance)]) + expected = pt.TealSimpleBlock( + [pt.TealOp(arg, pt.Op.int, 0), pt.TealOp(expr, pt.Op.min_balance)] + ) actual, _ = expr.__teal__(teal3Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected def test_min_balance_direct_ref(): - arg = Txn.sender() - expr = MinBalance(arg) - assert expr.type_of() == TealType.uint64 + arg = pt.Txn.sender() + expr = pt.MinBalance(arg) + assert expr.type_of() == pt.TealType.uint64 - expected = TealSimpleBlock( - [TealOp(arg, Op.txn, "Sender"), TealOp(expr, Op.min_balance)] + expected = pt.TealSimpleBlock( + [pt.TealOp(arg, pt.Op.txn, "Sender"), pt.TealOp(expr, pt.Op.min_balance)] ) actual, _ = expr.__teal__(teal3Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected def test_min_balance_invalid(): - with pytest.raises(TealTypeError): - args = [Txn.sender(), Int(17)] - expr = AssetHolding.balance(args[0], args[1]) - MinBalance(expr) + with pytest.raises(pt.TealTypeError): + args = [pt.Txn.sender(), pt.Int(17)] + expr = pt.AssetHolding.balance(args[0], args[1]) + pt.MinBalance(expr) def test_b_not(): - arg = Bytes("base16", "0xFFFFFFFFFFFFFFFFFF") - expr = BytesNot(arg) - assert expr.type_of() == TealType.bytes - - expected = TealSimpleBlock( - [TealOp(arg, Op.byte, "0xFFFFFFFFFFFFFFFFFF"), TealOp(expr, Op.b_not)] + arg = pt.Bytes("base16", "0xFFFFFFFFFFFFFFFFFF") + expr = pt.BytesNot(arg) + assert expr.type_of() == pt.TealType.bytes + + expected = pt.TealSimpleBlock( + [ + pt.TealOp(arg, pt.Op.byte, "0xFFFFFFFFFFFFFFFFFF"), + pt.TealOp(expr, pt.Op.b_not), + ] ) actual, _ = expr.__teal__(teal4Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected def test_b_not_invalid(): - with pytest.raises(TealTypeError): - BytesNot(Int(2)) + with pytest.raises(pt.TealTypeError): + pt.BytesNot(pt.Int(2)) def test_bsqrt(): - arg = Bytes("base16", "0xFEDCBA9876543210") - expr = BytesSqrt(arg) - assert expr.type_of() == TealType.bytes + arg = pt.Bytes("base16", "0xFEDCBA9876543210") + expr = pt.BytesSqrt(arg) + assert expr.type_of() == pt.TealType.bytes - expected = TealSimpleBlock( - [TealOp(arg, Op.byte, "0xFEDCBA9876543210"), TealOp(expr, Op.bsqrt)] + expected = pt.TealSimpleBlock( + [pt.TealOp(arg, pt.Op.byte, "0xFEDCBA9876543210"), pt.TealOp(expr, pt.Op.bsqrt)] ) actual, _ = expr.__teal__(teal6Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected def test_bsqrt_invalid(): - with pytest.raises(TealTypeError): - BytesSqrt(Int(2**64 - 1)) + with pytest.raises(pt.TealTypeError): + pt.BytesSqrt(pt.Int(2**64 - 1)) def test_b_zero(): - arg = Int(8) - expr = BytesZero(arg) - assert expr.type_of() == TealType.bytes + arg = pt.Int(8) + expr = pt.BytesZero(arg) + assert expr.type_of() == pt.TealType.bytes - expected = TealSimpleBlock([TealOp(arg, Op.int, 8), TealOp(expr, Op.bzero)]) + expected = pt.TealSimpleBlock( + [pt.TealOp(arg, pt.Op.int, 8), pt.TealOp(expr, pt.Op.bzero)] + ) actual, _ = expr.__teal__(teal4Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected def test_b_zero_invalid(): - with pytest.raises(TealTypeError): - BytesZero(Bytes("base16", "0x11")) + with pytest.raises(pt.TealTypeError): + pt.BytesZero(pt.Bytes("base16", "0x11")) def test_log(): - arg = Bytes("message") - expr = Log(arg) - assert expr.type_of() == TealType.none + arg = pt.Bytes("message") + expr = pt.Log(arg) + assert expr.type_of() == pt.TealType.none assert not expr.has_return() - expected = TealSimpleBlock( - [TealOp(arg, Op.byte, '"message"'), TealOp(expr, Op.log)] + expected = pt.TealSimpleBlock( + [pt.TealOp(arg, pt.Op.byte, '"message"'), pt.TealOp(expr, pt.Op.log)] ) actual, _ = expr.__teal__(teal5Options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected - with pytest.raises(TealInputError): + with pytest.raises(pt.TealInputError): expr.__teal__(teal4Options) def test_log_invalid(): - with pytest.raises(TealTypeError): - Log(Int(7)) + with pytest.raises(pt.TealTypeError): + pt.Log(pt.Int(7)) diff --git a/pyteal/ast/while_.py b/pyteal/ast/while_.py index be90d59fa..5dd6fd09b 100644 --- a/pyteal/ast/while_.py +++ b/pyteal/ast/while_.py @@ -1,13 +1,12 @@ from typing import TYPE_CHECKING, Optional -from ..errors import TealCompileError -from ..types import TealType, require_type -from ..ir import TealSimpleBlock, TealConditionalBlock -from .expr import Expr -from .seq import Seq +from pyteal.errors import TealCompileError +from pyteal.types import TealType, require_type +from pyteal.ir import TealSimpleBlock, TealConditionalBlock +from pyteal.ast.expr import Expr if TYPE_CHECKING: - from ..compiler import CompileOptions + from pyteal.compiler import CompileOptions class While(Expr): diff --git a/pyteal/ast/while_test.py b/pyteal/ast/while_test.py index ff539a046..0fdf909de 100644 --- a/pyteal/ast/while_test.py +++ b/pyteal/ast/while_test.py @@ -1,48 +1,47 @@ import pytest -from .. import * +import pyteal as pt -# this is not necessary but mypy complains if it's not included -from .. import CompileOptions - -options = CompileOptions() +options = pt.CompileOptions() def test_while_compiles(): - i = ScratchVar() - expr = While(Int(2)).Do(Seq([i.store(Int(0))])) - assert expr.type_of() == TealType.none + i = pt.ScratchVar() + expr = pt.While(pt.Int(2)).Do(pt.Seq([i.store(pt.Int(0))])) + assert expr.type_of() == pt.TealType.none assert not expr.has_return() expr.__teal__(options) def test_nested_whiles_compile(): - i = ScratchVar() - expr = While(Int(2)).Do(Seq([While(Int(2)).Do(Seq([i.store(Int(0))]))])) - assert expr.type_of() == TealType.none + i = pt.ScratchVar() + expr = pt.While(pt.Int(2)).Do( + pt.Seq([pt.While(pt.Int(2)).Do(pt.Seq([i.store(pt.Int(0))]))]) + ) + assert expr.type_of() == pt.TealType.none assert not expr.has_return() def test_continue_break(): - expr = While(Int(0)).Do(Seq([If(Int(1), Break(), Continue())])) - assert expr.type_of() == TealType.none + expr = pt.While(pt.Int(0)).Do(pt.Seq([pt.If(pt.Int(1), pt.Break(), pt.Continue())])) + assert expr.type_of() == pt.TealType.none assert not expr.has_return() expr.__teal__(options) def test_while(): - i = ScratchVar() - i.store(Int(0)) - items = [i.load() < Int(2), [i.store(i.load() + Int(1))]] - expr = While(items[0]).Do(Seq(items[1])) - assert expr.type_of() == TealType.none + i = pt.ScratchVar() + i.store(pt.Int(0)) + items = [i.load() < pt.Int(2), [i.store(i.load() + pt.Int(1))]] + expr = pt.While(items[0]).Do(pt.Seq(items[1])) + assert expr.type_of() == pt.TealType.none assert not expr.has_return() expected, condEnd = items[0].__teal__(options) - do, doEnd = Seq(items[1]).__teal__(options) - expectedBranch = TealConditionalBlock([]) - end = TealSimpleBlock([]) + do, doEnd = pt.Seq(items[1]).__teal__(options) + expectedBranch = pt.TealConditionalBlock([]) + end = pt.TealSimpleBlock([]) expectedBranch.setTrueBlock(do) expectedBranch.setFalseBlock(end) @@ -54,23 +53,23 @@ def test_while(): def test_while_continue(): - i = ScratchVar() - i.store(Int(0)) + i = pt.ScratchVar() + i.store(pt.Int(0)) items = [ - i.load() < Int(2), - i.store(i.load() + Int(1)), - If(i.load() == Int(1), Continue()), + i.load() < pt.Int(2), + i.store(i.load() + pt.Int(1)), + pt.If(i.load() == pt.Int(1), pt.Continue()), ] - expr = While(items[0]).Do(Seq(items[1], items[2])) - assert expr.type_of() == TealType.none + expr = pt.While(items[0]).Do(pt.Seq(items[1], items[2])) + assert expr.type_of() == pt.TealType.none assert not expr.has_return() options.enterLoop() expected, condEnd = items[0].__teal__(options) - do, doEnd = Seq([items[1], items[2]]).__teal__(options) - expectedBranch = TealConditionalBlock([]) - end = TealSimpleBlock([]) + do, doEnd = pt.Seq([items[1], items[2]]).__teal__(options) + expectedBranch = pt.TealConditionalBlock([]) + end = pt.TealSimpleBlock([]) expectedBranch.setTrueBlock(do) expectedBranch.setFalseBlock(end) @@ -88,23 +87,23 @@ def test_while_continue(): def test_while_break(): - i = ScratchVar() - i.store(Int(0)) + i = pt.ScratchVar() + i.store(pt.Int(0)) items = [ - i.load() < Int(2), - i.store(i.load() + Int(1)), - If(i.load() == Int(1), Break()), + i.load() < pt.Int(2), + i.store(i.load() + pt.Int(1)), + pt.If(i.load() == pt.Int(1), pt.Break()), ] - expr = While(items[0]).Do(Seq(items[1], items[2])) - assert expr.type_of() == TealType.none + expr = pt.While(items[0]).Do(pt.Seq(items[1], items[2])) + assert expr.type_of() == pt.TealType.none assert not expr.has_return() options.enterLoop() expected, condEnd = items[0].__teal__(options) - do, doEnd = Seq([items[1], items[2]]).__teal__(options) - expectedBranch = TealConditionalBlock([]) - end = TealSimpleBlock([]) + do, doEnd = pt.Seq([items[1], items[2]]).__teal__(options) + expectedBranch = pt.TealConditionalBlock([]) + end = pt.TealSimpleBlock([]) expectedBranch.setTrueBlock(do) expectedBranch.setFalseBlock(end) @@ -124,28 +123,28 @@ def test_while_break(): def test_while_invalid(): with pytest.raises(TypeError): - expr = While() + expr = pt.While() - with pytest.raises(TealCompileError): - expr = While(Int(2)) + with pytest.raises(pt.TealCompileError): + expr = pt.While(pt.Int(2)) expr.type_of() - with pytest.raises(TealCompileError): - expr = While(Int(2)) + with pytest.raises(pt.TealCompileError): + expr = pt.While(pt.Int(2)) expr.__teal__(options) - with pytest.raises(TealCompileError): - expr = While(Int(2)) + with pytest.raises(pt.TealCompileError): + expr = pt.While(pt.Int(2)) expr.type_of() - with pytest.raises(TealCompileError): - expr = While(Int(2)) + with pytest.raises(pt.TealCompileError): + expr = pt.While(pt.Int(2)) expr.__str__() - with pytest.raises(TealTypeError): - expr = While(Int(2)).Do(Int(2)) + with pytest.raises(pt.TealTypeError): + expr = pt.While(pt.Int(2)).Do(pt.Int(2)) expr.__str__() - with pytest.raises(TealCompileError): - expr = While(Int(0)).Do(Continue()).Do(Continue()) + with pytest.raises(pt.TealCompileError): + expr = pt.While(pt.Int(0)).Do(pt.Continue()).Do(pt.Continue()) expr.__str__() diff --git a/pyteal/ast/widemath.py b/pyteal/ast/widemath.py index 5b7fd756a..3098a7d3a 100644 --- a/pyteal/ast/widemath.py +++ b/pyteal/ast/widemath.py @@ -1,12 +1,12 @@ from typing import List, Tuple, TYPE_CHECKING -from ..types import TealType, require_type -from ..errors import TealInputError, TealInternalError, TealCompileError -from ..ir import TealOp, Op, TealSimpleBlock, TealBlock -from .expr import Expr +from pyteal.types import TealType +from pyteal.errors import TealInternalError, TealCompileError +from pyteal.ir import TealOp, Op, TealSimpleBlock +from pyteal.ast.expr import Expr if TYPE_CHECKING: - from ..compiler import CompileOptions + from pyteal.compiler import CompileOptions def multiplyFactors( diff --git a/pyteal/compiler/__init__.py b/pyteal/compiler/__init__.py index 74222f57c..13a3e6e92 100644 --- a/pyteal/compiler/__init__.py +++ b/pyteal/compiler/__init__.py @@ -1,4 +1,4 @@ -from .compiler import ( +from pyteal.compiler.compiler import ( MAX_TEAL_VERSION, MIN_TEAL_VERSION, DEFAULT_TEAL_VERSION, @@ -6,7 +6,7 @@ compileTeal, ) -from .optimizer import OptimizeOptions +from pyteal.compiler.optimizer import OptimizeOptions __all__ = [ "MAX_TEAL_VERSION", diff --git a/pyteal/compiler/compiler.py b/pyteal/compiler/compiler.py index beff7b8b0..d5a0174b2 100644 --- a/pyteal/compiler/compiler.py +++ b/pyteal/compiler/compiler.py @@ -1,29 +1,29 @@ from typing import List, Tuple, Set, Dict, Optional, cast -from .optimizer import OptimizeOptions, apply_global_optimizations +from pyteal.compiler.optimizer import OptimizeOptions, apply_global_optimizations -from ..types import TealType -from ..ast import ( +from pyteal.types import TealType +from pyteal.ast import ( Expr, Return, Seq, SubroutineDefinition, SubroutineDeclaration, ) -from ..ir import Mode, TealComponent, TealOp, TealBlock, TealSimpleBlock -from ..errors import TealInputError, TealInternalError +from pyteal.ir import Mode, TealComponent, TealOp, TealBlock, TealSimpleBlock +from pyteal.errors import TealInputError, TealInternalError -from .sort import sortBlocks -from .flatten import flattenBlocks, flattenSubroutines -from .scratchslots import ( +from pyteal.compiler.sort import sortBlocks +from pyteal.compiler.flatten import flattenBlocks, flattenSubroutines +from pyteal.compiler.scratchslots import ( assignScratchSlotsToSubroutines, collect_unoptimized_slots, ) -from .subroutines import ( +from pyteal.compiler.subroutines import ( spillLocalSlotsDuringRecursion, resolveSubroutines, ) -from .constants import createConstantBlocks +from pyteal.compiler.constants import createConstantBlocks MAX_TEAL_VERSION = 6 MIN_TEAL_VERSION = 2 diff --git a/pyteal/compiler/compiler_test.py b/pyteal/compiler/compiler_test.py index 74b5799b5..24f30dc18 100644 --- a/pyteal/compiler/compiler_test.py +++ b/pyteal/compiler/compiler_test.py @@ -1,28 +1,25 @@ import pytest -from .. import * - -# this is not necessary but mypy complains if it's not included -from ..ast import * +import pyteal as pt def test_compile_single(): - expr = Int(1) + expr = pt.Int(1) expected = """ #pragma version 2 int 1 return """.strip() - actual_application = compileTeal(expr, Mode.Application) - actual_signature = compileTeal(expr, Mode.Signature) + actual_application = pt.compileTeal(expr, pt.Mode.Application) + actual_signature = pt.compileTeal(expr, pt.Mode.Signature) assert actual_application == actual_signature assert actual_application == expected def test_compile_sequence(): - expr = Seq([Pop(Int(1)), Pop(Int(2)), Int(3) + Int(4)]) + expr = pt.Seq([pt.Pop(pt.Int(1)), pt.Pop(pt.Int(2)), pt.Int(3) + pt.Int(4)]) expected = """ #pragma version 2 @@ -35,15 +32,15 @@ def test_compile_sequence(): + return """.strip() - actual_application = compileTeal(expr, Mode.Application) - actual_signature = compileTeal(expr, Mode.Signature) + actual_application = pt.compileTeal(expr, pt.Mode.Application) + actual_signature = pt.compileTeal(expr, pt.Mode.Signature) assert actual_application == actual_signature assert actual_application == expected def test_compile_branch(): - expr = If(Int(1)).Then(Int(2)).Else(Int(3)) + expr = pt.If(pt.Int(1)).Then(pt.Int(2)).Else(pt.Int(3)) expected = """ #pragma version 2 @@ -56,15 +53,21 @@ def test_compile_branch(): main_l3: return """.strip() - actual_application = compileTeal(expr, Mode.Application) - actual_signature = compileTeal(expr, Mode.Signature) + actual_application = pt.compileTeal(expr, pt.Mode.Application) + actual_signature = pt.compileTeal(expr, pt.Mode.Signature) assert actual_application == actual_signature assert actual_application == expected def test_compile_branch_multiple(): - expr = If(Int(1)).Then(Int(2)).ElseIf(Int(3)).Then(Int(4)).Else(Int(5)) + expr = ( + pt.If(pt.Int(1)) + .Then(pt.Int(2)) + .ElseIf(pt.Int(3)) + .Then(pt.Int(4)) + .Else(pt.Int(5)) + ) expected = """ #pragma version 2 @@ -82,18 +85,18 @@ def test_compile_branch_multiple(): main_l5: return """.strip() - actual_application = compileTeal(expr, Mode.Application) - actual_signature = compileTeal(expr, Mode.Signature) + actual_application = pt.compileTeal(expr, pt.Mode.Application) + actual_signature = pt.compileTeal(expr, pt.Mode.Signature) assert actual_application == actual_signature assert actual_application == expected def test_empty_branch(): - program = Seq( + program = pt.Seq( [ - If(Txn.application_id() == Int(0)).Then(Seq()), - Approve(), + pt.If(pt.Txn.application_id() == pt.Int(0)).Then(pt.Seq()), + pt.Approve(), ] ) @@ -106,12 +109,14 @@ def test_empty_branch(): int 1 return """.strip() - actual = compileTeal(program, Mode.Application, version=5, assembleConstants=False) + actual = pt.compileTeal( + program, pt.Mode.Application, version=5, assembleConstants=False + ) assert actual == expected def test_compile_mode(): - expr = App.globalGet(Bytes("key")) + expr = pt.App.globalGet(pt.Bytes("key")) expected = """ #pragma version 2 @@ -119,128 +124,128 @@ def test_compile_mode(): app_global_get return """.strip() - actual_application = compileTeal(expr, Mode.Application) + actual_application = pt.compileTeal(expr, pt.Mode.Application) assert actual_application == expected - with pytest.raises(TealInputError): - compileTeal(expr, Mode.Signature) + with pytest.raises(pt.TealInputError): + pt.compileTeal(expr, pt.Mode.Signature) def test_compile_version_invalid(): - expr = Int(1) + expr = pt.Int(1) - with pytest.raises(TealInputError): - compileTeal(expr, Mode.Signature, version=1) # too small + with pytest.raises(pt.TealInputError): + pt.compileTeal(expr, pt.Mode.Signature, version=1) # too small - with pytest.raises(TealInputError): - compileTeal(expr, Mode.Signature, version=7) # too large + with pytest.raises(pt.TealInputError): + pt.compileTeal(expr, pt.Mode.Signature, version=7) # too large - with pytest.raises(TealInputError): - compileTeal(expr, Mode.Signature, version=2.0) # decimal + with pytest.raises(pt.TealInputError): + pt.compileTeal(expr, pt.Mode.Signature, version=2.0) # decimal def test_compile_version_2(): - expr = Int(1) + expr = pt.Int(1) expected = """ #pragma version 2 int 1 return """.strip() - actual = compileTeal(expr, Mode.Signature, version=2) + actual = pt.compileTeal(expr, pt.Mode.Signature, version=2) assert actual == expected def test_compile_version_default(): - expr = Int(1) + expr = pt.Int(1) - actual_default = compileTeal(expr, Mode.Signature) - actual_version_2 = compileTeal(expr, Mode.Signature, version=2) + actual_default = pt.compileTeal(expr, pt.Mode.Signature) + actual_version_2 = pt.compileTeal(expr, pt.Mode.Signature, version=2) assert actual_default == actual_version_2 def test_compile_version_3(): - expr = Int(1) + expr = pt.Int(1) expected = """ #pragma version 3 int 1 return """.strip() - actual = compileTeal(expr, Mode.Signature, version=3) + actual = pt.compileTeal(expr, pt.Mode.Signature, version=3) assert actual == expected def test_compile_version_4(): - expr = Int(1) + expr = pt.Int(1) expected = """ #pragma version 4 int 1 return """.strip() - actual = compileTeal(expr, Mode.Signature, version=4) + actual = pt.compileTeal(expr, pt.Mode.Signature, version=4) assert actual == expected def test_compile_version_5(): - expr = Int(1) + expr = pt.Int(1) expected = """ #pragma version 5 int 1 return """.strip() - actual = compileTeal(expr, Mode.Signature, version=5) + actual = pt.compileTeal(expr, pt.Mode.Signature, version=5) assert actual == expected def test_compile_version_6(): - expr = Int(1) + expr = pt.Int(1) expected = """ #pragma version 6 int 1 return """.strip() - actual = compileTeal(expr, Mode.Signature, version=6) + actual = pt.compileTeal(expr, pt.Mode.Signature, version=6) assert actual == expected def test_slot_load_before_store(): - program = AssetHolding.balance(Int(0), Int(0)).value() - with pytest.raises(TealInternalError): - compileTeal(program, Mode.Application, version=2) + program = pt.AssetHolding.balance(pt.Int(0), pt.Int(0)).value() + with pytest.raises(pt.TealInternalError): + pt.compileTeal(program, pt.Mode.Application, version=2) - program = AssetHolding.balance(Int(0), Int(0)).hasValue() - with pytest.raises(TealInternalError): - compileTeal(program, Mode.Application, version=2) + program = pt.AssetHolding.balance(pt.Int(0), pt.Int(0)).hasValue() + with pytest.raises(pt.TealInternalError): + pt.compileTeal(program, pt.Mode.Application, version=2) - program = App.globalGetEx(Int(0), Bytes("key")).value() - with pytest.raises(TealInternalError): - compileTeal(program, Mode.Application, version=2) + program = pt.App.globalGetEx(pt.Int(0), pt.Bytes("key")).value() + with pytest.raises(pt.TealInternalError): + pt.compileTeal(program, pt.Mode.Application, version=2) - program = App.globalGetEx(Int(0), Bytes("key")).hasValue() - with pytest.raises(TealInternalError): - compileTeal(program, Mode.Application, version=2) + program = pt.App.globalGetEx(pt.Int(0), pt.Bytes("key")).hasValue() + with pytest.raises(pt.TealInternalError): + pt.compileTeal(program, pt.Mode.Application, version=2) - program = ScratchVar().load() - with pytest.raises(TealInternalError): - compileTeal(program, Mode.Application, version=2) + program = pt.ScratchVar().load() + with pytest.raises(pt.TealInternalError): + pt.compileTeal(program, pt.Mode.Application, version=2) def test_assign_scratch_slots(): - myScratch = ScratchVar(TealType.uint64) - otherScratch = ScratchVar(TealType.uint64, 1) - anotherScratch = ScratchVar(TealType.uint64, 0) - lastScratch = ScratchVar(TealType.uint64) - prog = Seq( + myScratch = pt.ScratchVar(pt.TealType.uint64) + otherScratch = pt.ScratchVar(pt.TealType.uint64, 1) + anotherScratch = pt.ScratchVar(pt.TealType.uint64, 0) + lastScratch = pt.ScratchVar(pt.TealType.uint64) + prog = pt.Seq( [ - myScratch.store(Int(5)), # Slot 2 - otherScratch.store(Int(0)), # Slot 1 - anotherScratch.store(Int(7)), # Slot 0 - lastScratch.store(Int(9)), # Slot 3 - Approve(), + myScratch.store(pt.Int(5)), # Slot 2 + otherScratch.store(pt.Int(0)), # Slot 1 + anotherScratch.store(pt.Int(7)), # Slot 0 + lastScratch.store(pt.Int(9)), # Slot 3 + pt.Approve(), ] ) @@ -257,21 +262,21 @@ def test_assign_scratch_slots(): int 1 return """.strip() - actual = compileTeal(prog, mode=Mode.Signature, version=4) + actual = pt.compileTeal(prog, mode=pt.Mode.Signature, version=4) assert actual == expected def test_scratchvar_double_assign_invalid(): - myvar = ScratchVar(TealType.uint64, 10) - otherVar = ScratchVar(TealType.uint64, 10) - prog = Seq([myvar.store(Int(5)), otherVar.store(Int(0)), Approve()]) - with pytest.raises(TealInternalError): - compileTeal(prog, mode=Mode.Signature, version=4) + myvar = pt.ScratchVar(pt.TealType.uint64, 10) + otherVar = pt.ScratchVar(pt.TealType.uint64, 10) + prog = pt.Seq([myvar.store(pt.Int(5)), otherVar.store(pt.Int(0)), pt.Approve()]) + with pytest.raises(pt.TealInternalError): + pt.compileTeal(prog, mode=pt.Mode.Signature, version=4) def test_assembleConstants(): - program = Itob(Int(1) + Int(1) + Tmpl.Int("TMPL_VAR")) == Concat( - Bytes("test"), Bytes("test"), Bytes("test2") + program = pt.Itob(pt.Int(1) + pt.Int(1) + pt.Tmpl.Int("TMPL_VAR")) == pt.Concat( + pt.Bytes("test"), pt.Bytes("test"), pt.Bytes("test2") ) expectedNoAssemble = """ @@ -290,8 +295,8 @@ def test_assembleConstants(): == return """.strip() - actualNoAssemble = compileTeal( - program, Mode.Application, version=3, assembleConstants=False + actualNoAssemble = pt.compileTeal( + program, pt.Mode.Application, version=3, assembleConstants=False ) assert expectedNoAssemble == actualNoAssemble @@ -313,22 +318,22 @@ def test_assembleConstants(): == return """.strip() - actualAssemble = compileTeal( - program, Mode.Application, version=3, assembleConstants=True + actualAssemble = pt.compileTeal( + program, pt.Mode.Application, version=3, assembleConstants=True ) assert expectedAssemble == actualAssemble - with pytest.raises(TealInternalError): - compileTeal(program, Mode.Application, version=2, assembleConstants=True) + with pytest.raises(pt.TealInternalError): + pt.compileTeal(program, pt.Mode.Application, version=2, assembleConstants=True) def test_compile_while(): - i = ScratchVar() - program = Seq( + i = pt.ScratchVar() + program = pt.Seq( [ - i.store(Int(0)), - While(i.load() < Int(2)).Do(Seq([i.store(i.load() + Int(1))])), - Approve(), + i.store(pt.Int(0)), + pt.While(i.load() < pt.Int(2)).Do(pt.Seq([i.store(i.load() + pt.Int(1))])), + pt.Approve(), ] ) @@ -350,26 +355,30 @@ def test_compile_while(): int 1 return """.strip() - actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) + actual = pt.compileTeal( + program, pt.Mode.Application, version=4, assembleConstants=False + ) assert expected == actual # nested - i = ScratchVar() - j = ScratchVar() + i = pt.ScratchVar() + j = pt.ScratchVar() - program = Seq( + program = pt.Seq( [ - i.store(Int(0)), - While(i.load() < Int(2)).Do( - Seq( + i.store(pt.Int(0)), + pt.While(i.load() < pt.Int(2)).Do( + pt.Seq( [ - j.store(Int(0)), - While(j.load() < Int(5)).Do(Seq([j.store(j.load() + Int(1))])), - i.store(i.load() + Int(1)), + j.store(pt.Int(0)), + pt.While(j.load() < pt.Int(5)).Do( + pt.Seq([j.store(j.load() + pt.Int(1))]) + ), + i.store(i.load() + pt.Int(1)), ] ) ), - Approve(), + pt.Approve(), ] ) @@ -404,18 +413,20 @@ def test_compile_while(): return """.strip() - actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) + actual = pt.compileTeal( + program, pt.Mode.Application, version=4, assembleConstants=False + ) assert expected == actual def test_compile_for(): - i = ScratchVar() - program = Seq( + i = pt.ScratchVar() + program = pt.Seq( [ - For(i.store(Int(0)), i.load() < Int(10), i.store(i.load() + Int(1))).Do( - Seq([App.globalPut(Itob(i.load()), i.load() * Int(2))]) - ), - Approve(), + pt.For( + i.store(pt.Int(0)), i.load() < pt.Int(10), i.store(i.load() + pt.Int(1)) + ).Do(pt.Seq([pt.App.globalPut(pt.Itob(i.load()), i.load() * pt.Int(2))])), + pt.Approve(), ] ) @@ -442,26 +453,38 @@ def test_compile_for(): int 1 return """.strip() - actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) + actual = pt.compileTeal( + program, pt.Mode.Application, version=4, assembleConstants=False + ) assert expected == actual # nested - i = ScratchVar() - j = ScratchVar() - program = Seq( + i = pt.ScratchVar() + j = pt.ScratchVar() + program = pt.Seq( [ - For(i.store(Int(0)), i.load() < Int(10), i.store(i.load() + Int(1))).Do( - Seq( + pt.For( + i.store(pt.Int(0)), i.load() < pt.Int(10), i.store(i.load() + pt.Int(1)) + ).Do( + pt.Seq( [ - For( - j.store(Int(0)), - j.load() < Int(4), - j.store(j.load() + Int(2)), - ).Do(Seq([App.globalPut(Itob(j.load()), j.load() * Int(2))])) + pt.For( + j.store(pt.Int(0)), + j.load() < pt.Int(4), + j.store(j.load() + pt.Int(2)), + ).Do( + pt.Seq( + [ + pt.App.globalPut( + pt.Itob(j.load()), j.load() * pt.Int(2) + ) + ] + ) + ) ] ) ), - Approve(), + pt.Approve(), ] ) @@ -501,21 +524,28 @@ def test_compile_for(): int 1 return """.strip() - actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) + actual = pt.compileTeal( + program, pt.Mode.Application, version=4, assembleConstants=False + ) assert expected == actual def test_compile_break(): - # While - i = ScratchVar() - program = Seq( + # pt.While + i = pt.ScratchVar() + program = pt.Seq( [ - i.store(Int(0)), - While(i.load() < Int(3)).Do( - Seq([If(i.load() == Int(2), Break()), i.store(i.load() + Int(1))]) + i.store(pt.Int(0)), + pt.While(i.load() < pt.Int(3)).Do( + pt.Seq( + [ + pt.If(i.load() == pt.Int(2), pt.Break()), + i.store(i.load() + pt.Int(1)), + ] + ) ), - Approve(), + pt.Approve(), ] ) @@ -540,22 +570,26 @@ def test_compile_break(): int 1 return """.strip() - actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) + actual = pt.compileTeal( + program, pt.Mode.Application, version=4, assembleConstants=False + ) assert expected == actual - # For - i = ScratchVar() - program = Seq( + # pt.For + i = pt.ScratchVar() + program = pt.Seq( [ - For(i.store(Int(0)), i.load() < Int(10), i.store(i.load() + Int(1))).Do( - Seq( + pt.For( + i.store(pt.Int(0)), i.load() < pt.Int(10), i.store(i.load() + pt.Int(1)) + ).Do( + pt.Seq( [ - If(i.load() == Int(4), Break()), - App.globalPut(Itob(i.load()), i.load() * Int(2)), + pt.If(i.load() == pt.Int(4), pt.Break()), + pt.App.globalPut(pt.Itob(i.load()), i.load() * pt.Int(2)), ] ) ), - Approve(), + pt.Approve(), ] ) @@ -586,20 +620,27 @@ def test_compile_break(): int 1 return """.strip() - actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) + actual = pt.compileTeal( + program, pt.Mode.Application, version=4, assembleConstants=False + ) assert expected == actual def test_compile_continue(): - # While - i = ScratchVar() - program = Seq( + # pt.While + i = pt.ScratchVar() + program = pt.Seq( [ - i.store(Int(0)), - While(i.load() < Int(3)).Do( - Seq([If(i.load() == Int(2), Continue()), i.store(i.load() + Int(1))]) + i.store(pt.Int(0)), + pt.While(i.load() < pt.Int(3)).Do( + pt.Seq( + [ + pt.If(i.load() == pt.Int(2), pt.Continue()), + i.store(i.load() + pt.Int(1)), + ] + ) ), - Approve(), + pt.Approve(), ] ) @@ -625,22 +666,26 @@ def test_compile_continue(): int 1 return """.strip() - actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) + actual = pt.compileTeal( + program, pt.Mode.Application, version=4, assembleConstants=False + ) assert expected == actual - # For - i = ScratchVar() - program = Seq( + # pt.For + i = pt.ScratchVar() + program = pt.Seq( [ - For(i.store(Int(0)), i.load() < Int(10), i.store(i.load() + Int(1))).Do( - Seq( + pt.For( + i.store(pt.Int(0)), i.load() < pt.Int(10), i.store(i.load() + pt.Int(1)) + ).Do( + pt.Seq( [ - If(i.load() == Int(4), Continue()), - App.globalPut(Itob(i.load()), i.load() * Int(2)), + pt.If(i.load() == pt.Int(4), pt.Continue()), + pt.App.globalPut(pt.Itob(i.load()), i.load() * pt.Int(2)), ] ) ), - Approve(), + pt.Approve(), ] ) @@ -672,25 +717,27 @@ def test_compile_continue(): int 1 return """.strip() - actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) + actual = pt.compileTeal( + program, pt.Mode.Application, version=4, assembleConstants=False + ) assert expected == actual def test_compile_continue_break_nested(): - i = ScratchVar() - program = Seq( + i = pt.ScratchVar() + program = pt.Seq( [ - i.store(Int(0)), - While(i.load() < Int(10)).Do( - Seq( + i.store(pt.Int(0)), + pt.While(i.load() < pt.Int(10)).Do( + pt.Seq( [ - i.store(i.load() + Int(1)), - If(i.load() < Int(4), Continue(), Break()), + i.store(i.load() + pt.Int(1)), + pt.If(i.load() < pt.Int(4), pt.Continue(), pt.Break()), ] ) ), - Approve(), + pt.Approve(), ] ) @@ -714,31 +761,33 @@ def test_compile_continue_break_nested(): int 1 return """.strip() - actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) + actual = pt.compileTeal( + program, pt.Mode.Application, version=4, assembleConstants=False + ) assert expected == actual - i = ScratchVar() - program = Seq( + i = pt.ScratchVar() + program = pt.Seq( [ - i.store(Int(0)), - While(i.load() < Int(10)).Do( - Seq( + i.store(pt.Int(0)), + pt.While(i.load() < pt.Int(10)).Do( + pt.Seq( [ - If(i.load() == Int(8), Break()), - While(i.load() < Int(6)).Do( - Seq( + pt.If(i.load() == pt.Int(8), pt.Break()), + pt.While(i.load() < pt.Int(6)).Do( + pt.Seq( [ - If(i.load() == Int(3), Break()), - i.store(i.load() + Int(1)), + pt.If(i.load() == pt.Int(3), pt.Break()), + i.store(i.load() + pt.Int(1)), ] ) ), - If(i.load() < Int(5), Continue()), - i.store(i.load() + Int(1)), + pt.If(i.load() < pt.Int(5), pt.Continue()), + i.store(i.load() + pt.Int(1)), ] ) ), - Approve(), + pt.Approve(), ] ) @@ -784,39 +833,41 @@ def test_compile_continue_break_nested(): int 1 return """.strip() - actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) + actual = pt.compileTeal( + program, pt.Mode.Application, version=4, assembleConstants=False + ) assert expected == actual def test_compile_subroutine_unsupported(): - @Subroutine(TealType.none) - def storeValue(value: Expr) -> Expr: - return App.globalPut(Bytes("key"), value) + @pt.Subroutine(pt.TealType.none) + def storeValue(value: pt.Expr) -> pt.Expr: + return pt.App.globalPut(pt.Bytes("key"), value) - program = Seq( + program = pt.Seq( [ - If(Txn.sender() == Global.creator_address()).Then( - storeValue(Txn.application_args[0]) + pt.If(pt.Txn.sender() == pt.Global.creator_address()).Then( + storeValue(pt.Txn.application_args[0]) ), - Approve(), + pt.Approve(), ] ) - with pytest.raises(TealInputError): - compileTeal(program, Mode.Application, version=3) + with pytest.raises(pt.TealInputError): + pt.compileTeal(program, pt.Mode.Application, version=3) def test_compile_subroutine_no_return(): - @Subroutine(TealType.none) - def storeValue(value: Expr) -> Expr: - return App.globalPut(Bytes("key"), value) + @pt.Subroutine(pt.TealType.none) + def storeValue(value: pt.Expr) -> pt.Expr: + return pt.App.globalPut(pt.Bytes("key"), value) - program = Seq( + program = pt.Seq( [ - If(Txn.sender() == Global.creator_address()).Then( - storeValue(Txn.application_args[0]) + pt.If(pt.Txn.sender() == pt.Global.creator_address()).Then( + storeValue(pt.Txn.application_args[0]) ), - Approve(), + pt.Approve(), ] ) @@ -839,26 +890,28 @@ def storeValue(value: Expr) -> Expr: app_global_put retsub """.strip() - actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) + actual = pt.compileTeal( + program, pt.Mode.Application, version=4, assembleConstants=False + ) assert actual == expected def test_compile_subroutine_with_return(): - @Subroutine(TealType.none) - def storeValue(value: Expr) -> Expr: - return App.globalPut(Bytes("key"), value) + @pt.Subroutine(pt.TealType.none) + def storeValue(value: pt.Expr) -> pt.Expr: + return pt.App.globalPut(pt.Bytes("key"), value) - @Subroutine(TealType.bytes) - def getValue() -> Expr: - return App.globalGet(Bytes("key")) + @pt.Subroutine(pt.TealType.bytes) + def getValue() -> pt.Expr: + return pt.App.globalGet(pt.Bytes("key")) - program = Seq( + program = pt.Seq( [ - If(Txn.sender() == Global.creator_address()).Then( - storeValue(Txn.application_args[0]) + pt.If(pt.Txn.sender() == pt.Global.creator_address()).Then( + storeValue(pt.Txn.application_args[0]) ), - If(getValue() == Bytes("fail")).Then(Reject()), - Approve(), + pt.If(getValue() == pt.Bytes("fail")).Then(pt.Reject()), + pt.Approve(), ] ) @@ -896,20 +949,22 @@ def getValue() -> Expr: app_global_get retsub """.strip() - actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) + actual = pt.compileTeal( + program, pt.Mode.Application, version=4, assembleConstants=False + ) assert actual == expected def test_compile_subroutine_many_args(): - @Subroutine(TealType.uint64) + @pt.Subroutine(pt.TealType.uint64) def calculateSum( - a1: Expr, a2: Expr, a3: Expr, a4: Expr, a5: Expr, a6: Expr - ) -> Expr: + a1: pt.Expr, a2: pt.Expr, a3: pt.Expr, a4: pt.Expr, a5: pt.Expr, a6: pt.Expr + ) -> pt.Expr: return a1 + a2 + a3 + a4 + a5 + a6 - program = Return( - calculateSum(Int(1), Int(2), Int(3), Int(4), Int(5), Int(6)) - == Int(1 + 2 + 3 + 4 + 5 + 6) + program = pt.Return( + calculateSum(pt.Int(1), pt.Int(2), pt.Int(3), pt.Int(4), pt.Int(5), pt.Int(6)) + == pt.Int(1 + 2 + 3 + 4 + 5 + 6) ) expected = """#pragma version 4 @@ -945,22 +1000,24 @@ def calculateSum( + retsub """.strip() - actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) + actual = pt.compileTeal( + program, pt.Mode.Application, version=4, assembleConstants=False + ) assert actual == expected def test_compile_subroutine_recursive(): - @Subroutine(TealType.uint64) - def isEven(i: Expr) -> Expr: + @pt.Subroutine(pt.TealType.uint64) + def isEven(i: pt.Expr) -> pt.Expr: return ( - If(i == Int(0)) - .Then(Int(1)) - .ElseIf(i == Int(1)) - .Then(Int(0)) - .Else(isEven(i - Int(2))) + pt.If(i == pt.Int(0)) + .Then(pt.Int(1)) + .ElseIf(i == pt.Int(1)) + .Then(pt.Int(0)) + .Else(isEven(i - pt.Int(2))) ) - program = Return(isEven(Int(6))) + program = pt.Return(isEven(pt.Int(6))) expected = """#pragma version 4 int 6 @@ -997,22 +1054,24 @@ def isEven(i: Expr) -> Expr: isEven_0_l5: retsub """.strip() - actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) + actual = pt.compileTeal( + program, pt.Mode.Application, version=4, assembleConstants=False + ) assert actual == expected def test_compile_subroutine_recursive_5(): - @Subroutine(TealType.uint64) - def isEven(i: Expr) -> Expr: + @pt.Subroutine(pt.TealType.uint64) + def isEven(i: pt.Expr) -> pt.Expr: return ( - If(i == Int(0)) - .Then(Int(1)) - .ElseIf(i == Int(1)) - .Then(Int(0)) - .Else(isEven(i - Int(2))) + pt.If(i == pt.Int(0)) + .Then(pt.Int(1)) + .ElseIf(i == pt.Int(1)) + .Then(pt.Int(0)) + .Else(isEven(i - pt.Int(2))) ) - program = Return(isEven(Int(6))) + program = pt.Return(isEven(pt.Int(6))) expected = """#pragma version 5 int 6 @@ -1047,20 +1106,22 @@ def isEven(i: Expr) -> Expr: isEven_0_l5: retsub """.strip() - actual = compileTeal(program, Mode.Application, version=5, assembleConstants=False) + actual = pt.compileTeal( + program, pt.Mode.Application, version=5, assembleConstants=False + ) assert actual == expected def test_compile_subroutine_recursive_multiple_args(): - @Subroutine(TealType.uint64) + @pt.Subroutine(pt.TealType.uint64) def multiplyByAdding(a, b): return ( - If(a == Int(0)) - .Then(Return(Int(0))) - .Else(Return(b + multiplyByAdding(a - Int(1), b))) + pt.If(a == pt.Int(0)) + .Then(pt.Return(pt.Int(0))) + .Else(pt.Return(b + multiplyByAdding(a - pt.Int(1), b))) ) - program = Return(multiplyByAdding(Int(3), Int(5))) + program = pt.Return(multiplyByAdding(pt.Int(3), pt.Int(5))) expected = """#pragma version 4 int 3 @@ -1101,20 +1162,22 @@ def multiplyByAdding(a, b): int 0 retsub """.strip() - actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) + actual = pt.compileTeal( + program, pt.Mode.Application, version=4, assembleConstants=False + ) assert actual == expected def test_compile_subroutine_recursive_multiple_args_5(): - @Subroutine(TealType.uint64) + @pt.Subroutine(pt.TealType.uint64) def multiplyByAdding(a, b): return ( - If(a == Int(0)) - .Then(Return(Int(0))) - .Else(Return(b + multiplyByAdding(a - Int(1), b))) + pt.If(a == pt.Int(0)) + .Then(pt.Return(pt.Int(0))) + .Else(pt.Return(b + multiplyByAdding(a - pt.Int(1), b))) ) - program = Return(multiplyByAdding(Int(3), Int(5))) + program = pt.Return(multiplyByAdding(pt.Int(3), pt.Int(5))) expected = """#pragma version 5 int 3 @@ -1149,20 +1212,22 @@ def multiplyByAdding(a, b): int 0 retsub """.strip() - actual = compileTeal(program, Mode.Application, version=5, assembleConstants=False) + actual = pt.compileTeal( + program, pt.Mode.Application, version=5, assembleConstants=False + ) assert actual == expected def test_compile_subroutine_mutually_recursive_4(): - @Subroutine(TealType.uint64) - def isEven(i: Expr) -> Expr: - return If(i == Int(0), Int(1), Not(isOdd(i - Int(1)))) + @pt.Subroutine(pt.TealType.uint64) + def isEven(i: pt.Expr) -> pt.Expr: + return pt.If(i == pt.Int(0), pt.Int(1), pt.Not(isOdd(i - pt.Int(1)))) - @Subroutine(TealType.uint64) - def isOdd(i: Expr) -> Expr: - return If(i == Int(0), Int(0), Not(isEven(i - Int(1)))) + @pt.Subroutine(pt.TealType.uint64) + def isOdd(i: pt.Expr) -> pt.Expr: + return pt.If(i == pt.Int(0), pt.Int(0), pt.Not(isEven(i - pt.Int(1)))) - program = Return(isEven(Int(6))) + program = pt.Return(isEven(pt.Int(6))) expected = """#pragma version 4 int 6 @@ -1217,20 +1282,22 @@ def isOdd(i: Expr) -> Expr: isOdd_1_l3: retsub """.strip() - actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) + actual = pt.compileTeal( + program, pt.Mode.Application, version=4, assembleConstants=False + ) assert actual == expected def test_compile_subroutine_mutually_recursive_5(): - @Subroutine(TealType.uint64) - def isEven(i: Expr) -> Expr: - return If(i == Int(0), Int(1), Not(isOdd(i - Int(1)))) + @pt.Subroutine(pt.TealType.uint64) + def isEven(i: pt.Expr) -> pt.Expr: + return pt.If(i == pt.Int(0), pt.Int(1), pt.Not(isOdd(i - pt.Int(1)))) - @Subroutine(TealType.uint64) - def isOdd(i: Expr) -> Expr: - return If(i == Int(0), Int(0), Not(isEven(i - Int(1)))) + @pt.Subroutine(pt.TealType.uint64) + def isOdd(i: pt.Expr) -> pt.Expr: + return pt.If(i == pt.Int(0), pt.Int(0), pt.Not(isEven(i - pt.Int(1)))) - program = Return(isEven(Int(6))) + program = pt.Return(isEven(pt.Int(6))) expected = """#pragma version 5 int 6 @@ -1281,24 +1348,26 @@ def isOdd(i: Expr) -> Expr: isOdd_1_l3: retsub """.strip() - actual = compileTeal(program, Mode.Application, version=5, assembleConstants=False) + actual = pt.compileTeal( + program, pt.Mode.Application, version=5, assembleConstants=False + ) assert actual == expected def test_compile_subroutine_mutually_recursive_different_arg_count_4(): - @Subroutine(TealType.uint64) - def factorial(i: Expr) -> Expr: - return If( - i <= Int(1), - Int(1), - factorial_intermediate(i - Int(1), Bytes("inconsequential")) * i, + @pt.Subroutine(pt.TealType.uint64) + def factorial(i: pt.Expr) -> pt.Expr: + return pt.If( + i <= pt.Int(1), + pt.Int(1), + factorial_intermediate(i - pt.Int(1), pt.Bytes("inconsequential")) * i, ) - @Subroutine(TealType.uint64) - def factorial_intermediate(i: Expr, j: Expr) -> Expr: - return Seq(Pop(j), factorial(i)) + @pt.Subroutine(pt.TealType.uint64) + def factorial_intermediate(i: pt.Expr, j: pt.Expr) -> pt.Expr: + return pt.Seq(pt.Pop(j), factorial(i)) - program = Return(factorial(Int(4)) == Int(24)) + program = pt.Return(factorial(pt.Int(4)) == pt.Int(24)) expected = """#pragma version 4 int 4 @@ -1356,24 +1425,26 @@ def factorial_intermediate(i: Expr, j: Expr) -> Expr: pop retsub """.strip() - actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) + actual = pt.compileTeal( + program, pt.Mode.Application, version=4, assembleConstants=False + ) assert actual == expected def test_compile_subroutine_mutually_recursive_different_arg_count_5(): - @Subroutine(TealType.uint64) - def factorial(i: Expr) -> Expr: - return If( - i <= Int(1), - Int(1), - factorial_intermediate(i - Int(1), Bytes("inconsequential")) * i, + @pt.Subroutine(pt.TealType.uint64) + def factorial(i: pt.Expr) -> pt.Expr: + return pt.If( + i <= pt.Int(1), + pt.Int(1), + factorial_intermediate(i - pt.Int(1), pt.Bytes("inconsequential")) * i, ) - @Subroutine(TealType.uint64) - def factorial_intermediate(i: Expr, j: Expr) -> Expr: - return Seq(Log(j), factorial(i)) + @pt.Subroutine(pt.TealType.uint64) + def factorial_intermediate(i: pt.Expr, j: pt.Expr) -> pt.Expr: + return pt.Seq(pt.Log(j), factorial(i)) - program = Return(factorial(Int(4)) == Int(24)) + program = pt.Return(factorial(pt.Int(4)) == pt.Int(24)) expected = """#pragma version 5 int 4 @@ -1422,19 +1493,21 @@ def factorial_intermediate(i: Expr, j: Expr) -> Expr: store 1 retsub """.strip() - actual = compileTeal(program, Mode.Application, version=5, assembleConstants=False) + actual = pt.compileTeal( + program, pt.Mode.Application, version=5, assembleConstants=False + ) assert actual == expected def test_compile_loop_in_subroutine(): - @Subroutine(TealType.none) - def setState(value: Expr) -> Expr: - i = ScratchVar() - return For(i.store(Int(0)), i.load() < Int(10), i.store(i.load() + Int(1))).Do( - App.globalPut(Itob(i.load()), value) - ) + @pt.Subroutine(pt.TealType.none) + def setState(value: pt.Expr) -> pt.Expr: + i = pt.ScratchVar() + return pt.For( + i.store(pt.Int(0)), i.load() < pt.Int(10), i.store(i.load() + pt.Int(1)) + ).Do(pt.App.globalPut(pt.Itob(i.load()), value)) - program = Seq([setState(Bytes("value")), Approve()]) + program = pt.Seq([setState(pt.Bytes("value")), pt.Approve()]) expected = """#pragma version 4 byte "value" @@ -1464,17 +1537,19 @@ def setState(value: Expr) -> Expr: setState_0_l3: retsub """.strip() - actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) + actual = pt.compileTeal( + program, pt.Mode.Application, version=4, assembleConstants=False + ) assert actual == expected def test_compile_subroutine_invalid_name(): - def tmp() -> Expr: - return Int(1) + def tmp() -> pt.Expr: + return pt.Int(1) tmp.__name__ = "invalid-;)" - program = Subroutine(TealType.uint64)(tmp)() + program = pt.Subroutine(pt.TealType.uint64)(tmp)() expected = """#pragma version 4 callsub invalid_0 return @@ -1484,26 +1559,28 @@ def tmp() -> Expr: int 1 retsub """.strip() - actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) + actual = pt.compileTeal( + program, pt.Mode.Application, version=4, assembleConstants=False + ) assert actual == expected def test_compile_subroutine_assemble_constants(): - @Subroutine(TealType.none) - def storeValue(key: Expr, t1: Expr, t2: Expr, t3: Expr) -> Expr: - return App.globalPut(key, t1 + t2 + t3 + Int(10)) + @pt.Subroutine(pt.TealType.none) + def storeValue(key: pt.Expr, t1: pt.Expr, t2: pt.Expr, t3: pt.Expr) -> pt.Expr: + return pt.App.globalPut(key, t1 + t2 + t3 + pt.Int(10)) - program = Seq( + program = pt.Seq( [ - If(Txn.application_id() == Int(0)).Then( + pt.If(pt.Txn.application_id() == pt.Int(0)).Then( storeValue( - Concat(Bytes("test"), Bytes("test"), Bytes("a")), - Int(1), - Int(1), - Int(3), + pt.Concat(pt.Bytes("test"), pt.Bytes("test"), pt.Bytes("a")), + pt.Int(1), + pt.Int(1), + pt.Int(3), ) ), - Approve(), + pt.Approve(), ] ) @@ -1544,14 +1621,16 @@ def storeValue(key: Expr, t1: Expr, t2: Expr, t3: Expr) -> Expr: app_global_put retsub """.strip() - actual = compileTeal(program, Mode.Application, version=4, assembleConstants=True) + actual = pt.compileTeal( + program, pt.Mode.Application, version=4, assembleConstants=True + ) assert actual == expected def test_compile_wide_ratio(): cases = ( ( - WideRatio([Int(2), Int(100)], [Int(5)]), + pt.WideRatio([pt.Int(2), pt.Int(100)], [pt.Int(5)]), """#pragma version 5 int 2 int 100 @@ -1568,7 +1647,7 @@ def test_compile_wide_ratio(): """, ), ( - WideRatio([Int(2), Int(100)], [Int(10), Int(5)]), + pt.WideRatio([pt.Int(2), pt.Int(100)], [pt.Int(10), pt.Int(5)]), """#pragma version 5 int 2 int 100 @@ -1586,7 +1665,7 @@ def test_compile_wide_ratio(): """, ), ( - WideRatio([Int(2), Int(100), Int(3)], [Int(10), Int(5)]), + pt.WideRatio([pt.Int(2), pt.Int(100), pt.Int(3)], [pt.Int(10), pt.Int(5)]), """#pragma version 5 int 2 int 100 @@ -1613,7 +1692,9 @@ def test_compile_wide_ratio(): """, ), ( - WideRatio([Int(2), Int(100), Int(3)], [Int(10), Int(5), Int(6)]), + pt.WideRatio( + [pt.Int(2), pt.Int(100), pt.Int(3)], [pt.Int(10), pt.Int(5), pt.Int(6)] + ), """#pragma version 5 int 2 int 100 @@ -1649,7 +1730,10 @@ def test_compile_wide_ratio(): """, ), ( - WideRatio([Int(2), Int(100), Int(3), Int(4)], [Int(10), Int(5), Int(6)]), + pt.WideRatio( + [pt.Int(2), pt.Int(100), pt.Int(3), pt.Int(4)], + [pt.Int(10), pt.Int(5), pt.Int(6)], + ), """#pragma version 5 int 2 int 100 @@ -1696,7 +1780,7 @@ def test_compile_wide_ratio(): ) for program, expected in cases: - actual = compileTeal( - program, Mode.Application, version=5, assembleConstants=False + actual = pt.compileTeal( + program, pt.Mode.Application, version=5, assembleConstants=False ) assert actual == expected.strip() diff --git a/pyteal/compiler/constants.py b/pyteal/compiler/constants.py index b2449d1d2..b47215588 100644 --- a/pyteal/compiler/constants.py +++ b/pyteal/compiler/constants.py @@ -4,13 +4,13 @@ from collections import OrderedDict from algosdk import encoding -from ..ir import ( +from pyteal.ir import ( Op, TealOp, TealComponent, ) -from ..util import unescapeStr, correctBase32Padding -from ..errors import TealInternalError +from pyteal.util import unescapeStr, correctBase32Padding +from pyteal.errors import TealInternalError intEnumValues = { # OnComplete values diff --git a/pyteal/compiler/constants_test.py b/pyteal/compiler/constants_test.py index d9c3a2b4b..e18b19208 100644 --- a/pyteal/compiler/constants_test.py +++ b/pyteal/compiler/constants_test.py @@ -1,6 +1,6 @@ -from .. import * +import pyteal as pt -from .constants import ( +from pyteal.compiler.constants import ( extractIntValue, extractBytesValue, extractAddrValue, @@ -11,12 +11,12 @@ def test_extractIntValue(): tests = [ - (TealOp(None, Op.int, 0), 0), - (TealOp(None, Op.int, 5), 5), - (TealOp(None, Op.int, "pay"), 1), - (TealOp(None, Op.int, "NoOp"), 0), - (TealOp(None, Op.int, "UpdateApplication"), 4), - (TealOp(None, Op.int, "TMPL_NAME"), "TMPL_NAME"), + (pt.TealOp(None, pt.Op.int, 0), 0), + (pt.TealOp(None, pt.Op.int, 5), 5), + (pt.TealOp(None, pt.Op.int, "pay"), 1), + (pt.TealOp(None, pt.Op.int, "NoOp"), 0), + (pt.TealOp(None, pt.Op.int, "UpdateApplication"), 4), + (pt.TealOp(None, pt.Op.int, "TMPL_NAME"), "TMPL_NAME"), ] for op, expected in tests: @@ -26,19 +26,19 @@ def test_extractIntValue(): def test_extractBytesValue(): tests = [ - (TealOp(None, Op.byte, '""'), b""), - (TealOp(None, Op.byte, '"test"'), b"test"), - (TealOp(None, Op.byte, '"\\t\\n\\\\\\""'), b'\t\n\\"'), - (TealOp(None, Op.byte, "0x"), b""), - (TealOp(None, Op.byte, "0x00"), b"\x00"), - (TealOp(None, Op.byte, "0xFF00"), b"\xff\x00"), - (TealOp(None, Op.byte, "0xff00"), b"\xff\x00"), - (TealOp(None, Op.byte, "base32()"), b""), - (TealOp(None, Op.byte, "base32(ORSXG5A)"), b"test"), - (TealOp(None, Op.byte, "base32(ORSXG5A=)"), b"test"), - (TealOp(None, Op.byte, "base64()"), b""), - (TealOp(None, Op.byte, "base64(dGVzdA==)"), b"test"), - (TealOp(None, Op.byte, "TMPL_NAME"), "TMPL_NAME"), + (pt.TealOp(None, pt.Op.byte, '""'), b""), + (pt.TealOp(None, pt.Op.byte, '"test"'), b"test"), + (pt.TealOp(None, pt.Op.byte, '"\\t\\n\\\\\\""'), b'\t\n\\"'), + (pt.TealOp(None, pt.Op.byte, "0x"), b""), + (pt.TealOp(None, pt.Op.byte, "0x00"), b"\x00"), + (pt.TealOp(None, pt.Op.byte, "0xFF00"), b"\xff\x00"), + (pt.TealOp(None, pt.Op.byte, "0xff00"), b"\xff\x00"), + (pt.TealOp(None, pt.Op.byte, "base32()"), b""), + (pt.TealOp(None, pt.Op.byte, "base32(ORSXG5A)"), b"test"), + (pt.TealOp(None, pt.Op.byte, "base32(ORSXG5A=)"), b"test"), + (pt.TealOp(None, pt.Op.byte, "base64()"), b""), + (pt.TealOp(None, pt.Op.byte, "base64(dGVzdA==)"), b"test"), + (pt.TealOp(None, pt.Op.byte, "TMPL_NAME"), "TMPL_NAME"), ] for op, expected in tests: @@ -49,14 +49,14 @@ def test_extractBytesValue(): def test_extractAddrValue(): tests = [ ( - TealOp( + pt.TealOp( None, - Op.byte, + pt.Op.byte, "WSJHNPJ6YCLX5K4GUMQ4ISPK3ABMS3AL3F6CSVQTCUI5F4I65PWEMCWT3M", ), b"\xb4\x92v\xbd>\xc0\x97~\xab\x86\xa3!\xc4I\xea\xd8\x02\xc9l\x0b\xd9|)V\x13\x15\x11\xd2\xf1\x1e\xeb\xec", ), - (TealOp(None, Op.addr, "TMPL_NAME"), "TMPL_NAME"), + (pt.TealOp(None, pt.Op.addr, "TMPL_NAME"), "TMPL_NAME"), ] for op, expected in tests: @@ -68,29 +68,38 @@ def test_extractAddrValue(): def test_extractMethodValue(): tests = [ ( - TealOp(None, Op.method_signature, '"create(uint64)uint64"'), + pt.TealOp(None, pt.Op.method_signature, '"create(uint64)uint64"'), b"\x43\x46\x41\x01", ), - (TealOp(None, Op.method_signature, '"update()void"'), b"\xa0\xe8\x18\x72"), ( - TealOp(None, Op.method_signature, '"optIn(string)string"'), + pt.TealOp(None, pt.Op.method_signature, '"update()void"'), + b"\xa0\xe8\x18\x72", + ), + ( + pt.TealOp(None, pt.Op.method_signature, '"optIn(string)string"'), b"\xcf\xa6\x8e\x36", ), - (TealOp(None, Op.method_signature, '"closeOut()string"'), b"\xa9\xf4\x2b\x3d"), - (TealOp(None, Op.method_signature, '"delete()void"'), b"\x24\x37\x8d\x3c"), ( - TealOp(None, Op.method_signature, '"add(uint64,uint64)uint64"'), + pt.TealOp(None, pt.Op.method_signature, '"closeOut()string"'), + b"\xa9\xf4\x2b\x3d", + ), + ( + pt.TealOp(None, pt.Op.method_signature, '"delete()void"'), + b"\x24\x37\x8d\x3c", + ), + ( + pt.TealOp(None, pt.Op.method_signature, '"add(uint64,uint64)uint64"'), b"\xfe\x6b\xdf\x69", ), - (TealOp(None, Op.method_signature, '"empty()void"'), b"\xa8\x8c\x26\xa5"), + (pt.TealOp(None, pt.Op.method_signature, '"empty()void"'), b"\xa8\x8c\x26\xa5"), ( - TealOp(None, Op.method_signature, '"payment(pay,uint64)bool"'), + pt.TealOp(None, pt.Op.method_signature, '"payment(pay,uint64)bool"'), b"\x3e\x3b\x3d\x28", ), ( - TealOp( + pt.TealOp( None, - Op.method_signature, + pt.Op.method_signature, '"referenceTest(account,application,account,asset,account,asset,asset,application,application)uint8[9]"', ), b"\x0d\xf0\x05\x0f", @@ -113,9 +122,9 @@ def test_createConstantBlocks_empty(): def test_createConstantBlocks_no_consts(): ops = [ - TealOp(None, Op.txn, "Sender"), - TealOp(None, Op.txn, "Receiver"), - TealOp(None, Op.eq), + pt.TealOp(None, pt.Op.txn, "Sender"), + pt.TealOp(None, pt.Op.txn, "Receiver"), + pt.TealOp(None, pt.Op.eq), ] expected = ops[:] @@ -126,15 +135,15 @@ def test_createConstantBlocks_no_consts(): def test_createConstantBlocks_pushint(): ops = [ - TealOp(None, Op.int, 0), - TealOp(None, Op.int, "OptIn"), - TealOp(None, Op.add), + pt.TealOp(None, pt.Op.int, 0), + pt.TealOp(None, pt.Op.int, "OptIn"), + pt.TealOp(None, pt.Op.add), ] expected = [ - TealOp(None, Op.pushint, 0, "//", 0), - TealOp(None, Op.pushint, 1, "//", "OptIn"), - TealOp(None, Op.add), + pt.TealOp(None, pt.Op.pushint, 0, "//", 0), + pt.TealOp(None, pt.Op.pushint, 1, "//", "OptIn"), + pt.TealOp(None, pt.Op.add), ] actual = createConstantBlocks(ops) @@ -143,16 +152,16 @@ def test_createConstantBlocks_pushint(): def test_createConstantBlocks_intblock_single(): ops = [ - TealOp(None, Op.int, 1), - TealOp(None, Op.int, "OptIn"), - TealOp(None, Op.add), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.int, "OptIn"), + pt.TealOp(None, pt.Op.add), ] expected = [ - TealOp(None, Op.intcblock, 1), - TealOp(None, Op.intc_0, "//", 1), - TealOp(None, Op.intc_0, "//", "OptIn"), - TealOp(None, Op.add), + pt.TealOp(None, pt.Op.intcblock, 1), + pt.TealOp(None, pt.Op.intc_0, "//", 1), + pt.TealOp(None, pt.Op.intc_0, "//", "OptIn"), + pt.TealOp(None, pt.Op.add), ] actual = createConstantBlocks(ops) @@ -161,28 +170,28 @@ def test_createConstantBlocks_intblock_single(): def test_createConstantBlocks_intblock_multiple(): ops = [ - TealOp(None, Op.int, 1), - TealOp(None, Op.int, "OptIn"), - TealOp(None, Op.add), - TealOp(None, Op.int, 2), - TealOp(None, Op.int, "keyreg"), - TealOp(None, Op.add), - TealOp(None, Op.int, 3), - TealOp(None, Op.int, "ClearState"), - TealOp(None, Op.add), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.int, "OptIn"), + pt.TealOp(None, pt.Op.add), + pt.TealOp(None, pt.Op.int, 2), + pt.TealOp(None, pt.Op.int, "keyreg"), + pt.TealOp(None, pt.Op.add), + pt.TealOp(None, pt.Op.int, 3), + pt.TealOp(None, pt.Op.int, "ClearState"), + pt.TealOp(None, pt.Op.add), ] expected = [ - TealOp(None, Op.intcblock, 1, 2, 3), - TealOp(None, Op.intc_0, "//", 1), - TealOp(None, Op.intc_0, "//", "OptIn"), - TealOp(None, Op.add), - TealOp(None, Op.intc_1, "//", 2), - TealOp(None, Op.intc_1, "//", "keyreg"), - TealOp(None, Op.add), - TealOp(None, Op.intc_2, "//", 3), - TealOp(None, Op.intc_2, "//", "ClearState"), - TealOp(None, Op.add), + pt.TealOp(None, pt.Op.intcblock, 1, 2, 3), + pt.TealOp(None, pt.Op.intc_0, "//", 1), + pt.TealOp(None, pt.Op.intc_0, "//", "OptIn"), + pt.TealOp(None, pt.Op.add), + pt.TealOp(None, pt.Op.intc_1, "//", 2), + pt.TealOp(None, pt.Op.intc_1, "//", "keyreg"), + pt.TealOp(None, pt.Op.add), + pt.TealOp(None, pt.Op.intc_2, "//", 3), + pt.TealOp(None, pt.Op.intc_2, "//", "ClearState"), + pt.TealOp(None, pt.Op.add), ] actual = createConstantBlocks(ops) @@ -191,28 +200,28 @@ def test_createConstantBlocks_intblock_multiple(): def test_createConstantBlocks_intblock_pushint(): ops = [ - TealOp(None, Op.int, 1), - TealOp(None, Op.int, "OptIn"), - TealOp(None, Op.add), - TealOp(None, Op.int, 2), - TealOp(None, Op.int, 3), - TealOp(None, Op.add), - TealOp(None, Op.int, 3), - TealOp(None, Op.int, "ClearState"), - TealOp(None, Op.add), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.int, "OptIn"), + pt.TealOp(None, pt.Op.add), + pt.TealOp(None, pt.Op.int, 2), + pt.TealOp(None, pt.Op.int, 3), + pt.TealOp(None, pt.Op.add), + pt.TealOp(None, pt.Op.int, 3), + pt.TealOp(None, pt.Op.int, "ClearState"), + pt.TealOp(None, pt.Op.add), ] expected = [ - TealOp(None, Op.intcblock, 3, 1), - TealOp(None, Op.intc_1, "//", 1), - TealOp(None, Op.intc_1, "//", "OptIn"), - TealOp(None, Op.add), - TealOp(None, Op.pushint, 2, "//", 2), - TealOp(None, Op.intc_0, "//", 3), - TealOp(None, Op.add), - TealOp(None, Op.intc_0, "//", 3), - TealOp(None, Op.intc_0, "//", "ClearState"), - TealOp(None, Op.add), + pt.TealOp(None, pt.Op.intcblock, 3, 1), + pt.TealOp(None, pt.Op.intc_1, "//", 1), + pt.TealOp(None, pt.Op.intc_1, "//", "OptIn"), + pt.TealOp(None, pt.Op.add), + pt.TealOp(None, pt.Op.pushint, 2, "//", 2), + pt.TealOp(None, pt.Op.intc_0, "//", 3), + pt.TealOp(None, pt.Op.add), + pt.TealOp(None, pt.Op.intc_0, "//", 3), + pt.TealOp(None, pt.Op.intc_0, "//", "ClearState"), + pt.TealOp(None, pt.Op.add), ] actual = createConstantBlocks(ops) @@ -221,17 +230,17 @@ def test_createConstantBlocks_intblock_pushint(): def test_createConstantBlocks_pushbytes(): ops = [ - TealOp(None, Op.byte, "0x0102"), - TealOp(None, Op.byte, "0x0103"), - TealOp(None, Op.method_signature, '"empty()void"'), - TealOp(None, Op.concat), + pt.TealOp(None, pt.Op.byte, "0x0102"), + pt.TealOp(None, pt.Op.byte, "0x0103"), + pt.TealOp(None, pt.Op.method_signature, '"empty()void"'), + pt.TealOp(None, pt.Op.concat), ] expected = [ - TealOp(None, Op.pushbytes, "0x0102", "//", "0x0102"), - TealOp(None, Op.pushbytes, "0x0103", "//", "0x0103"), - TealOp(None, Op.pushbytes, "0xa88c26a5", "//", '"empty()void"'), - TealOp(None, Op.concat), + pt.TealOp(None, pt.Op.pushbytes, "0x0102", "//", "0x0102"), + pt.TealOp(None, pt.Op.pushbytes, "0x0103", "//", "0x0103"), + pt.TealOp(None, pt.Op.pushbytes, "0xa88c26a5", "//", '"empty()void"'), + pt.TealOp(None, pt.Op.concat), ] actual = createConstantBlocks(ops) @@ -240,20 +249,20 @@ def test_createConstantBlocks_pushbytes(): def test_createConstantBlocks_byteblock_single(): ops = [ - TealOp(None, Op.byte, "0x0102"), - TealOp(None, Op.byte, "base64(AQI=)"), - TealOp(None, Op.concat), - TealOp(None, Op.byte, "base32(AEBA====)"), - TealOp(None, Op.concat), + pt.TealOp(None, pt.Op.byte, "0x0102"), + pt.TealOp(None, pt.Op.byte, "base64(AQI=)"), + pt.TealOp(None, pt.Op.concat), + pt.TealOp(None, pt.Op.byte, "base32(AEBA====)"), + pt.TealOp(None, pt.Op.concat), ] expected = [ - TealOp(None, Op.bytecblock, "0x0102"), - TealOp(None, Op.bytec_0, "//", "0x0102"), - TealOp(None, Op.bytec_0, "//", "base64(AQI=)"), - TealOp(None, Op.concat), - TealOp(None, Op.bytec_0, "//", "base32(AEBA====)"), - TealOp(None, Op.concat), + pt.TealOp(None, pt.Op.bytecblock, "0x0102"), + pt.TealOp(None, pt.Op.bytec_0, "//", "0x0102"), + pt.TealOp(None, pt.Op.bytec_0, "//", "base64(AQI=)"), + pt.TealOp(None, pt.Op.concat), + pt.TealOp(None, pt.Op.bytec_0, "//", "base32(AEBA====)"), + pt.TealOp(None, pt.Op.concat), ] actual = createConstantBlocks(ops) @@ -262,65 +271,67 @@ def test_createConstantBlocks_byteblock_single(): def test_createConstantBlocks_byteblock_multiple(): ops = [ - TealOp(None, Op.byte, "0x0102"), - TealOp(None, Op.byte, "base64(AQI=)"), - TealOp(None, Op.concat), - TealOp(None, Op.byte, "base32(AEBA====)"), - TealOp(None, Op.concat), - TealOp(None, Op.byte, '"test"'), - TealOp(None, Op.concat), - TealOp(None, Op.byte, "base32(ORSXG5A=)"), - TealOp(None, Op.concat), - TealOp( + pt.TealOp(None, pt.Op.byte, "0x0102"), + pt.TealOp(None, pt.Op.byte, "base64(AQI=)"), + pt.TealOp(None, pt.Op.concat), + pt.TealOp(None, pt.Op.byte, "base32(AEBA====)"), + pt.TealOp(None, pt.Op.concat), + pt.TealOp(None, pt.Op.byte, '"test"'), + pt.TealOp(None, pt.Op.concat), + pt.TealOp(None, pt.Op.byte, "base32(ORSXG5A=)"), + pt.TealOp(None, pt.Op.concat), + pt.TealOp( None, - Op.byte, + pt.Op.byte, "0xb49276bd3ec0977eab86a321c449ead802c96c0bd97c2956131511d2f11eebec", ), - TealOp(None, Op.concat), - TealOp( - None, Op.addr, "WSJHNPJ6YCLX5K4GUMQ4ISPK3ABMS3AL3F6CSVQTCUI5F4I65PWEMCWT3M" + pt.TealOp(None, pt.Op.concat), + pt.TealOp( + None, + pt.Op.addr, + "WSJHNPJ6YCLX5K4GUMQ4ISPK3ABMS3AL3F6CSVQTCUI5F4I65PWEMCWT3M", ), - TealOp(None, Op.concat), - TealOp(None, Op.method_signature, '"closeOut()string"'), - TealOp(None, Op.concat), - TealOp(None, Op.byte, "base64(qfQrPQ==)"), + pt.TealOp(None, pt.Op.concat), + pt.TealOp(None, pt.Op.method_signature, '"closeOut()string"'), + pt.TealOp(None, pt.Op.concat), + pt.TealOp(None, pt.Op.byte, "base64(qfQrPQ==)"), ] expected = [ - TealOp( + pt.TealOp( None, - Op.bytecblock, + pt.Op.bytecblock, "0x0102", "0x74657374", "0xb49276bd3ec0977eab86a321c449ead802c96c0bd97c2956131511d2f11eebec", "0xa9f42b3d", ), - TealOp(None, Op.bytec_0, "//", "0x0102"), - TealOp(None, Op.bytec_0, "//", "base64(AQI=)"), - TealOp(None, Op.concat), - TealOp(None, Op.bytec_0, "//", "base32(AEBA====)"), - TealOp(None, Op.concat), - TealOp(None, Op.bytec_1, "//", '"test"'), - TealOp(None, Op.concat), - TealOp(None, Op.bytec_1, "//", "base32(ORSXG5A=)"), - TealOp(None, Op.concat), - TealOp( + pt.TealOp(None, pt.Op.bytec_0, "//", "0x0102"), + pt.TealOp(None, pt.Op.bytec_0, "//", "base64(AQI=)"), + pt.TealOp(None, pt.Op.concat), + pt.TealOp(None, pt.Op.bytec_0, "//", "base32(AEBA====)"), + pt.TealOp(None, pt.Op.concat), + pt.TealOp(None, pt.Op.bytec_1, "//", '"test"'), + pt.TealOp(None, pt.Op.concat), + pt.TealOp(None, pt.Op.bytec_1, "//", "base32(ORSXG5A=)"), + pt.TealOp(None, pt.Op.concat), + pt.TealOp( None, - Op.bytec_2, + pt.Op.bytec_2, "//", "0xb49276bd3ec0977eab86a321c449ead802c96c0bd97c2956131511d2f11eebec", ), - TealOp(None, Op.concat), - TealOp( + pt.TealOp(None, pt.Op.concat), + pt.TealOp( None, - Op.bytec_2, + pt.Op.bytec_2, "//", "WSJHNPJ6YCLX5K4GUMQ4ISPK3ABMS3AL3F6CSVQTCUI5F4I65PWEMCWT3M", ), - TealOp(None, Op.concat), - TealOp(None, Op.bytec_3, "//", '"closeOut()string"'), - TealOp(None, Op.concat), - TealOp(None, Op.bytec_3, "//", "base64(qfQrPQ==)"), + pt.TealOp(None, pt.Op.concat), + pt.TealOp(None, pt.Op.bytec_3, "//", '"closeOut()string"'), + pt.TealOp(None, pt.Op.concat), + pt.TealOp(None, pt.Op.bytec_3, "//", "base64(qfQrPQ==)"), ] actual = createConstantBlocks(ops) @@ -329,40 +340,42 @@ def test_createConstantBlocks_byteblock_multiple(): def test_createConstantBlocks_byteblock_pushbytes(): ops = [ - TealOp(None, Op.byte, "0x0102"), - TealOp(None, Op.byte, "base64(AQI=)"), - TealOp(None, Op.concat), - TealOp(None, Op.byte, "base32(AEBA====)"), - TealOp(None, Op.concat), - TealOp(None, Op.byte, '"test"'), - TealOp(None, Op.concat), - TealOp(None, Op.byte, "base32(ORSXG5A=)"), - TealOp(None, Op.concat), - TealOp( - None, Op.addr, "WSJHNPJ6YCLX5K4GUMQ4ISPK3ABMS3AL3F6CSVQTCUI5F4I65PWEMCWT3M" + pt.TealOp(None, pt.Op.byte, "0x0102"), + pt.TealOp(None, pt.Op.byte, "base64(AQI=)"), + pt.TealOp(None, pt.Op.concat), + pt.TealOp(None, pt.Op.byte, "base32(AEBA====)"), + pt.TealOp(None, pt.Op.concat), + pt.TealOp(None, pt.Op.byte, '"test"'), + pt.TealOp(None, pt.Op.concat), + pt.TealOp(None, pt.Op.byte, "base32(ORSXG5A=)"), + pt.TealOp(None, pt.Op.concat), + pt.TealOp( + None, + pt.Op.addr, + "WSJHNPJ6YCLX5K4GUMQ4ISPK3ABMS3AL3F6CSVQTCUI5F4I65PWEMCWT3M", ), - TealOp(None, Op.concat), + pt.TealOp(None, pt.Op.concat), ] expected = [ - TealOp(None, Op.bytecblock, "0x0102", "0x74657374"), - TealOp(None, Op.bytec_0, "//", "0x0102"), - TealOp(None, Op.bytec_0, "//", "base64(AQI=)"), - TealOp(None, Op.concat), - TealOp(None, Op.bytec_0, "//", "base32(AEBA====)"), - TealOp(None, Op.concat), - TealOp(None, Op.bytec_1, "//", '"test"'), - TealOp(None, Op.concat), - TealOp(None, Op.bytec_1, "//", "base32(ORSXG5A=)"), - TealOp(None, Op.concat), - TealOp( + pt.TealOp(None, pt.Op.bytecblock, "0x0102", "0x74657374"), + pt.TealOp(None, pt.Op.bytec_0, "//", "0x0102"), + pt.TealOp(None, pt.Op.bytec_0, "//", "base64(AQI=)"), + pt.TealOp(None, pt.Op.concat), + pt.TealOp(None, pt.Op.bytec_0, "//", "base32(AEBA====)"), + pt.TealOp(None, pt.Op.concat), + pt.TealOp(None, pt.Op.bytec_1, "//", '"test"'), + pt.TealOp(None, pt.Op.concat), + pt.TealOp(None, pt.Op.bytec_1, "//", "base32(ORSXG5A=)"), + pt.TealOp(None, pt.Op.concat), + pt.TealOp( None, - Op.pushbytes, + pt.Op.pushbytes, "0xb49276bd3ec0977eab86a321c449ead802c96c0bd97c2956131511d2f11eebec", "//", "WSJHNPJ6YCLX5K4GUMQ4ISPK3ABMS3AL3F6CSVQTCUI5F4I65PWEMCWT3M", ), - TealOp(None, Op.concat), + pt.TealOp(None, pt.Op.concat), ] actual = createConstantBlocks(ops) @@ -371,59 +384,61 @@ def test_createConstantBlocks_byteblock_pushbytes(): def test_createConstantBlocks_all(): ops = [ - TealOp(None, Op.byte, "0x0102"), - TealOp(None, Op.byte, "base64(AQI=)"), - TealOp(None, Op.concat), - TealOp(None, Op.byte, "base32(AEBA====)"), - TealOp(None, Op.concat), - TealOp(None, Op.byte, '"test"'), - TealOp(None, Op.concat), - TealOp(None, Op.byte, "base32(ORSXG5A=)"), - TealOp(None, Op.concat), - TealOp( - None, Op.addr, "WSJHNPJ6YCLX5K4GUMQ4ISPK3ABMS3AL3F6CSVQTCUI5F4I65PWEMCWT3M" + pt.TealOp(None, pt.Op.byte, "0x0102"), + pt.TealOp(None, pt.Op.byte, "base64(AQI=)"), + pt.TealOp(None, pt.Op.concat), + pt.TealOp(None, pt.Op.byte, "base32(AEBA====)"), + pt.TealOp(None, pt.Op.concat), + pt.TealOp(None, pt.Op.byte, '"test"'), + pt.TealOp(None, pt.Op.concat), + pt.TealOp(None, pt.Op.byte, "base32(ORSXG5A=)"), + pt.TealOp(None, pt.Op.concat), + pt.TealOp( + None, + pt.Op.addr, + "WSJHNPJ6YCLX5K4GUMQ4ISPK3ABMS3AL3F6CSVQTCUI5F4I65PWEMCWT3M", ), - TealOp(None, Op.concat), - TealOp(None, Op.int, 1), - TealOp(None, Op.int, "OptIn"), - TealOp(None, Op.add), - TealOp(None, Op.int, 2), - TealOp(None, Op.int, 3), - TealOp(None, Op.add), - TealOp(None, Op.int, 3), - TealOp(None, Op.int, "ClearState"), - TealOp(None, Op.add), + pt.TealOp(None, pt.Op.concat), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.int, "OptIn"), + pt.TealOp(None, pt.Op.add), + pt.TealOp(None, pt.Op.int, 2), + pt.TealOp(None, pt.Op.int, 3), + pt.TealOp(None, pt.Op.add), + pt.TealOp(None, pt.Op.int, 3), + pt.TealOp(None, pt.Op.int, "ClearState"), + pt.TealOp(None, pt.Op.add), ] expected = [ - TealOp(None, Op.intcblock, 3, 1), - TealOp(None, Op.bytecblock, "0x0102", "0x74657374"), - TealOp(None, Op.bytec_0, "//", "0x0102"), - TealOp(None, Op.bytec_0, "//", "base64(AQI=)"), - TealOp(None, Op.concat), - TealOp(None, Op.bytec_0, "//", "base32(AEBA====)"), - TealOp(None, Op.concat), - TealOp(None, Op.bytec_1, "//", '"test"'), - TealOp(None, Op.concat), - TealOp(None, Op.bytec_1, "//", "base32(ORSXG5A=)"), - TealOp(None, Op.concat), - TealOp( + pt.TealOp(None, pt.Op.intcblock, 3, 1), + pt.TealOp(None, pt.Op.bytecblock, "0x0102", "0x74657374"), + pt.TealOp(None, pt.Op.bytec_0, "//", "0x0102"), + pt.TealOp(None, pt.Op.bytec_0, "//", "base64(AQI=)"), + pt.TealOp(None, pt.Op.concat), + pt.TealOp(None, pt.Op.bytec_0, "//", "base32(AEBA====)"), + pt.TealOp(None, pt.Op.concat), + pt.TealOp(None, pt.Op.bytec_1, "//", '"test"'), + pt.TealOp(None, pt.Op.concat), + pt.TealOp(None, pt.Op.bytec_1, "//", "base32(ORSXG5A=)"), + pt.TealOp(None, pt.Op.concat), + pt.TealOp( None, - Op.pushbytes, + pt.Op.pushbytes, "0xb49276bd3ec0977eab86a321c449ead802c96c0bd97c2956131511d2f11eebec", "//", "WSJHNPJ6YCLX5K4GUMQ4ISPK3ABMS3AL3F6CSVQTCUI5F4I65PWEMCWT3M", ), - TealOp(None, Op.concat), - TealOp(None, Op.intc_1, "//", 1), - TealOp(None, Op.intc_1, "//", "OptIn"), - TealOp(None, Op.add), - TealOp(None, Op.pushint, 2, "//", 2), - TealOp(None, Op.intc_0, "//", 3), - TealOp(None, Op.add), - TealOp(None, Op.intc_0, "//", 3), - TealOp(None, Op.intc_0, "//", "ClearState"), - TealOp(None, Op.add), + pt.TealOp(None, pt.Op.concat), + pt.TealOp(None, pt.Op.intc_1, "//", 1), + pt.TealOp(None, pt.Op.intc_1, "//", "OptIn"), + pt.TealOp(None, pt.Op.add), + pt.TealOp(None, pt.Op.pushint, 2, "//", 2), + pt.TealOp(None, pt.Op.intc_0, "//", 3), + pt.TealOp(None, pt.Op.add), + pt.TealOp(None, pt.Op.intc_0, "//", 3), + pt.TealOp(None, pt.Op.intc_0, "//", "ClearState"), + pt.TealOp(None, pt.Op.add), ] actual = createConstantBlocks(ops) @@ -432,20 +447,20 @@ def test_createConstantBlocks_all(): def test_createConstantBlocks_tmpl_int(): ops = [ - TealOp(None, Op.int, "TMPL_INT_1"), - TealOp(None, Op.int, "TMPL_INT_1"), - TealOp(None, Op.eq), - TealOp(None, Op.int, "TMPL_INT_2"), - TealOp(None, Op.add), + pt.TealOp(None, pt.Op.int, "TMPL_INT_1"), + pt.TealOp(None, pt.Op.int, "TMPL_INT_1"), + pt.TealOp(None, pt.Op.eq), + pt.TealOp(None, pt.Op.int, "TMPL_INT_2"), + pt.TealOp(None, pt.Op.add), ] expected = [ - TealOp(None, Op.intcblock, "TMPL_INT_1"), - TealOp(None, Op.intc_0, "//", "TMPL_INT_1"), - TealOp(None, Op.intc_0, "//", "TMPL_INT_1"), - TealOp(None, Op.eq), - TealOp(None, Op.pushint, "TMPL_INT_2", "//", "TMPL_INT_2"), - TealOp(None, Op.add), + pt.TealOp(None, pt.Op.intcblock, "TMPL_INT_1"), + pt.TealOp(None, pt.Op.intc_0, "//", "TMPL_INT_1"), + pt.TealOp(None, pt.Op.intc_0, "//", "TMPL_INT_1"), + pt.TealOp(None, pt.Op.eq), + pt.TealOp(None, pt.Op.pushint, "TMPL_INT_2", "//", "TMPL_INT_2"), + pt.TealOp(None, pt.Op.add), ] actual = createConstantBlocks(ops) @@ -454,30 +469,30 @@ def test_createConstantBlocks_tmpl_int(): def test_createConstantBlocks_tmpl_int_mixed(): ops = [ - TealOp(None, Op.int, "TMPL_INT_1"), - TealOp(None, Op.int, "TMPL_INT_1"), - TealOp(None, Op.eq), - TealOp(None, Op.int, "TMPL_INT_2"), - TealOp(None, Op.add), - TealOp(None, Op.int, 0), - TealOp(None, Op.int, 0), - TealOp(None, Op.add), - TealOp(None, Op.int, 1), - TealOp(None, Op.add), + pt.TealOp(None, pt.Op.int, "TMPL_INT_1"), + pt.TealOp(None, pt.Op.int, "TMPL_INT_1"), + pt.TealOp(None, pt.Op.eq), + pt.TealOp(None, pt.Op.int, "TMPL_INT_2"), + pt.TealOp(None, pt.Op.add), + pt.TealOp(None, pt.Op.int, 0), + pt.TealOp(None, pt.Op.int, 0), + pt.TealOp(None, pt.Op.add), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.add), ] expected = [ - TealOp(None, Op.intcblock, "TMPL_INT_1", 0), - TealOp(None, Op.intc_0, "//", "TMPL_INT_1"), - TealOp(None, Op.intc_0, "//", "TMPL_INT_1"), - TealOp(None, Op.eq), - TealOp(None, Op.pushint, "TMPL_INT_2", "//", "TMPL_INT_2"), - TealOp(None, Op.add), - TealOp(None, Op.intc_1, "//", 0), - TealOp(None, Op.intc_1, "//", 0), - TealOp(None, Op.add), - TealOp(None, Op.pushint, 1, "//", 1), - TealOp(None, Op.add), + pt.TealOp(None, pt.Op.intcblock, "TMPL_INT_1", 0), + pt.TealOp(None, pt.Op.intc_0, "//", "TMPL_INT_1"), + pt.TealOp(None, pt.Op.intc_0, "//", "TMPL_INT_1"), + pt.TealOp(None, pt.Op.eq), + pt.TealOp(None, pt.Op.pushint, "TMPL_INT_2", "//", "TMPL_INT_2"), + pt.TealOp(None, pt.Op.add), + pt.TealOp(None, pt.Op.intc_1, "//", 0), + pt.TealOp(None, pt.Op.intc_1, "//", 0), + pt.TealOp(None, pt.Op.add), + pt.TealOp(None, pt.Op.pushint, 1, "//", 1), + pt.TealOp(None, pt.Op.add), ] actual = createConstantBlocks(ops) @@ -486,20 +501,20 @@ def test_createConstantBlocks_tmpl_int_mixed(): def test_createConstantBlocks_tmpl_bytes(): ops = [ - TealOp(None, Op.byte, "TMPL_BYTES_1"), - TealOp(None, Op.byte, "TMPL_BYTES_1"), - TealOp(None, Op.eq), - TealOp(None, Op.byte, "TMPL_BYTES_2"), - TealOp(None, Op.concat), + pt.TealOp(None, pt.Op.byte, "TMPL_BYTES_1"), + pt.TealOp(None, pt.Op.byte, "TMPL_BYTES_1"), + pt.TealOp(None, pt.Op.eq), + pt.TealOp(None, pt.Op.byte, "TMPL_BYTES_2"), + pt.TealOp(None, pt.Op.concat), ] expected = [ - TealOp(None, Op.bytecblock, "TMPL_BYTES_1"), - TealOp(None, Op.bytec_0, "//", "TMPL_BYTES_1"), - TealOp(None, Op.bytec_0, "//", "TMPL_BYTES_1"), - TealOp(None, Op.eq), - TealOp(None, Op.pushbytes, "TMPL_BYTES_2", "//", "TMPL_BYTES_2"), - TealOp(None, Op.concat), + pt.TealOp(None, pt.Op.bytecblock, "TMPL_BYTES_1"), + pt.TealOp(None, pt.Op.bytec_0, "//", "TMPL_BYTES_1"), + pt.TealOp(None, pt.Op.bytec_0, "//", "TMPL_BYTES_1"), + pt.TealOp(None, pt.Op.eq), + pt.TealOp(None, pt.Op.pushbytes, "TMPL_BYTES_2", "//", "TMPL_BYTES_2"), + pt.TealOp(None, pt.Op.concat), ] actual = createConstantBlocks(ops) @@ -508,30 +523,30 @@ def test_createConstantBlocks_tmpl_bytes(): def test_createConstantBlocks_tmpl_bytes_mixed(): ops = [ - TealOp(None, Op.byte, "TMPL_BYTES_1"), - TealOp(None, Op.byte, "TMPL_BYTES_1"), - TealOp(None, Op.eq), - TealOp(None, Op.byte, "TMPL_BYTES_2"), - TealOp(None, Op.concat), - TealOp(None, Op.byte, "0x00"), - TealOp(None, Op.byte, "0x00"), - TealOp(None, Op.concat), - TealOp(None, Op.byte, "0x01"), - TealOp(None, Op.concat), + pt.TealOp(None, pt.Op.byte, "TMPL_BYTES_1"), + pt.TealOp(None, pt.Op.byte, "TMPL_BYTES_1"), + pt.TealOp(None, pt.Op.eq), + pt.TealOp(None, pt.Op.byte, "TMPL_BYTES_2"), + pt.TealOp(None, pt.Op.concat), + pt.TealOp(None, pt.Op.byte, "0x00"), + pt.TealOp(None, pt.Op.byte, "0x00"), + pt.TealOp(None, pt.Op.concat), + pt.TealOp(None, pt.Op.byte, "0x01"), + pt.TealOp(None, pt.Op.concat), ] expected = [ - TealOp(None, Op.bytecblock, "TMPL_BYTES_1", "0x00"), - TealOp(None, Op.bytec_0, "//", "TMPL_BYTES_1"), - TealOp(None, Op.bytec_0, "//", "TMPL_BYTES_1"), - TealOp(None, Op.eq), - TealOp(None, Op.pushbytes, "TMPL_BYTES_2", "//", "TMPL_BYTES_2"), - TealOp(None, Op.concat), - TealOp(None, Op.bytec_1, "//", "0x00"), - TealOp(None, Op.bytec_1, "//", "0x00"), - TealOp(None, Op.concat), - TealOp(None, Op.pushbytes, "0x01", "//", "0x01"), - TealOp(None, Op.concat), + pt.TealOp(None, pt.Op.bytecblock, "TMPL_BYTES_1", "0x00"), + pt.TealOp(None, pt.Op.bytec_0, "//", "TMPL_BYTES_1"), + pt.TealOp(None, pt.Op.bytec_0, "//", "TMPL_BYTES_1"), + pt.TealOp(None, pt.Op.eq), + pt.TealOp(None, pt.Op.pushbytes, "TMPL_BYTES_2", "//", "TMPL_BYTES_2"), + pt.TealOp(None, pt.Op.concat), + pt.TealOp(None, pt.Op.bytec_1, "//", "0x00"), + pt.TealOp(None, pt.Op.bytec_1, "//", "0x00"), + pt.TealOp(None, pt.Op.concat), + pt.TealOp(None, pt.Op.pushbytes, "0x01", "//", "0x01"), + pt.TealOp(None, pt.Op.concat), ] actual = createConstantBlocks(ops) @@ -540,55 +555,55 @@ def test_createConstantBlocks_tmpl_bytes_mixed(): def test_createConstantBlocks_tmpl_all(): ops = [ - TealOp(None, Op.byte, "TMPL_BYTES_1"), - TealOp(None, Op.byte, "TMPL_BYTES_1"), - TealOp(None, Op.eq), - TealOp(None, Op.byte, "TMPL_BYTES_2"), - TealOp(None, Op.concat), - TealOp(None, Op.byte, "0x00"), - TealOp(None, Op.byte, "0x00"), - TealOp(None, Op.concat), - TealOp(None, Op.byte, "0x01"), - TealOp(None, Op.concat), - TealOp(None, Op.len), - TealOp(None, Op.int, "TMPL_INT_1"), - TealOp(None, Op.int, "TMPL_INT_1"), - TealOp(None, Op.eq), - TealOp(None, Op.int, "TMPL_INT_2"), - TealOp(None, Op.add), - TealOp(None, Op.int, 0), - TealOp(None, Op.int, 0), - TealOp(None, Op.add), - TealOp(None, Op.int, 1), - TealOp(None, Op.add), - TealOp(None, Op.eq), + pt.TealOp(None, pt.Op.byte, "TMPL_BYTES_1"), + pt.TealOp(None, pt.Op.byte, "TMPL_BYTES_1"), + pt.TealOp(None, pt.Op.eq), + pt.TealOp(None, pt.Op.byte, "TMPL_BYTES_2"), + pt.TealOp(None, pt.Op.concat), + pt.TealOp(None, pt.Op.byte, "0x00"), + pt.TealOp(None, pt.Op.byte, "0x00"), + pt.TealOp(None, pt.Op.concat), + pt.TealOp(None, pt.Op.byte, "0x01"), + pt.TealOp(None, pt.Op.concat), + pt.TealOp(None, pt.Op.len), + pt.TealOp(None, pt.Op.int, "TMPL_INT_1"), + pt.TealOp(None, pt.Op.int, "TMPL_INT_1"), + pt.TealOp(None, pt.Op.eq), + pt.TealOp(None, pt.Op.int, "TMPL_INT_2"), + pt.TealOp(None, pt.Op.add), + pt.TealOp(None, pt.Op.int, 0), + pt.TealOp(None, pt.Op.int, 0), + pt.TealOp(None, pt.Op.add), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.add), + pt.TealOp(None, pt.Op.eq), ] expected = [ - TealOp(None, Op.intcblock, "TMPL_INT_1", 0), - TealOp(None, Op.bytecblock, "TMPL_BYTES_1", "0x00"), - TealOp(None, Op.bytec_0, "//", "TMPL_BYTES_1"), - TealOp(None, Op.bytec_0, "//", "TMPL_BYTES_1"), - TealOp(None, Op.eq), - TealOp(None, Op.pushbytes, "TMPL_BYTES_2", "//", "TMPL_BYTES_2"), - TealOp(None, Op.concat), - TealOp(None, Op.bytec_1, "//", "0x00"), - TealOp(None, Op.bytec_1, "//", "0x00"), - TealOp(None, Op.concat), - TealOp(None, Op.pushbytes, "0x01", "//", "0x01"), - TealOp(None, Op.concat), - TealOp(None, Op.len), - TealOp(None, Op.intc_0, "//", "TMPL_INT_1"), - TealOp(None, Op.intc_0, "//", "TMPL_INT_1"), - TealOp(None, Op.eq), - TealOp(None, Op.pushint, "TMPL_INT_2", "//", "TMPL_INT_2"), - TealOp(None, Op.add), - TealOp(None, Op.intc_1, "//", 0), - TealOp(None, Op.intc_1, "//", 0), - TealOp(None, Op.add), - TealOp(None, Op.pushint, 1, "//", 1), - TealOp(None, Op.add), - TealOp(None, Op.eq), + pt.TealOp(None, pt.Op.intcblock, "TMPL_INT_1", 0), + pt.TealOp(None, pt.Op.bytecblock, "TMPL_BYTES_1", "0x00"), + pt.TealOp(None, pt.Op.bytec_0, "//", "TMPL_BYTES_1"), + pt.TealOp(None, pt.Op.bytec_0, "//", "TMPL_BYTES_1"), + pt.TealOp(None, pt.Op.eq), + pt.TealOp(None, pt.Op.pushbytes, "TMPL_BYTES_2", "//", "TMPL_BYTES_2"), + pt.TealOp(None, pt.Op.concat), + pt.TealOp(None, pt.Op.bytec_1, "//", "0x00"), + pt.TealOp(None, pt.Op.bytec_1, "//", "0x00"), + pt.TealOp(None, pt.Op.concat), + pt.TealOp(None, pt.Op.pushbytes, "0x01", "//", "0x01"), + pt.TealOp(None, pt.Op.concat), + pt.TealOp(None, pt.Op.len), + pt.TealOp(None, pt.Op.intc_0, "//", "TMPL_INT_1"), + pt.TealOp(None, pt.Op.intc_0, "//", "TMPL_INT_1"), + pt.TealOp(None, pt.Op.eq), + pt.TealOp(None, pt.Op.pushint, "TMPL_INT_2", "//", "TMPL_INT_2"), + pt.TealOp(None, pt.Op.add), + pt.TealOp(None, pt.Op.intc_1, "//", 0), + pt.TealOp(None, pt.Op.intc_1, "//", 0), + pt.TealOp(None, pt.Op.add), + pt.TealOp(None, pt.Op.pushint, 1, "//", 1), + pt.TealOp(None, pt.Op.add), + pt.TealOp(None, pt.Op.eq), ] actual = createConstantBlocks(ops) @@ -597,35 +612,35 @@ def test_createConstantBlocks_tmpl_all(): def test_createConstantBlocks_intc(): """Test scenario where there are more than 4 constants in the intcblock. - If the 4th constant can't fit in one varuint byte (more than 2**7) it - should be referenced with the Op.intc 4 command. + pt.If the 4th constant can't fit in one varuint byte (more than 2**7) it + should be referenced with the pt.Op.intc 4 command. """ ops = [ - TealOp(None, Op.int, 0), - TealOp(None, Op.int, 0), - TealOp(None, Op.int, 1), - TealOp(None, Op.int, 1), - TealOp(None, Op.int, 2), - TealOp(None, Op.int, 2), - TealOp(None, Op.int, 3), - TealOp(None, Op.int, 3), - TealOp(None, Op.int, 2**7), - TealOp(None, Op.int, 2**7), + pt.TealOp(None, pt.Op.int, 0), + pt.TealOp(None, pt.Op.int, 0), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.int, 2), + pt.TealOp(None, pt.Op.int, 2), + pt.TealOp(None, pt.Op.int, 3), + pt.TealOp(None, pt.Op.int, 3), + pt.TealOp(None, pt.Op.int, 2**7), + pt.TealOp(None, pt.Op.int, 2**7), ] expected = [ - TealOp(None, Op.intcblock, 0, 1, 2, 3, 2**7), - TealOp(None, Op.intc_0, "//", 0), - TealOp(None, Op.intc_0, "//", 0), - TealOp(None, Op.intc_1, "//", 1), - TealOp(None, Op.intc_1, "//", 1), - TealOp(None, Op.intc_2, "//", 2), - TealOp(None, Op.intc_2, "//", 2), - TealOp(None, Op.intc_3, "//", 3), - TealOp(None, Op.intc_3, "//", 3), - TealOp(None, Op.intc, 4, "//", 2**7), - TealOp(None, Op.intc, 4, "//", 2**7), + pt.TealOp(None, pt.Op.intcblock, 0, 1, 2, 3, 2**7), + pt.TealOp(None, pt.Op.intc_0, "//", 0), + pt.TealOp(None, pt.Op.intc_0, "//", 0), + pt.TealOp(None, pt.Op.intc_1, "//", 1), + pt.TealOp(None, pt.Op.intc_1, "//", 1), + pt.TealOp(None, pt.Op.intc_2, "//", 2), + pt.TealOp(None, pt.Op.intc_2, "//", 2), + pt.TealOp(None, pt.Op.intc_3, "//", 3), + pt.TealOp(None, pt.Op.intc_3, "//", 3), + pt.TealOp(None, pt.Op.intc, 4, "//", 2**7), + pt.TealOp(None, pt.Op.intc, 4, "//", 2**7), ] actual = createConstantBlocks(ops) @@ -633,36 +648,36 @@ def test_createConstantBlocks_intc(): def test_createConstantBlocks_small_constant(): - """If a constant cannot be referenced using the intc_[0..3] commands - and it can be stored in one varuint it byte then Op.pushint is used. + """pt.If a constant cannot be referenced using the intc_[0..3] commands + and it can be stored in one varuint it byte then pt.Op.pushint is used. """ for cur in range(4, 2**7): ops = [ - TealOp(None, Op.int, 0), - TealOp(None, Op.int, 0), - TealOp(None, Op.int, 1), - TealOp(None, Op.int, 1), - TealOp(None, Op.int, 2), - TealOp(None, Op.int, 2), - TealOp(None, Op.int, 3), - TealOp(None, Op.int, 3), - TealOp(None, Op.int, cur), - TealOp(None, Op.int, cur), + pt.TealOp(None, pt.Op.int, 0), + pt.TealOp(None, pt.Op.int, 0), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.int, 2), + pt.TealOp(None, pt.Op.int, 2), + pt.TealOp(None, pt.Op.int, 3), + pt.TealOp(None, pt.Op.int, 3), + pt.TealOp(None, pt.Op.int, cur), + pt.TealOp(None, pt.Op.int, cur), ] expected = [ - TealOp(None, Op.intcblock, 0, 1, 2, 3), - TealOp(None, Op.intc_0, "//", 0), - TealOp(None, Op.intc_0, "//", 0), - TealOp(None, Op.intc_1, "//", 1), - TealOp(None, Op.intc_1, "//", 1), - TealOp(None, Op.intc_2, "//", 2), - TealOp(None, Op.intc_2, "//", 2), - TealOp(None, Op.intc_3, "//", 3), - TealOp(None, Op.intc_3, "//", 3), - TealOp(None, Op.pushint, cur, "//", cur), - TealOp(None, Op.pushint, cur, "//", cur), + pt.TealOp(None, pt.Op.intcblock, 0, 1, 2, 3), + pt.TealOp(None, pt.Op.intc_0, "//", 0), + pt.TealOp(None, pt.Op.intc_0, "//", 0), + pt.TealOp(None, pt.Op.intc_1, "//", 1), + pt.TealOp(None, pt.Op.intc_1, "//", 1), + pt.TealOp(None, pt.Op.intc_2, "//", 2), + pt.TealOp(None, pt.Op.intc_2, "//", 2), + pt.TealOp(None, pt.Op.intc_3, "//", 3), + pt.TealOp(None, pt.Op.intc_3, "//", 3), + pt.TealOp(None, pt.Op.pushint, cur, "//", cur), + pt.TealOp(None, pt.Op.pushint, cur, "//", cur), ] actual = createConstantBlocks(ops) diff --git a/pyteal/compiler/flatten.py b/pyteal/compiler/flatten.py index 95454f1b2..99674836c 100644 --- a/pyteal/compiler/flatten.py +++ b/pyteal/compiler/flatten.py @@ -1,8 +1,8 @@ -from typing import List, Dict, DefaultDict, Optional, cast +from typing import List, Dict, DefaultDict, Optional from collections import defaultdict -from ..ast import SubroutineDefinition -from ..ir import ( +from pyteal.ast import SubroutineDefinition +from pyteal.ir import ( Op, TealOp, TealLabel, @@ -12,7 +12,7 @@ TealConditionalBlock, LabelReference, ) -from ..errors import TealInternalError +from pyteal.errors import TealInternalError def flattenBlocks(blocks: List[TealBlock]) -> List[TealComponent]: diff --git a/pyteal/compiler/flatten_test.py b/pyteal/compiler/flatten_test.py index 792371cb8..a01c45d64 100644 --- a/pyteal/compiler/flatten_test.py +++ b/pyteal/compiler/flatten_test.py @@ -1,11 +1,8 @@ from collections import OrderedDict -from .. import * +import pyteal as pt -# this is not necessary but mypy complains if it's not included -from ..ast import * - -from .flatten import flattenBlocks, flattenSubroutines +from pyteal.compiler.flatten import flattenBlocks, flattenSubroutines def test_flattenBlocks_none(): @@ -18,7 +15,7 @@ def test_flattenBlocks_none(): def test_flattenBlocks_single_empty(): - blocks = [TealSimpleBlock([])] + blocks = [pt.TealSimpleBlock([])] expected = [] actual = flattenBlocks(blocks) @@ -27,9 +24,9 @@ def test_flattenBlocks_single_empty(): def test_flattenBlocks_single_one(): - blocks = [TealSimpleBlock([TealOp(None, Op.int, 1)])] + blocks = [pt.TealSimpleBlock([pt.TealOp(None, pt.Op.int, 1)])] - expected = [TealOp(None, Op.int, 1)] + expected = [pt.TealOp(None, pt.Op.int, 1)] actual = flattenBlocks(blocks) assert actual == expected @@ -37,23 +34,23 @@ def test_flattenBlocks_single_one(): def test_flattenBlocks_single_many(): blocks = [ - TealSimpleBlock( + pt.TealSimpleBlock( [ - TealOp(None, Op.int, 1), - TealOp(None, Op.int, 2), - TealOp(None, Op.int, 3), - TealOp(None, Op.add), - TealOp(None, Op.add), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.int, 2), + pt.TealOp(None, pt.Op.int, 3), + pt.TealOp(None, pt.Op.add), + pt.TealOp(None, pt.Op.add), ] ) ] expected = [ - TealOp(None, Op.int, 1), - TealOp(None, Op.int, 2), - TealOp(None, Op.int, 3), - TealOp(None, Op.add), - TealOp(None, Op.add), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.int, 2), + pt.TealOp(None, pt.Op.int, 3), + pt.TealOp(None, pt.Op.add), + pt.TealOp(None, pt.Op.add), ] actual = flattenBlocks(blocks) @@ -61,25 +58,25 @@ def test_flattenBlocks_single_many(): def test_flattenBlocks_sequence(): - block5 = TealSimpleBlock([TealOp(None, Op.int, 5)]) - block4 = TealSimpleBlock([TealOp(None, Op.int, 4)]) + block5 = pt.TealSimpleBlock([pt.TealOp(None, pt.Op.int, 5)]) + block4 = pt.TealSimpleBlock([pt.TealOp(None, pt.Op.int, 4)]) block4.setNextBlock(block5) - block3 = TealSimpleBlock([TealOp(None, Op.int, 3)]) + block3 = pt.TealSimpleBlock([pt.TealOp(None, pt.Op.int, 3)]) block3.setNextBlock(block4) - block2 = TealSimpleBlock([TealOp(None, Op.int, 2)]) + block2 = pt.TealSimpleBlock([pt.TealOp(None, pt.Op.int, 2)]) block2.setNextBlock(block3) - block1 = TealSimpleBlock([TealOp(None, Op.int, 1)]) + block1 = pt.TealSimpleBlock([pt.TealOp(None, pt.Op.int, 1)]) block1.setNextBlock(block2) block1.addIncoming() block1.validateTree() blocks = [block1, block2, block3, block4, block5] expected = [ - TealOp(None, Op.int, 1), - TealOp(None, Op.int, 2), - TealOp(None, Op.int, 3), - TealOp(None, Op.int, 4), - TealOp(None, Op.int, 5), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.int, 2), + pt.TealOp(None, pt.Op.int, 3), + pt.TealOp(None, pt.Op.int, 4), + pt.TealOp(None, pt.Op.int, 5), ] actual = flattenBlocks(blocks) @@ -87,13 +84,13 @@ def test_flattenBlocks_sequence(): def test_flattenBlocks_branch(): - blockTrue = TealSimpleBlock( - [TealOp(None, Op.byte, '"true"'), TealOp(None, Op.return_)] + blockTrue = pt.TealSimpleBlock( + [pt.TealOp(None, pt.Op.byte, '"true"'), pt.TealOp(None, pt.Op.return_)] ) - blockFalse = TealSimpleBlock( - [TealOp(None, Op.byte, '"false"'), TealOp(None, Op.return_)] + blockFalse = pt.TealSimpleBlock( + [pt.TealOp(None, pt.Op.byte, '"false"'), pt.TealOp(None, pt.Op.return_)] ) - block = TealConditionalBlock([TealOp(None, Op.int, 1)]) + block = pt.TealConditionalBlock([pt.TealOp(None, pt.Op.int, 1)]) block.setTrueBlock(blockTrue) block.setFalseBlock(blockFalse) block.addIncoming() @@ -101,13 +98,13 @@ def test_flattenBlocks_branch(): blocks = [block, blockFalse, blockTrue] expected = [ - TealOp(None, Op.int, 1), - TealOp(None, Op.bnz, LabelReference("l2")), - TealOp(None, Op.byte, '"false"'), - TealOp(None, Op.return_), - TealLabel(None, LabelReference("l2")), - TealOp(None, Op.byte, '"true"'), - TealOp(None, Op.return_), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.bnz, pt.LabelReference("l2")), + pt.TealOp(None, pt.Op.byte, '"false"'), + pt.TealOp(None, pt.Op.return_), + pt.TealLabel(None, pt.LabelReference("l2")), + pt.TealOp(None, pt.Op.byte, '"true"'), + pt.TealOp(None, pt.Op.return_), ] actual = flattenBlocks(blocks) @@ -115,13 +112,13 @@ def test_flattenBlocks_branch(): def test_flattenBlocks_branch_equal_end_nodes(): - blockTrueEnd = TealSimpleBlock([TealOp(None, Op.return_)]) - blockTrue = TealSimpleBlock([TealOp(None, Op.byte, '"true"')]) + blockTrueEnd = pt.TealSimpleBlock([pt.TealOp(None, pt.Op.return_)]) + blockTrue = pt.TealSimpleBlock([pt.TealOp(None, pt.Op.byte, '"true"')]) blockTrue.setNextBlock(blockTrueEnd) - blockFalseEnd = TealSimpleBlock([TealOp(None, Op.return_)]) - blockFalse = TealSimpleBlock([TealOp(None, Op.byte, '"false"')]) + blockFalseEnd = pt.TealSimpleBlock([pt.TealOp(None, pt.Op.return_)]) + blockFalse = pt.TealSimpleBlock([pt.TealOp(None, pt.Op.byte, '"false"')]) blockFalse.setNextBlock(blockFalseEnd) - block = TealConditionalBlock([TealOp(None, Op.int, 1)]) + block = pt.TealConditionalBlock([pt.TealOp(None, pt.Op.int, 1)]) block.setTrueBlock(blockTrue) block.setFalseBlock(blockFalse) block.addIncoming() @@ -129,13 +126,13 @@ def test_flattenBlocks_branch_equal_end_nodes(): blocks = [block, blockFalse, blockFalseEnd, blockTrue, blockTrueEnd] expected = [ - TealOp(None, Op.int, 1), - TealOp(None, Op.bnz, LabelReference("l3")), - TealOp(None, Op.byte, '"false"'), - TealOp(None, Op.return_), - TealLabel(None, LabelReference("l3")), - TealOp(None, Op.byte, '"true"'), - TealOp(None, Op.return_), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.bnz, pt.LabelReference("l3")), + pt.TealOp(None, pt.Op.byte, '"false"'), + pt.TealOp(None, pt.Op.return_), + pt.TealLabel(None, pt.LabelReference("l3")), + pt.TealOp(None, pt.Op.byte, '"true"'), + pt.TealOp(None, pt.Op.return_), ] actual = flattenBlocks(blocks) @@ -143,12 +140,12 @@ def test_flattenBlocks_branch_equal_end_nodes(): def test_flattenBlocks_branch_converge(): - blockEnd = TealSimpleBlock([TealOp(None, Op.return_)]) - blockTrue = TealSimpleBlock([TealOp(None, Op.byte, '"true"')]) + blockEnd = pt.TealSimpleBlock([pt.TealOp(None, pt.Op.return_)]) + blockTrue = pt.TealSimpleBlock([pt.TealOp(None, pt.Op.byte, '"true"')]) blockTrue.setNextBlock(blockEnd) - blockFalse = TealSimpleBlock([TealOp(None, Op.byte, '"false"')]) + blockFalse = pt.TealSimpleBlock([pt.TealOp(None, pt.Op.byte, '"false"')]) blockFalse.setNextBlock(blockEnd) - block = TealConditionalBlock([TealOp(None, Op.int, 1)]) + block = pt.TealConditionalBlock([pt.TealOp(None, pt.Op.int, 1)]) block.setTrueBlock(blockTrue) block.setFalseBlock(blockFalse) block.addIncoming() @@ -156,14 +153,14 @@ def test_flattenBlocks_branch_converge(): blocks = [block, blockFalse, blockTrue, blockEnd] expected = [ - TealOp(None, Op.int, 1), - TealOp(None, Op.bnz, LabelReference("l2")), - TealOp(None, Op.byte, '"false"'), - TealOp(None, Op.b, LabelReference("l3")), - TealLabel(None, LabelReference("l2")), - TealOp(None, Op.byte, '"true"'), - TealLabel(None, LabelReference("l3")), - TealOp(None, Op.return_), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.bnz, pt.LabelReference("l2")), + pt.TealOp(None, pt.Op.byte, '"false"'), + pt.TealOp(None, pt.Op.b, pt.LabelReference("l3")), + pt.TealLabel(None, pt.LabelReference("l2")), + pt.TealOp(None, pt.Op.byte, '"true"'), + pt.TealLabel(None, pt.LabelReference("l3")), + pt.TealOp(None, pt.Op.return_), ] actual = flattenBlocks(blocks) @@ -171,21 +168,21 @@ def test_flattenBlocks_branch_converge(): def test_flattenBlocks_multiple_branch(): - blockTrueTrue = TealSimpleBlock( - [TealOp(None, Op.byte, '"true true"'), TealOp(None, Op.return_)] + blockTrueTrue = pt.TealSimpleBlock( + [pt.TealOp(None, pt.Op.byte, '"true true"'), pt.TealOp(None, pt.Op.return_)] ) - blockTrueFalse = TealSimpleBlock( - [TealOp(None, Op.byte, '"true false"'), TealOp(None, Op.err)] + blockTrueFalse = pt.TealSimpleBlock( + [pt.TealOp(None, pt.Op.byte, '"true false"'), pt.TealOp(None, pt.Op.err)] ) - blockTrueBranch = TealConditionalBlock([]) + blockTrueBranch = pt.TealConditionalBlock([]) blockTrueBranch.setTrueBlock(blockTrueTrue) blockTrueBranch.setFalseBlock(blockTrueFalse) - blockTrue = TealSimpleBlock([TealOp(None, Op.byte, '"true"')]) + blockTrue = pt.TealSimpleBlock([pt.TealOp(None, pt.Op.byte, '"true"')]) blockTrue.setNextBlock(blockTrueBranch) - blockFalse = TealSimpleBlock( - [TealOp(None, Op.byte, '"false"'), TealOp(None, Op.return_)] + blockFalse = pt.TealSimpleBlock( + [pt.TealOp(None, pt.Op.byte, '"false"'), pt.TealOp(None, pt.Op.return_)] ) - block = TealConditionalBlock([TealOp(None, Op.int, 1)]) + block = pt.TealConditionalBlock([pt.TealOp(None, pt.Op.int, 1)]) block.setTrueBlock(blockTrue) block.setFalseBlock(blockFalse) block.addIncoming() @@ -200,18 +197,18 @@ def test_flattenBlocks_multiple_branch(): ] expected = [ - TealOp(None, Op.int, 1), - TealOp(None, Op.bnz, LabelReference("l2")), - TealOp(None, Op.byte, '"false"'), - TealOp(None, Op.return_), - TealLabel(None, LabelReference("l2")), - TealOp(None, Op.byte, '"true"'), - TealOp(None, Op.bnz, LabelReference("l5")), - TealOp(None, Op.byte, '"true false"'), - TealOp(None, Op.err), - TealLabel(None, LabelReference("l5")), - TealOp(None, Op.byte, '"true true"'), - TealOp(None, Op.return_), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.bnz, pt.LabelReference("l2")), + pt.TealOp(None, pt.Op.byte, '"false"'), + pt.TealOp(None, pt.Op.return_), + pt.TealLabel(None, pt.LabelReference("l2")), + pt.TealOp(None, pt.Op.byte, '"true"'), + pt.TealOp(None, pt.Op.bnz, pt.LabelReference("l5")), + pt.TealOp(None, pt.Op.byte, '"true false"'), + pt.TealOp(None, pt.Op.err), + pt.TealLabel(None, pt.LabelReference("l5")), + pt.TealOp(None, pt.Op.byte, '"true true"'), + pt.TealOp(None, pt.Op.return_), ] actual = flattenBlocks(blocks) @@ -219,20 +216,20 @@ def test_flattenBlocks_multiple_branch(): def test_flattenBlocks_multiple_branch_converge(): - blockEnd = TealSimpleBlock([TealOp(None, Op.return_)]) - blockTrueTrue = TealSimpleBlock([TealOp(None, Op.byte, '"true true"')]) + blockEnd = pt.TealSimpleBlock([pt.TealOp(None, pt.Op.return_)]) + blockTrueTrue = pt.TealSimpleBlock([pt.TealOp(None, pt.Op.byte, '"true true"')]) blockTrueTrue.setNextBlock(blockEnd) - blockTrueFalse = TealSimpleBlock( - [TealOp(None, Op.byte, '"true false"'), TealOp(None, Op.err)] + blockTrueFalse = pt.TealSimpleBlock( + [pt.TealOp(None, pt.Op.byte, '"true false"'), pt.TealOp(None, pt.Op.err)] ) - blockTrueBranch = TealConditionalBlock([]) + blockTrueBranch = pt.TealConditionalBlock([]) blockTrueBranch.setTrueBlock(blockTrueTrue) blockTrueBranch.setFalseBlock(blockTrueFalse) - blockTrue = TealSimpleBlock([TealOp(None, Op.byte, '"true"')]) + blockTrue = pt.TealSimpleBlock([pt.TealOp(None, pt.Op.byte, '"true"')]) blockTrue.setNextBlock(blockTrueBranch) - blockFalse = TealSimpleBlock([TealOp(None, Op.byte, '"false"')]) + blockFalse = pt.TealSimpleBlock([pt.TealOp(None, pt.Op.byte, '"false"')]) blockFalse.setNextBlock(blockEnd) - block = TealConditionalBlock([TealOp(None, Op.int, 1)]) + block = pt.TealConditionalBlock([pt.TealOp(None, pt.Op.int, 1)]) block.setTrueBlock(blockTrue) block.setFalseBlock(blockFalse) block.addIncoming() @@ -248,19 +245,19 @@ def test_flattenBlocks_multiple_branch_converge(): ] expected = [ - TealOp(None, Op.int, 1), - TealOp(None, Op.bnz, LabelReference("l2")), - TealOp(None, Op.byte, '"false"'), - TealOp(None, Op.b, LabelReference("l6")), - TealLabel(None, LabelReference("l2")), - TealOp(None, Op.byte, '"true"'), - TealOp(None, Op.bnz, LabelReference("l5")), - TealOp(None, Op.byte, '"true false"'), - TealOp(None, Op.err), - TealLabel(None, LabelReference("l5")), - TealOp(None, Op.byte, '"true true"'), - TealLabel(None, LabelReference("l6")), - TealOp(None, Op.return_), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.bnz, pt.LabelReference("l2")), + pt.TealOp(None, pt.Op.byte, '"false"'), + pt.TealOp(None, pt.Op.b, pt.LabelReference("l6")), + pt.TealLabel(None, pt.LabelReference("l2")), + pt.TealOp(None, pt.Op.byte, '"true"'), + pt.TealOp(None, pt.Op.bnz, pt.LabelReference("l5")), + pt.TealOp(None, pt.Op.byte, '"true false"'), + pt.TealOp(None, pt.Op.err), + pt.TealLabel(None, pt.LabelReference("l5")), + pt.TealOp(None, pt.Op.byte, '"true true"'), + pt.TealLabel(None, pt.LabelReference("l6")), + pt.TealOp(None, pt.Op.return_), ] actual = flattenBlocks(blocks) @@ -270,32 +267,32 @@ def test_flattenBlocks_multiple_branch_converge(): def test_flattenSubroutines_no_subroutines(): subroutineToLabel = OrderedDict() - l1Label = LabelReference("l1") + l1Label = pt.LabelReference("l1") mainOps = [ - TealOp(None, Op.txn, "Fee"), - TealOp(None, Op.int, 0), - TealOp(None, Op.eq), - TealOp(None, Op.bz, l1Label), - TealOp(None, Op.int, 1), - TealOp(None, Op.return_), - TealLabel(None, l1Label), - TealOp(None, Op.int, 0), - TealOp(None, Op.return_), + pt.TealOp(None, pt.Op.txn, "Fee"), + pt.TealOp(None, pt.Op.int, 0), + pt.TealOp(None, pt.Op.eq), + pt.TealOp(None, pt.Op.bz, l1Label), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.return_), + pt.TealLabel(None, l1Label), + pt.TealOp(None, pt.Op.int, 0), + pt.TealOp(None, pt.Op.return_), ] subroutineMapping = {None: mainOps} - expectedL1Label = LabelReference("main_l1") + expectedL1Label = pt.LabelReference("main_l1") expected = [ - TealOp(None, Op.txn, "Fee"), - TealOp(None, Op.int, 0), - TealOp(None, Op.eq), - TealOp(None, Op.bz, expectedL1Label), - TealOp(None, Op.int, 1), - TealOp(None, Op.return_), - TealLabel(None, expectedL1Label), - TealOp(None, Op.int, 0), - TealOp(None, Op.return_), + pt.TealOp(None, pt.Op.txn, "Fee"), + pt.TealOp(None, pt.Op.int, 0), + pt.TealOp(None, pt.Op.eq), + pt.TealOp(None, pt.Op.bz, expectedL1Label), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.return_), + pt.TealLabel(None, expectedL1Label), + pt.TealOp(None, pt.Op.int, 0), + pt.TealOp(None, pt.Op.return_), ] actual = flattenSubroutines(subroutineMapping, subroutineToLabel) @@ -304,55 +301,57 @@ def test_flattenSubroutines_no_subroutines(): def test_flattenSubroutines_1_subroutine(): - subroutine = SubroutineDefinition(lambda: Int(1) + Int(2) + Int(3), TealType.uint64) + subroutine = pt.SubroutineDefinition( + lambda: pt.Int(1) + pt.Int(2) + pt.Int(3), pt.TealType.uint64 + ) subroutineToLabel = OrderedDict() subroutineToLabel[subroutine] = "sub0" - subroutineLabel = LabelReference(subroutineToLabel[subroutine]) + subroutineLabel = pt.LabelReference(subroutineToLabel[subroutine]) subroutineOps = [ - TealOp(None, Op.int, 1), - TealOp(None, Op.int, 2), - TealOp(None, Op.int, 3), - TealOp(None, Op.add), - TealOp(None, Op.add), - TealOp(None, Op.retsub), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.int, 2), + pt.TealOp(None, pt.Op.int, 3), + pt.TealOp(None, pt.Op.add), + pt.TealOp(None, pt.Op.add), + pt.TealOp(None, pt.Op.retsub), ] - l1Label = LabelReference("l1") + l1Label = pt.LabelReference("l1") mainOps = [ - TealOp(None, Op.txn, "Fee"), - TealOp(None, Op.int, 0), - TealOp(None, Op.eq), - TealOp(None, Op.bz, l1Label), - TealOp(None, Op.callsub, subroutineLabel), - TealOp(None, Op.return_), - TealLabel(None, l1Label), - TealOp(None, Op.int, 0), - TealOp(None, Op.return_), + pt.TealOp(None, pt.Op.txn, "Fee"), + pt.TealOp(None, pt.Op.int, 0), + pt.TealOp(None, pt.Op.eq), + pt.TealOp(None, pt.Op.bz, l1Label), + pt.TealOp(None, pt.Op.callsub, subroutineLabel), + pt.TealOp(None, pt.Op.return_), + pt.TealLabel(None, l1Label), + pt.TealOp(None, pt.Op.int, 0), + pt.TealOp(None, pt.Op.return_), ] subroutineMapping = {None: mainOps, subroutine: subroutineOps} - expectedL1Label = LabelReference("main_l1") - expectedSubroutineLabel = LabelReference("sub0") + expectedL1Label = pt.LabelReference("main_l1") + expectedSubroutineLabel = pt.LabelReference("sub0") expected = [ - TealOp(None, Op.txn, "Fee"), - TealOp(None, Op.int, 0), - TealOp(None, Op.eq), - TealOp(None, Op.bz, expectedL1Label), - TealOp(None, Op.callsub, expectedSubroutineLabel), - TealOp(None, Op.return_), - TealLabel(None, expectedL1Label), - TealOp(None, Op.int, 0), - TealOp(None, Op.return_), - TealLabel(None, expectedSubroutineLabel, ""), - TealOp(None, Op.int, 1), - TealOp(None, Op.int, 2), - TealOp(None, Op.int, 3), - TealOp(None, Op.add), - TealOp(None, Op.add), - TealOp(None, Op.retsub), + pt.TealOp(None, pt.Op.txn, "Fee"), + pt.TealOp(None, pt.Op.int, 0), + pt.TealOp(None, pt.Op.eq), + pt.TealOp(None, pt.Op.bz, expectedL1Label), + pt.TealOp(None, pt.Op.callsub, expectedSubroutineLabel), + pt.TealOp(None, pt.Op.return_), + pt.TealLabel(None, expectedL1Label), + pt.TealOp(None, pt.Op.int, 0), + pt.TealOp(None, pt.Op.return_), + pt.TealLabel(None, expectedSubroutineLabel, ""), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.int, 2), + pt.TealOp(None, pt.Op.int, 3), + pt.TealOp(None, pt.Op.add), + pt.TealOp(None, pt.Op.add), + pt.TealOp(None, pt.Op.retsub), ] actual = flattenSubroutines(subroutineMapping, subroutineToLabel) @@ -370,93 +369,93 @@ def sub2Impl(a1): def sub3Impl(a1, a2, a3): return None - subroutine1 = SubroutineDefinition(sub1Impl, TealType.uint64) - subroutine2 = SubroutineDefinition(sub2Impl, TealType.bytes) - subroutine3 = SubroutineDefinition(sub3Impl, TealType.none) + subroutine1 = pt.SubroutineDefinition(sub1Impl, pt.TealType.uint64) + subroutine2 = pt.SubroutineDefinition(sub2Impl, pt.TealType.bytes) + subroutine3 = pt.SubroutineDefinition(sub3Impl, pt.TealType.none) subroutineToLabel = OrderedDict() subroutineToLabel[subroutine1] = "sub0" subroutineToLabel[subroutine2] = "sub1" subroutineToLabel[subroutine3] = "sub2" - subroutine1Label = LabelReference(subroutineToLabel[subroutine1]) + subroutine1Label = pt.LabelReference(subroutineToLabel[subroutine1]) subroutine1Ops = [ - TealOp(None, Op.int, 1), - TealOp(None, Op.int, 2), - TealOp(None, Op.int, 3), - TealOp(None, Op.add), - TealOp(None, Op.add), - TealOp(None, Op.retsub), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.int, 2), + pt.TealOp(None, pt.Op.int, 3), + pt.TealOp(None, pt.Op.add), + pt.TealOp(None, pt.Op.add), + pt.TealOp(None, pt.Op.retsub), ] - subroutine2Label = LabelReference(subroutineToLabel[subroutine2]) - subroutine2L1Label = LabelReference("l1") - subroutine2L2Label = LabelReference("l2") - subroutine2L3Label = LabelReference("l3") - subroutine2L4Label = LabelReference("l4") + subroutine2Label = pt.LabelReference(subroutineToLabel[subroutine2]) + subroutine2L1Label = pt.LabelReference("l1") + subroutine2L2Label = pt.LabelReference("l2") + subroutine2L3Label = pt.LabelReference("l3") + subroutine2L4Label = pt.LabelReference("l4") subroutine2Ops = [ - TealOp(None, Op.dup), - TealOp(None, Op.int, 1), - TealOp(None, Op.eq), - TealOp(None, Op.bnz, subroutine2L1Label), - TealOp(None, Op.dup), - TealOp(None, Op.int, 2), - TealOp(None, Op.eq), - TealOp(None, Op.bnz, subroutine2L2Label), - TealOp(None, Op.dup), - TealOp(None, Op.int, 3), - TealOp(None, Op.eq), - TealOp(None, Op.bnz, subroutine2L3Label), - TealOp(None, Op.dup), - TealOp(None, Op.int, 4), - TealOp(None, Op.eq), - TealOp(None, Op.bnz, subroutine2L4Label), - TealOp(None, Op.err), - TealLabel(None, subroutine2L1Label), - TealOp(None, Op.pop), - TealOp(None, Op.byte, '"1"'), - TealOp(None, Op.retsub), - TealLabel(None, subroutine2L2Label), - TealOp(None, Op.pop), - TealOp(None, Op.byte, '"2"'), - TealOp(None, Op.retsub), - TealLabel(None, subroutine2L3Label), - TealOp(None, Op.pop), - TealOp(None, Op.byte, '"3"'), - TealOp(None, Op.retsub), - TealLabel(None, subroutine2L4Label), - TealOp(None, Op.pop), - TealOp(None, Op.byte, '"4"'), - TealOp(None, Op.retsub), + pt.TealOp(None, pt.Op.dup), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.eq), + pt.TealOp(None, pt.Op.bnz, subroutine2L1Label), + pt.TealOp(None, pt.Op.dup), + pt.TealOp(None, pt.Op.int, 2), + pt.TealOp(None, pt.Op.eq), + pt.TealOp(None, pt.Op.bnz, subroutine2L2Label), + pt.TealOp(None, pt.Op.dup), + pt.TealOp(None, pt.Op.int, 3), + pt.TealOp(None, pt.Op.eq), + pt.TealOp(None, pt.Op.bnz, subroutine2L3Label), + pt.TealOp(None, pt.Op.dup), + pt.TealOp(None, pt.Op.int, 4), + pt.TealOp(None, pt.Op.eq), + pt.TealOp(None, pt.Op.bnz, subroutine2L4Label), + pt.TealOp(None, pt.Op.err), + pt.TealLabel(None, subroutine2L1Label), + pt.TealOp(None, pt.Op.pop), + pt.TealOp(None, pt.Op.byte, '"1"'), + pt.TealOp(None, pt.Op.retsub), + pt.TealLabel(None, subroutine2L2Label), + pt.TealOp(None, pt.Op.pop), + pt.TealOp(None, pt.Op.byte, '"2"'), + pt.TealOp(None, pt.Op.retsub), + pt.TealLabel(None, subroutine2L3Label), + pt.TealOp(None, pt.Op.pop), + pt.TealOp(None, pt.Op.byte, '"3"'), + pt.TealOp(None, pt.Op.retsub), + pt.TealLabel(None, subroutine2L4Label), + pt.TealOp(None, pt.Op.pop), + pt.TealOp(None, pt.Op.byte, '"4"'), + pt.TealOp(None, pt.Op.retsub), ] - subroutine3Label = LabelReference(subroutineToLabel[subroutine3]) - subroutine3L1Label = LabelReference("l1") + subroutine3Label = pt.LabelReference(subroutineToLabel[subroutine3]) + subroutine3L1Label = pt.LabelReference("l1") subroutine3Ops = [ - TealLabel(None, subroutine3L1Label), - TealOp(None, Op.app_local_put), - TealOp(None, Op.retsub), - TealOp(None, Op.b, subroutine3L1Label), + pt.TealLabel(None, subroutine3L1Label), + pt.TealOp(None, pt.Op.app_local_put), + pt.TealOp(None, pt.Op.retsub), + pt.TealOp(None, pt.Op.b, subroutine3L1Label), ] - l1Label = LabelReference("l1") + l1Label = pt.LabelReference("l1") mainOps = [ - TealOp(None, Op.byte, '"account"'), - TealOp(None, Op.byte, '"key"'), - TealOp(None, Op.byte, '"value"'), - TealOp(None, Op.callsub, subroutine3Label), - TealOp(None, Op.txn, "Fee"), - TealOp(None, Op.int, 0), - TealOp(None, Op.eq), - TealOp(None, Op.bz, l1Label), - TealOp(None, Op.int, 3), - TealOp(None, Op.callsub, subroutine2Label), - TealOp(None, Op.pop), - TealOp(None, Op.callsub, subroutine1Label), - TealOp(None, Op.return_), - TealLabel(None, l1Label), - TealOp(None, Op.int, 0), - TealOp(None, Op.return_), + pt.TealOp(None, pt.Op.byte, '"account"'), + pt.TealOp(None, pt.Op.byte, '"key"'), + pt.TealOp(None, pt.Op.byte, '"value"'), + pt.TealOp(None, pt.Op.callsub, subroutine3Label), + pt.TealOp(None, pt.Op.txn, "Fee"), + pt.TealOp(None, pt.Op.int, 0), + pt.TealOp(None, pt.Op.eq), + pt.TealOp(None, pt.Op.bz, l1Label), + pt.TealOp(None, pt.Op.int, 3), + pt.TealOp(None, pt.Op.callsub, subroutine2Label), + pt.TealOp(None, pt.Op.pop), + pt.TealOp(None, pt.Op.callsub, subroutine1Label), + pt.TealOp(None, pt.Op.return_), + pt.TealLabel(None, l1Label), + pt.TealOp(None, pt.Op.int, 0), + pt.TealOp(None, pt.Op.return_), ] subroutineMapping = { @@ -466,78 +465,78 @@ def sub3Impl(a1, a2, a3): subroutine3: subroutine3Ops, } - expectedL1Label = LabelReference("main_l1") - expectedSubroutine1Label = LabelReference("sub0") - expectedSubroutine2Label = LabelReference("sub1") - expectedSubroutine2L1Label = LabelReference("sub1_l1") - expectedSubroutine2L2Label = LabelReference("sub1_l2") - expectedSubroutine2L3Label = LabelReference("sub1_l3") - expectedSubroutine2L4Label = LabelReference("sub1_l4") - expectedSubroutine3Label = LabelReference("sub2") - expectedSubroutine3L1Label = LabelReference("sub2_l1") + expectedL1Label = pt.LabelReference("main_l1") + expectedSubroutine1Label = pt.LabelReference("sub0") + expectedSubroutine2Label = pt.LabelReference("sub1") + expectedSubroutine2L1Label = pt.LabelReference("sub1_l1") + expectedSubroutine2L2Label = pt.LabelReference("sub1_l2") + expectedSubroutine2L3Label = pt.LabelReference("sub1_l3") + expectedSubroutine2L4Label = pt.LabelReference("sub1_l4") + expectedSubroutine3Label = pt.LabelReference("sub2") + expectedSubroutine3L1Label = pt.LabelReference("sub2_l1") expected = [ - TealOp(None, Op.byte, '"account"'), - TealOp(None, Op.byte, '"key"'), - TealOp(None, Op.byte, '"value"'), - TealOp(None, Op.callsub, subroutine3Label), - TealOp(None, Op.txn, "Fee"), - TealOp(None, Op.int, 0), - TealOp(None, Op.eq), - TealOp(None, Op.bz, l1Label), - TealOp(None, Op.int, 3), - TealOp(None, Op.callsub, subroutine2Label), - TealOp(None, Op.pop), - TealOp(None, Op.callsub, subroutine1Label), - TealOp(None, Op.return_), - TealLabel(None, l1Label), - TealOp(None, Op.int, 0), - TealOp(None, Op.return_), - TealLabel(None, expectedSubroutine1Label, "sub1Impl"), - TealOp(None, Op.int, 1), - TealOp(None, Op.int, 2), - TealOp(None, Op.int, 3), - TealOp(None, Op.add), - TealOp(None, Op.add), - TealOp(None, Op.retsub), - TealLabel(None, expectedSubroutine2Label, "sub2Impl"), - TealOp(None, Op.dup), - TealOp(None, Op.int, 1), - TealOp(None, Op.eq), - TealOp(None, Op.bnz, expectedSubroutine2L1Label), - TealOp(None, Op.dup), - TealOp(None, Op.int, 2), - TealOp(None, Op.eq), - TealOp(None, Op.bnz, expectedSubroutine2L2Label), - TealOp(None, Op.dup), - TealOp(None, Op.int, 3), - TealOp(None, Op.eq), - TealOp(None, Op.bnz, expectedSubroutine2L3Label), - TealOp(None, Op.dup), - TealOp(None, Op.int, 4), - TealOp(None, Op.eq), - TealOp(None, Op.bnz, expectedSubroutine2L4Label), - TealOp(None, Op.err), - TealLabel(None, expectedSubroutine2L1Label), - TealOp(None, Op.pop), - TealOp(None, Op.byte, '"1"'), - TealOp(None, Op.retsub), - TealLabel(None, expectedSubroutine2L2Label), - TealOp(None, Op.pop), - TealOp(None, Op.byte, '"2"'), - TealOp(None, Op.retsub), - TealLabel(None, expectedSubroutine2L3Label), - TealOp(None, Op.pop), - TealOp(None, Op.byte, '"3"'), - TealOp(None, Op.retsub), - TealLabel(None, expectedSubroutine2L4Label), - TealOp(None, Op.pop), - TealOp(None, Op.byte, '"4"'), - TealOp(None, Op.retsub), - TealLabel(None, expectedSubroutine3Label, "sub3Impl"), - TealLabel(None, expectedSubroutine3L1Label), - TealOp(None, Op.app_local_put), - TealOp(None, Op.retsub), - TealOp(None, Op.b, expectedSubroutine3L1Label), + pt.TealOp(None, pt.Op.byte, '"account"'), + pt.TealOp(None, pt.Op.byte, '"key"'), + pt.TealOp(None, pt.Op.byte, '"value"'), + pt.TealOp(None, pt.Op.callsub, subroutine3Label), + pt.TealOp(None, pt.Op.txn, "Fee"), + pt.TealOp(None, pt.Op.int, 0), + pt.TealOp(None, pt.Op.eq), + pt.TealOp(None, pt.Op.bz, expectedL1Label), + pt.TealOp(None, pt.Op.int, 3), + pt.TealOp(None, pt.Op.callsub, subroutine2Label), + pt.TealOp(None, pt.Op.pop), + pt.TealOp(None, pt.Op.callsub, subroutine1Label), + pt.TealOp(None, pt.Op.return_), + pt.TealLabel(None, expectedL1Label), + pt.TealOp(None, pt.Op.int, 0), + pt.TealOp(None, pt.Op.return_), + pt.TealLabel(None, expectedSubroutine1Label, "sub1Impl"), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.int, 2), + pt.TealOp(None, pt.Op.int, 3), + pt.TealOp(None, pt.Op.add), + pt.TealOp(None, pt.Op.add), + pt.TealOp(None, pt.Op.retsub), + pt.TealLabel(None, expectedSubroutine2Label, "sub2Impl"), + pt.TealOp(None, pt.Op.dup), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.eq), + pt.TealOp(None, pt.Op.bnz, expectedSubroutine2L1Label), + pt.TealOp(None, pt.Op.dup), + pt.TealOp(None, pt.Op.int, 2), + pt.TealOp(None, pt.Op.eq), + pt.TealOp(None, pt.Op.bnz, expectedSubroutine2L2Label), + pt.TealOp(None, pt.Op.dup), + pt.TealOp(None, pt.Op.int, 3), + pt.TealOp(None, pt.Op.eq), + pt.TealOp(None, pt.Op.bnz, expectedSubroutine2L3Label), + pt.TealOp(None, pt.Op.dup), + pt.TealOp(None, pt.Op.int, 4), + pt.TealOp(None, pt.Op.eq), + pt.TealOp(None, pt.Op.bnz, expectedSubroutine2L4Label), + pt.TealOp(None, pt.Op.err), + pt.TealLabel(None, expectedSubroutine2L1Label), + pt.TealOp(None, pt.Op.pop), + pt.TealOp(None, pt.Op.byte, '"1"'), + pt.TealOp(None, pt.Op.retsub), + pt.TealLabel(None, expectedSubroutine2L2Label), + pt.TealOp(None, pt.Op.pop), + pt.TealOp(None, pt.Op.byte, '"2"'), + pt.TealOp(None, pt.Op.retsub), + pt.TealLabel(None, expectedSubroutine2L3Label), + pt.TealOp(None, pt.Op.pop), + pt.TealOp(None, pt.Op.byte, '"3"'), + pt.TealOp(None, pt.Op.retsub), + pt.TealLabel(None, expectedSubroutine2L4Label), + pt.TealOp(None, pt.Op.pop), + pt.TealOp(None, pt.Op.byte, '"4"'), + pt.TealOp(None, pt.Op.retsub), + pt.TealLabel(None, expectedSubroutine3Label, "sub3Impl"), + pt.TealLabel(None, expectedSubroutine3L1Label), + pt.TealOp(None, pt.Op.app_local_put), + pt.TealOp(None, pt.Op.retsub), + pt.TealOp(None, pt.Op.b, expectedSubroutine3L1Label), ] actual = flattenSubroutines(subroutineMapping, subroutineToLabel) diff --git a/pyteal/compiler/optimizer/__init__.py b/pyteal/compiler/optimizer/__init__.py index 821f329d3..f12afea53 100644 --- a/pyteal/compiler/optimizer/__init__.py +++ b/pyteal/compiler/optimizer/__init__.py @@ -1 +1,4 @@ -from .optimizer import OptimizeOptions, apply_global_optimizations +from pyteal.compiler.optimizer.optimizer import ( + OptimizeOptions, + apply_global_optimizations, +) diff --git a/pyteal/compiler/optimizer/optimizer.py b/pyteal/compiler/optimizer/optimizer.py index 93a063ed5..056cfe699 100644 --- a/pyteal/compiler/optimizer/optimizer.py +++ b/pyteal/compiler/optimizer/optimizer.py @@ -1,7 +1,7 @@ from typing import Set -from ...ast import ScratchSlot -from ...ir import TealBlock, TealOp, Op -from ...errors import TealInternalError +from pyteal.ast import ScratchSlot +from pyteal.ir import TealBlock, TealOp, Op +from pyteal.errors import TealInternalError class OptimizeOptions: diff --git a/pyteal/compiler/optimizer/optimizer_test.py b/pyteal/compiler/optimizer/optimizer_test.py index 05d61f312..f6f557471 100644 --- a/pyteal/compiler/optimizer/optimizer_test.py +++ b/pyteal/compiler/optimizer/optimizer_test.py @@ -1,79 +1,79 @@ -from .optimizer import OptimizeOptions, _apply_slot_to_stack +from pyteal.compiler.optimizer.optimizer import OptimizeOptions, _apply_slot_to_stack -from ... import * - -import pytest +import pyteal as pt def test_optimize_single_block(): - slot1 = ScratchSlot(1) - slot2 = ScratchSlot(2) + slot1 = pt.ScratchSlot(1) + slot2 = pt.ScratchSlot(2) # empty check - empty_block = TealSimpleBlock([]) + empty_block = pt.TealSimpleBlock([]) _apply_slot_to_stack(empty_block, empty_block, {}) - expected = TealSimpleBlock([]) + expected = pt.TealSimpleBlock([]) assert empty_block == expected # basic optimization - block = TealSimpleBlock( + block = pt.TealSimpleBlock( [ - TealOp(None, Op.store, slot1), - TealOp(None, Op.load, slot1), + pt.TealOp(None, pt.Op.store, slot1), + pt.TealOp(None, pt.Op.load, slot1), ] ) _apply_slot_to_stack(block, block, {}) - expected = TealSimpleBlock([]) + expected = pt.TealSimpleBlock([]) assert block == expected # iterate optimization - block = TealSimpleBlock( + block = pt.TealSimpleBlock( [ - TealOp(None, Op.store, slot1), - TealOp(None, Op.store, slot2), - TealOp(None, Op.load, slot2), - TealOp(None, Op.load, slot1), + pt.TealOp(None, pt.Op.store, slot1), + pt.TealOp(None, pt.Op.store, slot2), + pt.TealOp(None, pt.Op.load, slot2), + pt.TealOp(None, pt.Op.load, slot1), ] ) _apply_slot_to_stack(block, block, {}) - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(None, Op.store, slot1), - TealOp(None, Op.load, slot1), + pt.TealOp(None, pt.Op.store, slot1), + pt.TealOp(None, pt.Op.load, slot1), ] ) assert block == expected _apply_slot_to_stack(block, block, {}) - expected = TealSimpleBlock([]) + expected = pt.TealSimpleBlock([]) assert block == expected # remove extraneous stores - block = TealSimpleBlock( + block = pt.TealSimpleBlock( [ - TealOp(None, Op.store, slot1), - TealOp(None, Op.load, slot1), - TealOp(None, Op.store, slot1), + pt.TealOp(None, pt.Op.store, slot1), + pt.TealOp(None, pt.Op.load, slot1), + pt.TealOp(None, pt.Op.store, slot1), ] ) _apply_slot_to_stack(block, block, {}) - expected = TealSimpleBlock([]) + expected = pt.TealSimpleBlock([]) assert block == expected def test_optimize_subroutine(): - @Subroutine(TealType.uint64) - def add(a1: Expr, a2: Expr) -> Expr: + @pt.Subroutine(pt.TealType.uint64) + def add(a1: pt.Expr, a2: pt.Expr) -> pt.Expr: return a1 + a2 - program = Seq( + program = pt.Seq( [ - If(Txn.sender() == Global.creator_address()).Then(Pop(add(Int(1), Int(2)))), - Approve(), + pt.If(pt.Txn.sender() == pt.Global.creator_address()).Then( + pt.Pop(add(pt.Int(1), pt.Int(2))) + ), + pt.Approve(), ] ) @@ -102,8 +102,8 @@ def add(a1: Expr, a2: Expr) -> Expr: + retsub """.strip() - actual = compileTeal( - program, version=4, mode=Mode.Application, optimize=optimize_options + actual = pt.compileTeal( + program, version=4, mode=pt.Mode.Application, optimize=optimize_options ) assert actual == expected @@ -127,24 +127,26 @@ def add(a1: Expr, a2: Expr) -> Expr: retsub """.strip() optimize_options = OptimizeOptions(scratch_slots=True) - actual = compileTeal( - program, version=4, mode=Mode.Application, optimize=optimize_options + actual = pt.compileTeal( + program, version=4, mode=pt.Mode.Application, optimize=optimize_options ) assert actual == expected def test_optimize_subroutine_with_scratchvar_arg(): - @Subroutine(TealType.uint64) - def add(a1: ScratchVar, a2: Expr) -> Expr: + @pt.Subroutine(pt.TealType.uint64) + def add(a1: pt.ScratchVar, a2: pt.Expr) -> pt.Expr: return a1.load() + a2 - arg = ScratchVar(TealType.uint64) + arg = pt.ScratchVar(pt.TealType.uint64) - program = Seq( + program = pt.Seq( [ - arg.store(Int(1)), - If(Txn.sender() == Global.creator_address()).Then(Pop(add(arg, Int(2)))), - Approve(), + arg.store(pt.Int(1)), + pt.If(pt.Txn.sender() == pt.Global.creator_address()).Then( + pt.Pop(add(arg, pt.Int(2))) + ), + pt.Approve(), ] ) @@ -175,8 +177,8 @@ def add(a1: ScratchVar, a2: Expr) -> Expr: load 2 + retsub""".strip() - actual = compileTeal( - program, version=5, mode=Mode.Application, optimize=optimize_options + actual = pt.compileTeal( + program, version=5, mode=pt.Mode.Application, optimize=optimize_options ) assert actual == expected @@ -204,23 +206,25 @@ def add(a1: ScratchVar, a2: Expr) -> Expr: + retsub""".strip() optimize_options = OptimizeOptions(scratch_slots=True) - actual = compileTeal( - program, version=5, mode=Mode.Application, optimize=optimize_options + actual = pt.compileTeal( + program, version=5, mode=pt.Mode.Application, optimize=optimize_options ) assert actual == expected def test_optimize_subroutine_with_local_var(): - local_var = ScratchVar(TealType.uint64) + local_var = pt.ScratchVar(pt.TealType.uint64) - @Subroutine(TealType.uint64) - def add(a1: Expr) -> Expr: - return Seq(local_var.store(Int(2)), local_var.load() + a1) + @pt.Subroutine(pt.TealType.uint64) + def add(a1: pt.Expr) -> pt.Expr: + return pt.Seq(local_var.store(pt.Int(2)), local_var.load() + a1) - program = Seq( + program = pt.Seq( [ - If(Txn.sender() == Global.creator_address()).Then(Pop(add(Int(1)))), - Approve(), + pt.If(pt.Txn.sender() == pt.Global.creator_address()).Then( + pt.Pop(add(pt.Int(1))) + ), + pt.Approve(), ] ) @@ -249,8 +253,8 @@ def add(a1: Expr) -> Expr: + retsub """.strip() - actual = compileTeal( - program, version=4, mode=Mode.Application, optimize=optimize_options + actual = pt.compileTeal( + program, version=4, mode=pt.Mode.Application, optimize=optimize_options ) assert actual == expected @@ -276,24 +280,26 @@ def add(a1: Expr) -> Expr: retsub """.strip() optimize_options = OptimizeOptions(scratch_slots=True) - actual = compileTeal( - program, version=4, mode=Mode.Application, optimize=optimize_options + actual = pt.compileTeal( + program, version=4, mode=pt.Mode.Application, optimize=optimize_options ) assert actual == expected def test_optimize_subroutine_with_global_var(): - global_var = ScratchVar(TealType.uint64) + global_var = pt.ScratchVar(pt.TealType.uint64) - @Subroutine(TealType.uint64) - def add(a1: Expr) -> Expr: - return Seq(global_var.store(Int(2)), global_var.load() + a1) + @pt.Subroutine(pt.TealType.uint64) + def add(a1: pt.Expr) -> pt.Expr: + return pt.Seq(global_var.store(pt.Int(2)), global_var.load() + a1) - program = Seq( + program = pt.Seq( [ - If(Txn.sender() == Global.creator_address()).Then(Pop(add(Int(1)))), - global_var.store(Int(5)), - Approve(), + pt.If(pt.Txn.sender() == pt.Global.creator_address()).Then( + pt.Pop(add(pt.Int(1))) + ), + global_var.store(pt.Int(5)), + pt.Approve(), ] ) @@ -324,30 +330,32 @@ def add(a1: Expr) -> Expr: + retsub """.strip() - actual = compileTeal( - program, version=4, mode=Mode.Application, optimize=optimize_options + actual = pt.compileTeal( + program, version=4, mode=pt.Mode.Application, optimize=optimize_options ) assert actual == expected # optimization should not apply to global vars optimize_options = OptimizeOptions(scratch_slots=True) - actual = compileTeal( - program, version=4, mode=Mode.Application, optimize=optimize_options + actual = pt.compileTeal( + program, version=4, mode=pt.Mode.Application, optimize=optimize_options ) assert actual == expected def test_optimize_subroutine_with_reserved_local_var(): - local_var = ScratchVar(TealType.uint64, 0) + local_var = pt.ScratchVar(pt.TealType.uint64, 0) - @Subroutine(TealType.uint64) - def add(a1: Expr) -> Expr: - return Seq(local_var.store(Int(2)), local_var.load() + a1) + @pt.Subroutine(pt.TealType.uint64) + def add(a1: pt.Expr) -> pt.Expr: + return pt.Seq(local_var.store(pt.Int(2)), local_var.load() + a1) - program = Seq( + program = pt.Seq( [ - If(Txn.sender() == Global.creator_address()).Then(Pop(add(Int(1)))), - Approve(), + pt.If(pt.Txn.sender() == pt.Global.creator_address()).Then( + pt.Pop(add(pt.Int(1))) + ), + pt.Approve(), ] ) @@ -376,29 +384,31 @@ def add(a1: Expr) -> Expr: + retsub """.strip() - actual = compileTeal( - program, version=4, mode=Mode.Application, optimize=optimize_options + actual = pt.compileTeal( + program, version=4, mode=pt.Mode.Application, optimize=optimize_options ) assert actual == expected # The optimization must skip over the reserved slot id so the expected result # hasn't changed. optimize_options = OptimizeOptions(scratch_slots=True) - actual = compileTeal( - program, version=4, mode=Mode.Application, optimize=optimize_options + actual = pt.compileTeal( + program, version=4, mode=pt.Mode.Application, optimize=optimize_options ) assert actual == expected def test_optimize_subroutine_with_load_dependency(): - @Subroutine(TealType.uint64) - def add(a1: Expr, a2: Expr) -> Expr: - return Seq(Pop(a1 + a2), a2) + @pt.Subroutine(pt.TealType.uint64) + def add(a1: pt.Expr, a2: pt.Expr) -> pt.Expr: + return pt.Seq(pt.Pop(a1 + a2), a2) - program = Seq( + program = pt.Seq( [ - If(Txn.sender() == Global.creator_address()).Then(Pop(add(Int(1), Int(2)))), - Approve(), + pt.If(pt.Txn.sender() == pt.Global.creator_address()).Then( + pt.Pop(add(pt.Int(1), pt.Int(2))) + ), + pt.Approve(), ] ) @@ -428,8 +438,8 @@ def add(a1: Expr, a2: Expr) -> Expr: pop load 1 retsub""".strip() - actual = compileTeal( - program, version=4, mode=Mode.Application, optimize=optimize_options + actual = pt.compileTeal( + program, version=4, mode=pt.Mode.Application, optimize=optimize_options ) assert actual == expected @@ -457,22 +467,22 @@ def add(a1: Expr, a2: Expr) -> Expr: load 0 retsub""".strip() optimize_options = OptimizeOptions(scratch_slots=True) - actual = compileTeal( - program, version=4, mode=Mode.Application, optimize=optimize_options + actual = pt.compileTeal( + program, version=4, mode=pt.Mode.Application, optimize=optimize_options ) assert actual == expected def test_optimize_multi_value(): # note: this is incorrect usage of the app_global_get_ex opcode - program = Seq( - MultiValue( - Op.app_global_get_ex, - [TealType.uint64, TealType.uint64], + program = pt.Seq( + pt.MultiValue( + pt.Op.app_global_get_ex, + [pt.TealType.uint64, pt.TealType.uint64], immediate_args=[], - args=[Int(0), Int(1)], - ).outputReducer(lambda value, hasValue: Pop(value + hasValue)), - Approve(), + args=[pt.Int(0), pt.Int(1)], + ).outputReducer(lambda value, hasValue: pt.Pop(value + hasValue)), + pt.Approve(), ) optimize_options = OptimizeOptions() @@ -490,8 +500,8 @@ def test_optimize_multi_value(): pop int 1 return""".strip() - actual = compileTeal( - program, version=4, mode=Mode.Application, optimize=optimize_options + actual = pt.compileTeal( + program, version=4, mode=pt.Mode.Application, optimize=optimize_options ) assert actual == expected @@ -505,21 +515,21 @@ def test_optimize_multi_value(): int 1 return""".strip() optimize_options = OptimizeOptions(scratch_slots=True) - actual = compileTeal( - program, version=4, mode=Mode.Application, optimize=optimize_options + actual = pt.compileTeal( + program, version=4, mode=pt.Mode.Application, optimize=optimize_options ) assert actual == expected def test_optimize_dynamic_var(): - myvar = DynamicScratchVar() - regvar = ScratchVar() - program = Seq( - regvar.store(Int(1)), + myvar = pt.DynamicScratchVar() + regvar = pt.ScratchVar() + program = pt.Seq( + regvar.store(pt.Int(1)), myvar.set_index(regvar), - regvar.store(Int(2)), - Pop(regvar.load()), - Approve(), + regvar.store(pt.Int(2)), + pt.Pop(regvar.load()), + pt.Approve(), ) optimize_options = OptimizeOptions() @@ -536,15 +546,15 @@ def test_optimize_dynamic_var(): pop int 1 return""".strip() - actual = compileTeal( - program, version=4, mode=Mode.Application, optimize=optimize_options + actual = pt.compileTeal( + program, version=4, mode=pt.Mode.Application, optimize=optimize_options ) assert actual == expected # optimization should not change the code because the candidate slot # is used by the dynamic slot variable. optimize_options = OptimizeOptions(scratch_slots=True) - actual = compileTeal( - program, version=4, mode=Mode.Application, optimize=optimize_options + actual = pt.compileTeal( + program, version=4, mode=pt.Mode.Application, optimize=optimize_options ) assert actual == expected diff --git a/pyteal/compiler/scratchslots.py b/pyteal/compiler/scratchslots.py index 91198d979..3d316b269 100644 --- a/pyteal/compiler/scratchslots.py +++ b/pyteal/compiler/scratchslots.py @@ -1,9 +1,9 @@ from typing import Tuple, Set, Dict, Optional, cast -from ..ast import ScratchSlot, SubroutineDefinition -from ..ir import Mode, TealComponent, TealBlock, Op -from ..errors import TealInputError, TealInternalError -from ..config import NUM_SLOTS +from pyteal.ast import ScratchSlot, SubroutineDefinition +from pyteal.ir import TealBlock, Op +from pyteal.errors import TealInternalError +from pyteal.config import NUM_SLOTS def collect_unoptimized_slots( diff --git a/pyteal/compiler/scratchslots_test.py b/pyteal/compiler/scratchslots_test.py index d5155c235..5fee6c66a 100644 --- a/pyteal/compiler/scratchslots_test.py +++ b/pyteal/compiler/scratchslots_test.py @@ -1,8 +1,11 @@ import pytest -from .. import * +import pyteal as pt -from .scratchslots import collectScratchSlots, assignScratchSlotsToSubroutines +from pyteal.compiler.scratchslots import ( + collectScratchSlots, + assignScratchSlotsToSubroutines, +) def test_collectScratchSlots(): @@ -15,53 +18,53 @@ def sub2Impl(a1): def sub3Impl(a1, a2, a3): return None - subroutine1 = SubroutineDefinition(sub1Impl, TealType.uint64) - subroutine2 = SubroutineDefinition(sub2Impl, TealType.bytes) - subroutine3 = SubroutineDefinition(sub3Impl, TealType.none) + subroutine1 = pt.SubroutineDefinition(sub1Impl, pt.TealType.uint64) + subroutine2 = pt.SubroutineDefinition(sub2Impl, pt.TealType.bytes) + subroutine3 = pt.SubroutineDefinition(sub3Impl, pt.TealType.none) - globalSlot1 = ScratchSlot() + globalSlot1 = pt.ScratchSlot() - subroutine1Slot1 = ScratchSlot() - subroutine1Slot2 = ScratchSlot() + subroutine1Slot1 = pt.ScratchSlot() + subroutine1Slot2 = pt.ScratchSlot() subroutine1Ops = [ - TealOp(None, Op.int, 1), - TealOp(None, Op.store, subroutine1Slot1), - TealOp(None, Op.int, 3), - TealOp(None, Op.store, subroutine1Slot2), - TealOp(None, Op.load, globalSlot1), - TealOp(None, Op.retsub), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.store, subroutine1Slot1), + pt.TealOp(None, pt.Op.int, 3), + pt.TealOp(None, pt.Op.store, subroutine1Slot2), + pt.TealOp(None, pt.Op.load, globalSlot1), + pt.TealOp(None, pt.Op.retsub), ] - subroutine2Slot1 = ScratchSlot() + subroutine2Slot1 = pt.ScratchSlot() subroutine2Ops = [ - TealOp(None, Op.byte, '"value"'), - TealOp(None, Op.store, subroutine2Slot1), - TealOp(None, Op.load, subroutine2Slot1), - TealOp(None, Op.retsub), + pt.TealOp(None, pt.Op.byte, '"value"'), + pt.TealOp(None, pt.Op.store, subroutine2Slot1), + pt.TealOp(None, pt.Op.load, subroutine2Slot1), + pt.TealOp(None, pt.Op.retsub), ] subroutine3Ops = [ - TealOp(None, Op.retsub), + pt.TealOp(None, pt.Op.retsub), ] - mainSlot1 = ScratchSlot() - mainSlot2 = ScratchSlot() + mainSlot1 = pt.ScratchSlot() + mainSlot2 = pt.ScratchSlot() mainOps = [ - TealOp(None, Op.int, 7), - TealOp(None, Op.store, globalSlot1), - TealOp(None, Op.int, 1), - TealOp(None, Op.store, mainSlot1), - TealOp(None, Op.int, 2), - TealOp(None, Op.store, mainSlot2), - TealOp(None, Op.load, mainSlot1), - TealOp(None, Op.return_), + pt.TealOp(None, pt.Op.int, 7), + pt.TealOp(None, pt.Op.store, globalSlot1), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.store, mainSlot1), + pt.TealOp(None, pt.Op.int, 2), + pt.TealOp(None, pt.Op.store, mainSlot2), + pt.TealOp(None, pt.Op.load, mainSlot1), + pt.TealOp(None, pt.Op.return_), ] subroutineBlocks = { - None: TealSimpleBlock(mainOps), - subroutine1: TealSimpleBlock(subroutine1Ops), - subroutine2: TealSimpleBlock(subroutine2Ops), - subroutine3: TealSimpleBlock(subroutine3Ops), + None: pt.TealSimpleBlock(mainOps), + subroutine1: pt.TealSimpleBlock(subroutine1Ops), + subroutine2: pt.TealSimpleBlock(subroutine2Ops), + subroutine3: pt.TealSimpleBlock(subroutine3Ops), } expected_global = {globalSlot1} @@ -89,53 +92,53 @@ def sub2Impl(a1): def sub3Impl(a1, a2, a3): return None - subroutine1 = SubroutineDefinition(sub1Impl, TealType.uint64) - subroutine2 = SubroutineDefinition(sub2Impl, TealType.bytes) - subroutine3 = SubroutineDefinition(sub3Impl, TealType.none) + subroutine1 = pt.SubroutineDefinition(sub1Impl, pt.TealType.uint64) + subroutine2 = pt.SubroutineDefinition(sub2Impl, pt.TealType.bytes) + subroutine3 = pt.SubroutineDefinition(sub3Impl, pt.TealType.none) - globalSlot1 = ScratchSlot() + globalSlot1 = pt.ScratchSlot() - subroutine1Slot1 = ScratchSlot() - subroutine1Slot2 = ScratchSlot() + subroutine1Slot1 = pt.ScratchSlot() + subroutine1Slot2 = pt.ScratchSlot() subroutine1Ops = [ - TealOp(None, Op.int, 1), - TealOp(None, Op.store, subroutine1Slot1), - TealOp(None, Op.int, 3), - TealOp(None, Op.store, subroutine1Slot2), - TealOp(None, Op.load, globalSlot1), - TealOp(None, Op.retsub), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.store, subroutine1Slot1), + pt.TealOp(None, pt.Op.int, 3), + pt.TealOp(None, pt.Op.store, subroutine1Slot2), + pt.TealOp(None, pt.Op.load, globalSlot1), + pt.TealOp(None, pt.Op.retsub), ] - subroutine2Slot1 = ScratchSlot() + subroutine2Slot1 = pt.ScratchSlot() subroutine2Ops = [ - TealOp(None, Op.byte, '"value"'), - TealOp(None, Op.store, subroutine2Slot1), - TealOp(None, Op.load, subroutine2Slot1), - TealOp(None, Op.retsub), + pt.TealOp(None, pt.Op.byte, '"value"'), + pt.TealOp(None, pt.Op.store, subroutine2Slot1), + pt.TealOp(None, pt.Op.load, subroutine2Slot1), + pt.TealOp(None, pt.Op.retsub), ] subroutine3Ops = [ - TealOp(None, Op.retsub), + pt.TealOp(None, pt.Op.retsub), ] - mainSlot1 = ScratchSlot() - mainSlot2 = ScratchSlot() + mainSlot1 = pt.ScratchSlot() + mainSlot2 = pt.ScratchSlot() mainOps = [ - TealOp(None, Op.int, 7), - TealOp(None, Op.store, globalSlot1), - TealOp(None, Op.int, 1), - TealOp(None, Op.store, mainSlot1), - TealOp(None, Op.int, 2), - TealOp(None, Op.store, mainSlot2), - TealOp(None, Op.load, mainSlot1), - TealOp(None, Op.return_), + pt.TealOp(None, pt.Op.int, 7), + pt.TealOp(None, pt.Op.store, globalSlot1), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.store, mainSlot1), + pt.TealOp(None, pt.Op.int, 2), + pt.TealOp(None, pt.Op.store, mainSlot2), + pt.TealOp(None, pt.Op.load, mainSlot1), + pt.TealOp(None, pt.Op.return_), ] subroutineBlocks = { - None: TealSimpleBlock(mainOps), - subroutine1: TealSimpleBlock(subroutine1Ops), - subroutine2: TealSimpleBlock(subroutine2Ops), - subroutine3: TealSimpleBlock(subroutine3Ops), + None: pt.TealSimpleBlock(mainOps), + subroutine1: pt.TealSimpleBlock(subroutine1Ops), + subroutine2: pt.TealSimpleBlock(subroutine2Ops), + subroutine3: pt.TealSimpleBlock(subroutine3Ops), } expectedAssignments = { @@ -162,34 +165,34 @@ def sub3Impl(a1, a2, a3): assert actual == expected assert subroutine1Ops == [ - TealOp(None, Op.int, 1), - TealOp(None, Op.store, expectedAssignments[subroutine1Slot1]), - TealOp(None, Op.int, 3), - TealOp(None, Op.store, expectedAssignments[subroutine1Slot2]), - TealOp(None, Op.load, expectedAssignments[globalSlot1]), - TealOp(None, Op.retsub), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.store, expectedAssignments[subroutine1Slot1]), + pt.TealOp(None, pt.Op.int, 3), + pt.TealOp(None, pt.Op.store, expectedAssignments[subroutine1Slot2]), + pt.TealOp(None, pt.Op.load, expectedAssignments[globalSlot1]), + pt.TealOp(None, pt.Op.retsub), ] assert subroutine2Ops == [ - TealOp(None, Op.byte, '"value"'), - TealOp(None, Op.store, expectedAssignments[subroutine2Slot1]), - TealOp(None, Op.load, expectedAssignments[subroutine2Slot1]), - TealOp(None, Op.retsub), + pt.TealOp(None, pt.Op.byte, '"value"'), + pt.TealOp(None, pt.Op.store, expectedAssignments[subroutine2Slot1]), + pt.TealOp(None, pt.Op.load, expectedAssignments[subroutine2Slot1]), + pt.TealOp(None, pt.Op.retsub), ] assert subroutine3Ops == [ - TealOp(None, Op.retsub), + pt.TealOp(None, pt.Op.retsub), ] assert mainOps == [ - TealOp(None, Op.int, 7), - TealOp(None, Op.store, expectedAssignments[globalSlot1]), - TealOp(None, Op.int, 1), - TealOp(None, Op.store, expectedAssignments[mainSlot1]), - TealOp(None, Op.int, 2), - TealOp(None, Op.store, expectedAssignments[mainSlot2]), - TealOp(None, Op.load, expectedAssignments[mainSlot1]), - TealOp(None, Op.return_), + pt.TealOp(None, pt.Op.int, 7), + pt.TealOp(None, pt.Op.store, expectedAssignments[globalSlot1]), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.store, expectedAssignments[mainSlot1]), + pt.TealOp(None, pt.Op.int, 2), + pt.TealOp(None, pt.Op.store, expectedAssignments[mainSlot2]), + pt.TealOp(None, pt.Op.load, expectedAssignments[mainSlot1]), + pt.TealOp(None, pt.Op.return_), ] @@ -203,53 +206,53 @@ def sub2Impl(a1): def sub3Impl(a1, a2, a3): return None - subroutine1 = SubroutineDefinition(sub1Impl, TealType.uint64) - subroutine2 = SubroutineDefinition(sub2Impl, TealType.bytes) - subroutine3 = SubroutineDefinition(sub3Impl, TealType.none) + subroutine1 = pt.SubroutineDefinition(sub1Impl, pt.TealType.uint64) + subroutine2 = pt.SubroutineDefinition(sub2Impl, pt.TealType.bytes) + subroutine3 = pt.SubroutineDefinition(sub3Impl, pt.TealType.none) - globalSlot1 = ScratchSlot(requestedSlotId=8) + globalSlot1 = pt.ScratchSlot(requestedSlotId=8) - subroutine1Slot1 = ScratchSlot() - subroutine1Slot2 = ScratchSlot(requestedSlotId=5) + subroutine1Slot1 = pt.ScratchSlot() + subroutine1Slot2 = pt.ScratchSlot(requestedSlotId=5) subroutine1Ops = [ - TealOp(None, Op.int, 1), - TealOp(None, Op.store, subroutine1Slot1), - TealOp(None, Op.int, 3), - TealOp(None, Op.store, subroutine1Slot2), - TealOp(None, Op.load, globalSlot1), - TealOp(None, Op.retsub), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.store, subroutine1Slot1), + pt.TealOp(None, pt.Op.int, 3), + pt.TealOp(None, pt.Op.store, subroutine1Slot2), + pt.TealOp(None, pt.Op.load, globalSlot1), + pt.TealOp(None, pt.Op.retsub), ] - subroutine2Slot1 = ScratchSlot() + subroutine2Slot1 = pt.ScratchSlot() subroutine2Ops = [ - TealOp(None, Op.byte, '"value"'), - TealOp(None, Op.store, subroutine2Slot1), - TealOp(None, Op.load, subroutine2Slot1), - TealOp(None, Op.retsub), + pt.TealOp(None, pt.Op.byte, '"value"'), + pt.TealOp(None, pt.Op.store, subroutine2Slot1), + pt.TealOp(None, pt.Op.load, subroutine2Slot1), + pt.TealOp(None, pt.Op.retsub), ] subroutine3Ops = [ - TealOp(None, Op.retsub), + pt.TealOp(None, pt.Op.retsub), ] - mainSlot1 = ScratchSlot() - mainSlot2 = ScratchSlot(requestedSlotId=100) + mainSlot1 = pt.ScratchSlot() + mainSlot2 = pt.ScratchSlot(requestedSlotId=100) mainOps = [ - TealOp(None, Op.int, 7), - TealOp(None, Op.store, globalSlot1), - TealOp(None, Op.int, 1), - TealOp(None, Op.store, mainSlot1), - TealOp(None, Op.int, 2), - TealOp(None, Op.store, mainSlot2), - TealOp(None, Op.load, mainSlot1), - TealOp(None, Op.return_), + pt.TealOp(None, pt.Op.int, 7), + pt.TealOp(None, pt.Op.store, globalSlot1), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.store, mainSlot1), + pt.TealOp(None, pt.Op.int, 2), + pt.TealOp(None, pt.Op.store, mainSlot2), + pt.TealOp(None, pt.Op.load, mainSlot1), + pt.TealOp(None, pt.Op.return_), ] subroutineBlocks = { - None: TealSimpleBlock(mainOps), - subroutine1: TealSimpleBlock(subroutine1Ops), - subroutine2: TealSimpleBlock(subroutine2Ops), - subroutine3: TealSimpleBlock(subroutine3Ops), + None: pt.TealSimpleBlock(mainOps), + subroutine1: pt.TealSimpleBlock(subroutine1Ops), + subroutine2: pt.TealSimpleBlock(subroutine2Ops), + subroutine3: pt.TealSimpleBlock(subroutine3Ops), } expectedAssignments = { @@ -276,34 +279,34 @@ def sub3Impl(a1, a2, a3): assert actual == expected assert subroutine1Ops == [ - TealOp(None, Op.int, 1), - TealOp(None, Op.store, expectedAssignments[subroutine1Slot1]), - TealOp(None, Op.int, 3), - TealOp(None, Op.store, expectedAssignments[subroutine1Slot2]), - TealOp(None, Op.load, expectedAssignments[globalSlot1]), - TealOp(None, Op.retsub), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.store, expectedAssignments[subroutine1Slot1]), + pt.TealOp(None, pt.Op.int, 3), + pt.TealOp(None, pt.Op.store, expectedAssignments[subroutine1Slot2]), + pt.TealOp(None, pt.Op.load, expectedAssignments[globalSlot1]), + pt.TealOp(None, pt.Op.retsub), ] assert subroutine2Ops == [ - TealOp(None, Op.byte, '"value"'), - TealOp(None, Op.store, expectedAssignments[subroutine2Slot1]), - TealOp(None, Op.load, expectedAssignments[subroutine2Slot1]), - TealOp(None, Op.retsub), + pt.TealOp(None, pt.Op.byte, '"value"'), + pt.TealOp(None, pt.Op.store, expectedAssignments[subroutine2Slot1]), + pt.TealOp(None, pt.Op.load, expectedAssignments[subroutine2Slot1]), + pt.TealOp(None, pt.Op.retsub), ] assert subroutine3Ops == [ - TealOp(None, Op.retsub), + pt.TealOp(None, pt.Op.retsub), ] assert mainOps == [ - TealOp(None, Op.int, 7), - TealOp(None, Op.store, expectedAssignments[globalSlot1]), - TealOp(None, Op.int, 1), - TealOp(None, Op.store, expectedAssignments[mainSlot1]), - TealOp(None, Op.int, 2), - TealOp(None, Op.store, expectedAssignments[mainSlot2]), - TealOp(None, Op.load, expectedAssignments[mainSlot1]), - TealOp(None, Op.return_), + pt.TealOp(None, pt.Op.int, 7), + pt.TealOp(None, pt.Op.store, expectedAssignments[globalSlot1]), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.store, expectedAssignments[mainSlot1]), + pt.TealOp(None, pt.Op.int, 2), + pt.TealOp(None, pt.Op.store, expectedAssignments[mainSlot2]), + pt.TealOp(None, pt.Op.load, expectedAssignments[mainSlot1]), + pt.TealOp(None, pt.Op.return_), ] @@ -317,58 +320,58 @@ def sub2Impl(a1): def sub3Impl(a1, a2, a3): return None - subroutine1 = SubroutineDefinition(sub1Impl, TealType.uint64) - subroutine2 = SubroutineDefinition(sub2Impl, TealType.bytes) - subroutine3 = SubroutineDefinition(sub3Impl, TealType.none) + subroutine1 = pt.SubroutineDefinition(sub1Impl, pt.TealType.uint64) + subroutine2 = pt.SubroutineDefinition(sub2Impl, pt.TealType.bytes) + subroutine3 = pt.SubroutineDefinition(sub3Impl, pt.TealType.none) - globalSlot1 = ScratchSlot(requestedSlotId=8) + globalSlot1 = pt.ScratchSlot(requestedSlotId=8) - subroutine1Slot1 = ScratchSlot() - subroutine1Slot2 = ScratchSlot(requestedSlotId=5) + subroutine1Slot1 = pt.ScratchSlot() + subroutine1Slot2 = pt.ScratchSlot(requestedSlotId=5) subroutine1Ops = [ - TealOp(None, Op.int, 1), - TealOp(None, Op.store, subroutine1Slot1), - TealOp(None, Op.int, 3), - TealOp(None, Op.store, subroutine1Slot2), - TealOp(None, Op.load, globalSlot1), - TealOp(None, Op.retsub), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.store, subroutine1Slot1), + pt.TealOp(None, pt.Op.int, 3), + pt.TealOp(None, pt.Op.store, subroutine1Slot2), + pt.TealOp(None, pt.Op.load, globalSlot1), + pt.TealOp(None, pt.Op.retsub), ] - subroutine2Slot1 = ScratchSlot(requestedSlotId=100) + subroutine2Slot1 = pt.ScratchSlot(requestedSlotId=100) subroutine2Ops = [ - TealOp(None, Op.byte, '"value"'), - TealOp(None, Op.store, subroutine2Slot1), - TealOp(None, Op.load, subroutine2Slot1), - TealOp(None, Op.retsub), + pt.TealOp(None, pt.Op.byte, '"value"'), + pt.TealOp(None, pt.Op.store, subroutine2Slot1), + pt.TealOp(None, pt.Op.load, subroutine2Slot1), + pt.TealOp(None, pt.Op.retsub), ] subroutine3Ops = [ - TealOp(None, Op.retsub), + pt.TealOp(None, pt.Op.retsub), ] - mainSlot1 = ScratchSlot() - mainSlot2 = ScratchSlot(requestedSlotId=100) + mainSlot1 = pt.ScratchSlot() + mainSlot2 = pt.ScratchSlot(requestedSlotId=100) mainOps = [ - TealOp(None, Op.int, 7), - TealOp(None, Op.store, globalSlot1), - TealOp(None, Op.int, 1), - TealOp(None, Op.store, mainSlot1), - TealOp(None, Op.int, 2), - TealOp(None, Op.store, mainSlot2), - TealOp(None, Op.load, mainSlot1), - TealOp(None, Op.return_), + pt.TealOp(None, pt.Op.int, 7), + pt.TealOp(None, pt.Op.store, globalSlot1), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.store, mainSlot1), + pt.TealOp(None, pt.Op.int, 2), + pt.TealOp(None, pt.Op.store, mainSlot2), + pt.TealOp(None, pt.Op.load, mainSlot1), + pt.TealOp(None, pt.Op.return_), ] subroutineBlocks = { - None: TealSimpleBlock(mainOps), - subroutine1: TealSimpleBlock(subroutine1Ops), - subroutine2: TealSimpleBlock(subroutine2Ops), - subroutine3: TealSimpleBlock(subroutine3Ops), + None: pt.TealSimpleBlock(mainOps), + subroutine1: pt.TealSimpleBlock(subroutine1Ops), + subroutine2: pt.TealSimpleBlock(subroutine2Ops), + subroutine3: pt.TealSimpleBlock(subroutine3Ops), } # mainSlot2 and subroutine2Slot1 request the same ID, 100 - with pytest.raises(TealInternalError): - actual = assignScratchSlotsToSubroutines(subroutineBlocks) + with pytest.raises(pt.TealInternalError): + assignScratchSlotsToSubroutines(subroutineBlocks) def test_assignScratchSlotsToSubroutines_slot_used_before_assignment(): @@ -381,52 +384,52 @@ def sub2Impl(a1): def sub3Impl(a1, a2, a3): return None - subroutine1 = SubroutineDefinition(sub1Impl, TealType.uint64) - subroutine2 = SubroutineDefinition(sub2Impl, TealType.bytes) - subroutine3 = SubroutineDefinition(sub3Impl, TealType.none) + subroutine1 = pt.SubroutineDefinition(sub1Impl, pt.TealType.uint64) + subroutine2 = pt.SubroutineDefinition(sub2Impl, pt.TealType.bytes) + subroutine3 = pt.SubroutineDefinition(sub3Impl, pt.TealType.none) - globalSlot1 = ScratchSlot() + globalSlot1 = pt.ScratchSlot() - subroutine1Slot1 = ScratchSlot() - subroutine1Slot2 = ScratchSlot() + subroutine1Slot1 = pt.ScratchSlot() + subroutine1Slot2 = pt.ScratchSlot() subroutine1Ops = [ - TealOp(None, Op.int, 1), - TealOp(None, Op.store, subroutine1Slot1), - TealOp(None, Op.int, 3), - TealOp(None, Op.store, subroutine1Slot2), - TealOp(None, Op.load, globalSlot1), - TealOp(None, Op.retsub), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.store, subroutine1Slot1), + pt.TealOp(None, pt.Op.int, 3), + pt.TealOp(None, pt.Op.store, subroutine1Slot2), + pt.TealOp(None, pt.Op.load, globalSlot1), + pt.TealOp(None, pt.Op.retsub), ] - subroutine2Slot1 = ScratchSlot() + subroutine2Slot1 = pt.ScratchSlot() subroutine2Ops = [ - TealOp(None, Op.byte, '"value"'), - TealOp(None, Op.store, subroutine2Slot1), - TealOp(None, Op.load, subroutine2Slot1), - TealOp(None, Op.retsub), + pt.TealOp(None, pt.Op.byte, '"value"'), + pt.TealOp(None, pt.Op.store, subroutine2Slot1), + pt.TealOp(None, pt.Op.load, subroutine2Slot1), + pt.TealOp(None, pt.Op.retsub), ] subroutine3Ops = [ - TealOp(None, Op.retsub), + pt.TealOp(None, pt.Op.retsub), ] - mainSlot1 = ScratchSlot() - mainSlot2 = ScratchSlot() + mainSlot1 = pt.ScratchSlot() + mainSlot2 = pt.ScratchSlot() mainOps = [ - TealOp(None, Op.int, 7), - TealOp(None, Op.store, globalSlot1), - TealOp(None, Op.int, 2), - TealOp(None, Op.store, mainSlot2), - TealOp(None, Op.load, mainSlot1), - TealOp(None, Op.return_), + pt.TealOp(None, pt.Op.int, 7), + pt.TealOp(None, pt.Op.store, globalSlot1), + pt.TealOp(None, pt.Op.int, 2), + pt.TealOp(None, pt.Op.store, mainSlot2), + pt.TealOp(None, pt.Op.load, mainSlot1), + pt.TealOp(None, pt.Op.return_), ] subroutineBlocks = { - None: TealSimpleBlock(mainOps), - subroutine1: TealSimpleBlock(subroutine1Ops), - subroutine2: TealSimpleBlock(subroutine2Ops), - subroutine3: TealSimpleBlock(subroutine3Ops), + None: pt.TealSimpleBlock(mainOps), + subroutine1: pt.TealSimpleBlock(subroutine1Ops), + subroutine2: pt.TealSimpleBlock(subroutine2Ops), + subroutine3: pt.TealSimpleBlock(subroutine3Ops), } - with pytest.raises(TealInternalError): + with pytest.raises(pt.TealInternalError): assignScratchSlotsToSubroutines(subroutineBlocks) diff --git a/pyteal/compiler/sort.py b/pyteal/compiler/sort.py index f4be9b98f..fc80070e9 100644 --- a/pyteal/compiler/sort.py +++ b/pyteal/compiler/sort.py @@ -1,7 +1,7 @@ from typing import List -from ..ir import TealBlock -from ..errors import TealInternalError +from pyteal.ir import TealBlock +from pyteal.errors import TealInternalError def sortBlocks(start: TealBlock, end: TealBlock) -> List[TealBlock]: diff --git a/pyteal/compiler/sort_test.py b/pyteal/compiler/sort_test.py index 9889a11b4..fec49a6b4 100644 --- a/pyteal/compiler/sort_test.py +++ b/pyteal/compiler/sort_test.py @@ -1,10 +1,10 @@ -from .. import * +import pyteal as pt -from .sort import sortBlocks +from pyteal.compiler.sort import sortBlocks def test_sort_single(): - block = TealSimpleBlock([TealOp(None, Op.int, 1)]) + block = pt.TealSimpleBlock([pt.TealOp(None, pt.Op.int, 1)]) block.addIncoming() block.validateTree() @@ -15,14 +15,14 @@ def test_sort_single(): def test_sort_sequence(): - block5 = TealSimpleBlock([TealOp(None, Op.int, 5)]) - block4 = TealSimpleBlock([TealOp(None, Op.int, 4)]) + block5 = pt.TealSimpleBlock([pt.TealOp(None, pt.Op.int, 5)]) + block4 = pt.TealSimpleBlock([pt.TealOp(None, pt.Op.int, 4)]) block4.setNextBlock(block5) - block3 = TealSimpleBlock([TealOp(None, Op.int, 3)]) + block3 = pt.TealSimpleBlock([pt.TealOp(None, pt.Op.int, 3)]) block3.setNextBlock(block4) - block2 = TealSimpleBlock([TealOp(None, Op.int, 2)]) + block2 = pt.TealSimpleBlock([pt.TealOp(None, pt.Op.int, 2)]) block2.setNextBlock(block3) - block1 = TealSimpleBlock([TealOp(None, Op.int, 1)]) + block1 = pt.TealSimpleBlock([pt.TealOp(None, pt.Op.int, 1)]) block1.setNextBlock(block2) block1.addIncoming() block1.validateTree() @@ -34,9 +34,9 @@ def test_sort_sequence(): def test_sort_branch(): - blockTrue = TealSimpleBlock([TealOp(None, Op.byte, '"true"')]) - blockFalse = TealSimpleBlock([TealOp(None, Op.byte, '"false"')]) - block = TealConditionalBlock([TealOp(None, Op.int, 1)]) + blockTrue = pt.TealSimpleBlock([pt.TealOp(None, pt.Op.byte, '"true"')]) + blockFalse = pt.TealSimpleBlock([pt.TealOp(None, pt.Op.byte, '"false"')]) + block = pt.TealConditionalBlock([pt.TealOp(None, pt.Op.int, 1)]) block.setTrueBlock(blockTrue) block.setFalseBlock(blockFalse) block.addIncoming() @@ -49,15 +49,15 @@ def test_sort_branch(): def test_sort_multiple_branch(): - blockTrueTrue = TealSimpleBlock([TealOp(None, Op.byte, '"true true"')]) - blockTrueFalse = TealSimpleBlock([TealOp(None, Op.byte, '"true false"')]) - blockTrueBranch = TealConditionalBlock([]) + blockTrueTrue = pt.TealSimpleBlock([pt.TealOp(None, pt.Op.byte, '"true true"')]) + blockTrueFalse = pt.TealSimpleBlock([pt.TealOp(None, pt.Op.byte, '"true false"')]) + blockTrueBranch = pt.TealConditionalBlock([]) blockTrueBranch.setTrueBlock(blockTrueTrue) blockTrueBranch.setFalseBlock(blockTrueFalse) - blockTrue = TealSimpleBlock([TealOp(None, Op.byte, '"true"')]) + blockTrue = pt.TealSimpleBlock([pt.TealOp(None, pt.Op.byte, '"true"')]) blockTrue.setNextBlock(blockTrueBranch) - blockFalse = TealSimpleBlock([TealOp(None, Op.byte, '"false"')]) - block = TealConditionalBlock([TealOp(None, Op.int, 1)]) + blockFalse = pt.TealSimpleBlock([pt.TealOp(None, pt.Op.byte, '"false"')]) + block = pt.TealConditionalBlock([pt.TealOp(None, pt.Op.int, 1)]) block.setTrueBlock(blockTrue) block.setFalseBlock(blockFalse) block.addIncoming() @@ -77,12 +77,12 @@ def test_sort_multiple_branch(): def test_sort_branch_converge(): - blockEnd = TealSimpleBlock([TealOp(None, Op.return_)]) - blockTrue = TealSimpleBlock([TealOp(None, Op.byte, '"true"')]) + blockEnd = pt.TealSimpleBlock([pt.TealOp(None, pt.Op.return_)]) + blockTrue = pt.TealSimpleBlock([pt.TealOp(None, pt.Op.byte, '"true"')]) blockTrue.setNextBlock(blockEnd) - blockFalse = TealSimpleBlock([TealOp(None, Op.byte, '"false"')]) + blockFalse = pt.TealSimpleBlock([pt.TealOp(None, pt.Op.byte, '"false"')]) blockFalse.setNextBlock(blockEnd) - block = TealConditionalBlock([TealOp(None, Op.int, 1)]) + block = pt.TealConditionalBlock([pt.TealOp(None, pt.Op.int, 1)]) block.setTrueBlock(blockTrue) block.setFalseBlock(blockFalse) block.addIncoming() diff --git a/pyteal/compiler/subroutines.py b/pyteal/compiler/subroutines.py index c7b038d6e..bc3ee74e7 100644 --- a/pyteal/compiler/subroutines.py +++ b/pyteal/compiler/subroutines.py @@ -2,10 +2,10 @@ from typing import List, Dict, Set, Optional, TypeVar from collections import OrderedDict -from ..errors import TealInputError -from ..types import TealType -from ..ast import SubroutineDefinition -from ..ir import TealComponent, TealOp, Op +from pyteal.errors import TealInputError +from pyteal.types import TealType +from pyteal.ast import SubroutineDefinition +from pyteal.ir import TealComponent, TealOp, Op # generic type variable Node = TypeVar("Node") diff --git a/pyteal/compiler/subroutines_test.py b/pyteal/compiler/subroutines_test.py index aa707d127..bd3879a94 100644 --- a/pyteal/compiler/subroutines_test.py +++ b/pyteal/compiler/subroutines_test.py @@ -2,9 +2,9 @@ import pytest -from .. import * +import pyteal as pt -from .subroutines import ( +from pyteal.compiler.subroutines import ( findRecursionPoints, spillLocalSlotsDuringRecursion, resolveSubroutines, @@ -29,9 +29,9 @@ def sub2Impl(a1): def sub3Impl(a1, a2, a3): return None - subroutine1 = SubroutineDefinition(sub1Impl, TealType.uint64) - subroutine2 = SubroutineDefinition(sub2Impl, TealType.bytes) - subroutine3 = SubroutineDefinition(sub3Impl, TealType.none) + subroutine1 = pt.SubroutineDefinition(sub1Impl, pt.TealType.uint64) + subroutine2 = pt.SubroutineDefinition(sub2Impl, pt.TealType.bytes) + subroutine3 = pt.SubroutineDefinition(sub3Impl, pt.TealType.none) subroutines = { subroutine1: {subroutine2, subroutine3}, @@ -59,9 +59,9 @@ def sub2Impl(a1): def sub3Impl(a1, a2, a3): return None - subroutine1 = SubroutineDefinition(sub1Impl, TealType.uint64) - subroutine2 = SubroutineDefinition(sub2Impl, TealType.bytes) - subroutine3 = SubroutineDefinition(sub3Impl, TealType.none) + subroutine1 = pt.SubroutineDefinition(sub1Impl, pt.TealType.uint64) + subroutine2 = pt.SubroutineDefinition(sub2Impl, pt.TealType.bytes) + subroutine3 = pt.SubroutineDefinition(sub3Impl, pt.TealType.none) subroutines = { subroutine1: {subroutine2, subroutine3}, @@ -89,9 +89,9 @@ def sub2Impl(a1): def sub3Impl(a1, a2, a3): return None - subroutine1 = SubroutineDefinition(sub1Impl, TealType.uint64) - subroutine2 = SubroutineDefinition(sub2Impl, TealType.bytes) - subroutine3 = SubroutineDefinition(sub3Impl, TealType.none) + subroutine1 = pt.SubroutineDefinition(sub1Impl, pt.TealType.uint64) + subroutine2 = pt.SubroutineDefinition(sub2Impl, pt.TealType.bytes) + subroutine3 = pt.SubroutineDefinition(sub3Impl, pt.TealType.none) subroutines = { subroutine1: {subroutine2, subroutine3}, @@ -119,9 +119,9 @@ def sub2Impl(a1): def sub3Impl(a1, a2, a3): return None - subroutine1 = SubroutineDefinition(sub1Impl, TealType.uint64) - subroutine2 = SubroutineDefinition(sub2Impl, TealType.bytes) - subroutine3 = SubroutineDefinition(sub3Impl, TealType.none) + subroutine1 = pt.SubroutineDefinition(sub1Impl, pt.TealType.uint64) + subroutine2 = pt.SubroutineDefinition(sub2Impl, pt.TealType.bytes) + subroutine3 = pt.SubroutineDefinition(sub3Impl, pt.TealType.none) subroutines = { subroutine1: {subroutine2, subroutine3}, @@ -141,17 +141,17 @@ def sub3Impl(a1, a2, a3): def test_spillLocalSlotsDuringRecursion_no_subroutines(): for version in (4, 5): - l1Label = LabelReference("l1") + l1Label = pt.LabelReference("l1") mainOps = [ - TealOp(None, Op.txn, "Fee"), - TealOp(None, Op.int, 0), - TealOp(None, Op.eq), - TealOp(None, Op.bz, l1Label), - TealOp(None, Op.int, 1), - TealOp(None, Op.return_), - TealLabel(None, l1Label), - TealOp(None, Op.int, 0), - TealOp(None, Op.return_), + pt.TealOp(None, pt.Op.txn, "Fee"), + pt.TealOp(None, pt.Op.int, 0), + pt.TealOp(None, pt.Op.eq), + pt.TealOp(None, pt.Op.bz, l1Label), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.return_), + pt.TealLabel(None, l1Label), + pt.TealOp(None, pt.Op.int, 0), + pt.TealOp(None, pt.Op.return_), ] subroutineMapping = {None: mainOps} @@ -165,47 +165,47 @@ def test_spillLocalSlotsDuringRecursion_no_subroutines(): ) assert mainOps == [ - TealOp(None, Op.txn, "Fee"), - TealOp(None, Op.int, 0), - TealOp(None, Op.eq), - TealOp(None, Op.bz, l1Label), - TealOp(None, Op.int, 1), - TealOp(None, Op.return_), - TealLabel(None, l1Label), - TealOp(None, Op.int, 0), - TealOp(None, Op.return_), + pt.TealOp(None, pt.Op.txn, "Fee"), + pt.TealOp(None, pt.Op.int, 0), + pt.TealOp(None, pt.Op.eq), + pt.TealOp(None, pt.Op.bz, l1Label), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.return_), + pt.TealLabel(None, l1Label), + pt.TealOp(None, pt.Op.int, 0), + pt.TealOp(None, pt.Op.return_), ] def test_spillLocalSlotsDuringRecursion_1_subroutine_no_recursion(): for version in (4, 5): - subroutine = SubroutineDefinition(lambda: None, TealType.uint64) + subroutine = pt.SubroutineDefinition(lambda: None, pt.TealType.uint64) - subroutineL1Label = LabelReference("l1") + subroutineL1Label = pt.LabelReference("l1") subroutineOps = [ - TealOp(None, Op.store, 0), - TealOp(None, Op.load, 0), - TealOp(None, Op.int, 0), - TealOp(None, Op.eq), - TealOp(None, Op.bnz, subroutineL1Label), - TealOp(None, Op.err), - TealLabel(None, subroutineL1Label), - TealOp(None, Op.int, 1), - TealOp(None, Op.retsub), + pt.TealOp(None, pt.Op.store, 0), + pt.TealOp(None, pt.Op.load, 0), + pt.TealOp(None, pt.Op.int, 0), + pt.TealOp(None, pt.Op.eq), + pt.TealOp(None, pt.Op.bnz, subroutineL1Label), + pt.TealOp(None, pt.Op.err), + pt.TealLabel(None, subroutineL1Label), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.retsub), ] - l1Label = LabelReference("l1") + l1Label = pt.LabelReference("l1") mainOps = [ - TealOp(None, Op.txn, "Fee"), - TealOp(None, Op.int, 0), - TealOp(None, Op.eq), - TealOp(None, Op.bz, l1Label), - TealOp(None, Op.int, 100), - TealOp(None, Op.callsub, subroutine), - TealOp(None, Op.return_), - TealLabel(None, l1Label), - TealOp(None, Op.int, 0), - TealOp(None, Op.return_), + pt.TealOp(None, pt.Op.txn, "Fee"), + pt.TealOp(None, pt.Op.int, 0), + pt.TealOp(None, pt.Op.eq), + pt.TealOp(None, pt.Op.bz, l1Label), + pt.TealOp(None, pt.Op.int, 100), + pt.TealOp(None, pt.Op.callsub, subroutine), + pt.TealOp(None, pt.Op.return_), + pt.TealLabel(None, l1Label), + pt.TealOp(None, pt.Op.int, 0), + pt.TealOp(None, pt.Op.return_), ] subroutineMapping = {None: mainOps, subroutine: subroutineOps} @@ -220,27 +220,27 @@ def test_spillLocalSlotsDuringRecursion_1_subroutine_no_recursion(): assert subroutineMapping == { None: [ - TealOp(None, Op.txn, "Fee"), - TealOp(None, Op.int, 0), - TealOp(None, Op.eq), - TealOp(None, Op.bz, l1Label), - TealOp(None, Op.int, 100), - TealOp(None, Op.callsub, subroutine), - TealOp(None, Op.return_), - TealLabel(None, l1Label), - TealOp(None, Op.int, 0), - TealOp(None, Op.return_), + pt.TealOp(None, pt.Op.txn, "Fee"), + pt.TealOp(None, pt.Op.int, 0), + pt.TealOp(None, pt.Op.eq), + pt.TealOp(None, pt.Op.bz, l1Label), + pt.TealOp(None, pt.Op.int, 100), + pt.TealOp(None, pt.Op.callsub, subroutine), + pt.TealOp(None, pt.Op.return_), + pt.TealLabel(None, l1Label), + pt.TealOp(None, pt.Op.int, 0), + pt.TealOp(None, pt.Op.return_), ], subroutine: [ - TealOp(None, Op.store, 0), - TealOp(None, Op.load, 0), - TealOp(None, Op.int, 0), - TealOp(None, Op.eq), - TealOp(None, Op.bnz, subroutineL1Label), - TealOp(None, Op.err), - TealLabel(None, subroutineL1Label), - TealOp(None, Op.int, 1), - TealOp(None, Op.retsub), + pt.TealOp(None, pt.Op.store, 0), + pt.TealOp(None, pt.Op.load, 0), + pt.TealOp(None, pt.Op.int, 0), + pt.TealOp(None, pt.Op.eq), + pt.TealOp(None, pt.Op.bnz, subroutineL1Label), + pt.TealOp(None, pt.Op.err), + pt.TealLabel(None, subroutineL1Label), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.retsub), ], } @@ -249,39 +249,39 @@ def test_spillLocalSlotsDuringRecursion_1_subroutine_recursion_v4(): def sub1Impl(a1): return None - subroutine = SubroutineDefinition(sub1Impl, TealType.uint64) + subroutine = pt.SubroutineDefinition(sub1Impl, pt.TealType.uint64) - subroutineL1Label = LabelReference("l1") + subroutineL1Label = pt.LabelReference("l1") subroutineOps = [ - TealOp(None, Op.store, 0), - TealOp(None, Op.load, 0), - TealOp(None, Op.int, 0), - TealOp(None, Op.eq), - TealOp(None, Op.bnz, subroutineL1Label), - TealOp(None, Op.load, 0), - TealOp(None, Op.int, 1), - TealOp(None, Op.minus), - TealOp(None, Op.callsub, subroutine), - TealOp(None, Op.int, 1), - TealOp(None, Op.add), - TealOp(None, Op.retsub), - TealLabel(None, subroutineL1Label), - TealOp(None, Op.int, 1), - TealOp(None, Op.retsub), + pt.TealOp(None, pt.Op.store, 0), + pt.TealOp(None, pt.Op.load, 0), + pt.TealOp(None, pt.Op.int, 0), + pt.TealOp(None, pt.Op.eq), + pt.TealOp(None, pt.Op.bnz, subroutineL1Label), + pt.TealOp(None, pt.Op.load, 0), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.minus), + pt.TealOp(None, pt.Op.callsub, subroutine), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.add), + pt.TealOp(None, pt.Op.retsub), + pt.TealLabel(None, subroutineL1Label), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.retsub), ] - l1Label = LabelReference("l1") + l1Label = pt.LabelReference("l1") mainOps = [ - TealOp(None, Op.txn, "Fee"), - TealOp(None, Op.int, 0), - TealOp(None, Op.eq), - TealOp(None, Op.bz, l1Label), - TealOp(None, Op.int, 100), - TealOp(None, Op.callsub, subroutine), - TealOp(None, Op.return_), - TealLabel(None, l1Label), - TealOp(None, Op.int, 0), - TealOp(None, Op.return_), + pt.TealOp(None, pt.Op.txn, "Fee"), + pt.TealOp(None, pt.Op.int, 0), + pt.TealOp(None, pt.Op.eq), + pt.TealOp(None, pt.Op.bz, l1Label), + pt.TealOp(None, pt.Op.int, 100), + pt.TealOp(None, pt.Op.callsub, subroutine), + pt.TealOp(None, pt.Op.return_), + pt.TealLabel(None, l1Label), + pt.TealOp(None, pt.Op.int, 0), + pt.TealOp(None, pt.Op.return_), ] subroutineMapping = {None: mainOps, subroutine: subroutineOps} @@ -294,39 +294,39 @@ def sub1Impl(a1): assert subroutineMapping == { None: [ - TealOp(None, Op.txn, "Fee"), - TealOp(None, Op.int, 0), - TealOp(None, Op.eq), - TealOp(None, Op.bz, l1Label), - TealOp(None, Op.int, 100), - TealOp(None, Op.callsub, subroutine), - TealOp(None, Op.return_), - TealLabel(None, l1Label), - TealOp(None, Op.int, 0), - TealOp(None, Op.return_), + pt.TealOp(None, pt.Op.txn, "Fee"), + pt.TealOp(None, pt.Op.int, 0), + pt.TealOp(None, pt.Op.eq), + pt.TealOp(None, pt.Op.bz, l1Label), + pt.TealOp(None, pt.Op.int, 100), + pt.TealOp(None, pt.Op.callsub, subroutine), + pt.TealOp(None, pt.Op.return_), + pt.TealLabel(None, l1Label), + pt.TealOp(None, pt.Op.int, 0), + pt.TealOp(None, pt.Op.return_), ], subroutine: [ - TealOp(None, Op.store, 0), - TealOp(None, Op.load, 0), - TealOp(None, Op.int, 0), - TealOp(None, Op.eq), - TealOp(None, Op.bnz, subroutineL1Label), - TealOp(None, Op.load, 0), - TealOp(None, Op.int, 1), - TealOp(None, Op.minus), - TealOp(None, Op.load, 0), - TealOp(None, Op.dig, 1), - TealOp(None, Op.callsub, subroutine), - TealOp(None, Op.swap), - TealOp(None, Op.store, 0), - TealOp(None, Op.swap), - TealOp(None, Op.pop), - TealOp(None, Op.int, 1), - TealOp(None, Op.add), - TealOp(None, Op.retsub), - TealLabel(None, subroutineL1Label), - TealOp(None, Op.int, 1), - TealOp(None, Op.retsub), + pt.TealOp(None, pt.Op.store, 0), + pt.TealOp(None, pt.Op.load, 0), + pt.TealOp(None, pt.Op.int, 0), + pt.TealOp(None, pt.Op.eq), + pt.TealOp(None, pt.Op.bnz, subroutineL1Label), + pt.TealOp(None, pt.Op.load, 0), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.minus), + pt.TealOp(None, pt.Op.load, 0), + pt.TealOp(None, pt.Op.dig, 1), + pt.TealOp(None, pt.Op.callsub, subroutine), + pt.TealOp(None, pt.Op.swap), + pt.TealOp(None, pt.Op.store, 0), + pt.TealOp(None, pt.Op.swap), + pt.TealOp(None, pt.Op.pop), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.add), + pt.TealOp(None, pt.Op.retsub), + pt.TealLabel(None, subroutineL1Label), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.retsub), ], } @@ -335,39 +335,39 @@ def test_spillLocalSlotsDuringRecursion_1_subroutine_recursion_v5(): def sub1Impl(a1): return None - subroutine = SubroutineDefinition(sub1Impl, TealType.uint64) + subroutine = pt.SubroutineDefinition(sub1Impl, pt.TealType.uint64) - subroutineL1Label = LabelReference("l1") + subroutineL1Label = pt.LabelReference("l1") subroutineOps = [ - TealOp(None, Op.store, 0), - TealOp(None, Op.load, 0), - TealOp(None, Op.int, 0), - TealOp(None, Op.eq), - TealOp(None, Op.bnz, subroutineL1Label), - TealOp(None, Op.load, 0), - TealOp(None, Op.int, 1), - TealOp(None, Op.minus), - TealOp(None, Op.callsub, subroutine), - TealOp(None, Op.int, 1), - TealOp(None, Op.add), - TealOp(None, Op.retsub), - TealLabel(None, subroutineL1Label), - TealOp(None, Op.int, 1), - TealOp(None, Op.retsub), + pt.TealOp(None, pt.Op.store, 0), + pt.TealOp(None, pt.Op.load, 0), + pt.TealOp(None, pt.Op.int, 0), + pt.TealOp(None, pt.Op.eq), + pt.TealOp(None, pt.Op.bnz, subroutineL1Label), + pt.TealOp(None, pt.Op.load, 0), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.minus), + pt.TealOp(None, pt.Op.callsub, subroutine), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.add), + pt.TealOp(None, pt.Op.retsub), + pt.TealLabel(None, subroutineL1Label), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.retsub), ] - l1Label = LabelReference("l1") + l1Label = pt.LabelReference("l1") mainOps = [ - TealOp(None, Op.txn, "Fee"), - TealOp(None, Op.int, 0), - TealOp(None, Op.eq), - TealOp(None, Op.bz, l1Label), - TealOp(None, Op.int, 100), - TealOp(None, Op.callsub, subroutine), - TealOp(None, Op.return_), - TealLabel(None, l1Label), - TealOp(None, Op.int, 0), - TealOp(None, Op.return_), + pt.TealOp(None, pt.Op.txn, "Fee"), + pt.TealOp(None, pt.Op.int, 0), + pt.TealOp(None, pt.Op.eq), + pt.TealOp(None, pt.Op.bz, l1Label), + pt.TealOp(None, pt.Op.int, 100), + pt.TealOp(None, pt.Op.callsub, subroutine), + pt.TealOp(None, pt.Op.return_), + pt.TealLabel(None, l1Label), + pt.TealOp(None, pt.Op.int, 0), + pt.TealOp(None, pt.Op.return_), ] subroutineMapping = {None: mainOps, subroutine: subroutineOps} @@ -380,37 +380,37 @@ def sub1Impl(a1): assert subroutineMapping == { None: [ - TealOp(None, Op.txn, "Fee"), - TealOp(None, Op.int, 0), - TealOp(None, Op.eq), - TealOp(None, Op.bz, l1Label), - TealOp(None, Op.int, 100), - TealOp(None, Op.callsub, subroutine), - TealOp(None, Op.return_), - TealLabel(None, l1Label), - TealOp(None, Op.int, 0), - TealOp(None, Op.return_), + pt.TealOp(None, pt.Op.txn, "Fee"), + pt.TealOp(None, pt.Op.int, 0), + pt.TealOp(None, pt.Op.eq), + pt.TealOp(None, pt.Op.bz, l1Label), + pt.TealOp(None, pt.Op.int, 100), + pt.TealOp(None, pt.Op.callsub, subroutine), + pt.TealOp(None, pt.Op.return_), + pt.TealLabel(None, l1Label), + pt.TealOp(None, pt.Op.int, 0), + pt.TealOp(None, pt.Op.return_), ], subroutine: [ - TealOp(None, Op.store, 0), - TealOp(None, Op.load, 0), - TealOp(None, Op.int, 0), - TealOp(None, Op.eq), - TealOp(None, Op.bnz, subroutineL1Label), - TealOp(None, Op.load, 0), - TealOp(None, Op.int, 1), - TealOp(None, Op.minus), - TealOp(None, Op.load, 0), - TealOp(None, Op.swap), - TealOp(None, Op.callsub, subroutine), - TealOp(None, Op.swap), - TealOp(None, Op.store, 0), - TealOp(None, Op.int, 1), - TealOp(None, Op.add), - TealOp(None, Op.retsub), - TealLabel(None, subroutineL1Label), - TealOp(None, Op.int, 1), - TealOp(None, Op.retsub), + pt.TealOp(None, pt.Op.store, 0), + pt.TealOp(None, pt.Op.load, 0), + pt.TealOp(None, pt.Op.int, 0), + pt.TealOp(None, pt.Op.eq), + pt.TealOp(None, pt.Op.bnz, subroutineL1Label), + pt.TealOp(None, pt.Op.load, 0), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.minus), + pt.TealOp(None, pt.Op.load, 0), + pt.TealOp(None, pt.Op.swap), + pt.TealOp(None, pt.Op.callsub, subroutine), + pt.TealOp(None, pt.Op.swap), + pt.TealOp(None, pt.Op.store, 0), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.add), + pt.TealOp(None, pt.Op.retsub), + pt.TealLabel(None, subroutineL1Label), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.retsub), ], } @@ -427,54 +427,54 @@ def sub2Impl(a1, a2): def sub3Impl(a1, a2, a3): return None - subroutine1 = SubroutineDefinition(sub1Impl, TealType.uint64) - subroutine2 = SubroutineDefinition(sub1Impl, TealType.uint64) - subroutine3 = SubroutineDefinition(sub1Impl, TealType.none) + subroutine1 = pt.SubroutineDefinition(sub1Impl, pt.TealType.uint64) + subroutine2 = pt.SubroutineDefinition(sub1Impl, pt.TealType.uint64) + subroutine3 = pt.SubroutineDefinition(sub1Impl, pt.TealType.none) - subroutine1L1Label = LabelReference("l1") + subroutine1L1Label = pt.LabelReference("l1") subroutine1Ops = [ - TealOp(None, Op.store, 0), - TealOp(None, Op.load, 0), - TealOp(None, Op.int, 0), - TealOp(None, Op.eq), - TealOp(None, Op.bnz, subroutine1L1Label), - TealOp(None, Op.err), - TealLabel(None, subroutine1L1Label), - TealOp(None, Op.int, 1), - TealOp(None, Op.callsub, subroutine3), - TealOp(None, Op.retsub), + pt.TealOp(None, pt.Op.store, 0), + pt.TealOp(None, pt.Op.load, 0), + pt.TealOp(None, pt.Op.int, 0), + pt.TealOp(None, pt.Op.eq), + pt.TealOp(None, pt.Op.bnz, subroutine1L1Label), + pt.TealOp(None, pt.Op.err), + pt.TealLabel(None, subroutine1L1Label), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.callsub, subroutine3), + pt.TealOp(None, pt.Op.retsub), ] - subroutine2L1Label = LabelReference("l1") + subroutine2L1Label = pt.LabelReference("l1") subroutine2Ops = [ - TealOp(None, Op.store, 1), - TealOp(None, Op.load, 1), - TealOp(None, Op.int, 0), - TealOp(None, Op.eq), - TealOp(None, Op.bnz, subroutine2L1Label), - TealOp(None, Op.err), - TealLabel(None, subroutine2L1Label), - TealOp(None, Op.int, 1), - TealOp(None, Op.retsub), + pt.TealOp(None, pt.Op.store, 1), + pt.TealOp(None, pt.Op.load, 1), + pt.TealOp(None, pt.Op.int, 0), + pt.TealOp(None, pt.Op.eq), + pt.TealOp(None, pt.Op.bnz, subroutine2L1Label), + pt.TealOp(None, pt.Op.err), + pt.TealLabel(None, subroutine2L1Label), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.retsub), ] subroutine3Ops = [ - TealOp(None, Op.retsub), + pt.TealOp(None, pt.Op.retsub), ] - l1Label = LabelReference("l1") + l1Label = pt.LabelReference("l1") mainOps = [ - TealOp(None, Op.txn, "Fee"), - TealOp(None, Op.int, 0), - TealOp(None, Op.eq), - TealOp(None, Op.bz, l1Label), - TealOp(None, Op.int, 100), - TealOp(None, Op.callsub, subroutine1), - TealOp(None, Op.return_), - TealLabel(None, l1Label), - TealOp(None, Op.int, 101), - TealOp(None, Op.callsub, subroutine2), - TealOp(None, Op.return_), + pt.TealOp(None, pt.Op.txn, "Fee"), + pt.TealOp(None, pt.Op.int, 0), + pt.TealOp(None, pt.Op.eq), + pt.TealOp(None, pt.Op.bz, l1Label), + pt.TealOp(None, pt.Op.int, 100), + pt.TealOp(None, pt.Op.callsub, subroutine1), + pt.TealOp(None, pt.Op.return_), + pt.TealLabel(None, l1Label), + pt.TealOp(None, pt.Op.int, 101), + pt.TealOp(None, pt.Op.callsub, subroutine2), + pt.TealOp(None, pt.Op.return_), ] subroutineMapping = { @@ -498,43 +498,43 @@ def sub3Impl(a1, a2, a3): assert subroutineMapping == { None: [ - TealOp(None, Op.txn, "Fee"), - TealOp(None, Op.int, 0), - TealOp(None, Op.eq), - TealOp(None, Op.bz, l1Label), - TealOp(None, Op.int, 100), - TealOp(None, Op.callsub, subroutine1), - TealOp(None, Op.return_), - TealLabel(None, l1Label), - TealOp(None, Op.int, 101), - TealOp(None, Op.callsub, subroutine2), - TealOp(None, Op.return_), + pt.TealOp(None, pt.Op.txn, "Fee"), + pt.TealOp(None, pt.Op.int, 0), + pt.TealOp(None, pt.Op.eq), + pt.TealOp(None, pt.Op.bz, l1Label), + pt.TealOp(None, pt.Op.int, 100), + pt.TealOp(None, pt.Op.callsub, subroutine1), + pt.TealOp(None, pt.Op.return_), + pt.TealLabel(None, l1Label), + pt.TealOp(None, pt.Op.int, 101), + pt.TealOp(None, pt.Op.callsub, subroutine2), + pt.TealOp(None, pt.Op.return_), ], subroutine1: [ - TealOp(None, Op.store, 0), - TealOp(None, Op.load, 0), - TealOp(None, Op.int, 0), - TealOp(None, Op.eq), - TealOp(None, Op.bnz, subroutine1L1Label), - TealOp(None, Op.err), - TealLabel(None, subroutine1L1Label), - TealOp(None, Op.int, 1), - TealOp(None, Op.callsub, subroutine3), - TealOp(None, Op.retsub), + pt.TealOp(None, pt.Op.store, 0), + pt.TealOp(None, pt.Op.load, 0), + pt.TealOp(None, pt.Op.int, 0), + pt.TealOp(None, pt.Op.eq), + pt.TealOp(None, pt.Op.bnz, subroutine1L1Label), + pt.TealOp(None, pt.Op.err), + pt.TealLabel(None, subroutine1L1Label), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.callsub, subroutine3), + pt.TealOp(None, pt.Op.retsub), ], subroutine2: [ - TealOp(None, Op.store, 1), - TealOp(None, Op.load, 1), - TealOp(None, Op.int, 0), - TealOp(None, Op.eq), - TealOp(None, Op.bnz, subroutine2L1Label), - TealOp(None, Op.err), - TealLabel(None, subroutine2L1Label), - TealOp(None, Op.int, 1), - TealOp(None, Op.retsub), + pt.TealOp(None, pt.Op.store, 1), + pt.TealOp(None, pt.Op.load, 1), + pt.TealOp(None, pt.Op.int, 0), + pt.TealOp(None, pt.Op.eq), + pt.TealOp(None, pt.Op.bnz, subroutine2L1Label), + pt.TealOp(None, pt.Op.err), + pt.TealLabel(None, subroutine2L1Label), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.retsub), ], subroutine3: [ - TealOp(None, Op.retsub), + pt.TealOp(None, pt.Op.retsub), ], } @@ -549,69 +549,69 @@ def sub2Impl(a1, a2): def sub3Impl(a1, a2, a3): return None - subroutine1 = SubroutineDefinition(sub1Impl, TealType.uint64) - subroutine2 = SubroutineDefinition(sub1Impl, TealType.uint64) - subroutine3 = SubroutineDefinition(sub1Impl, TealType.none) + subroutine1 = pt.SubroutineDefinition(sub1Impl, pt.TealType.uint64) + subroutine2 = pt.SubroutineDefinition(sub1Impl, pt.TealType.uint64) + subroutine3 = pt.SubroutineDefinition(sub1Impl, pt.TealType.none) - subroutine1L1Label = LabelReference("l1") + subroutine1L1Label = pt.LabelReference("l1") subroutine1Ops = [ - TealOp(None, Op.store, 0), - TealOp(None, Op.load, 0), - TealOp(None, Op.int, 0), - TealOp(None, Op.eq), - TealOp(None, Op.bnz, subroutine1L1Label), - TealOp(None, Op.load, 0), - TealOp(None, Op.int, 1), - TealOp(None, Op.minus), - TealOp(None, Op.callsub, subroutine1), - TealOp(None, Op.int, 1), - TealOp(None, Op.add), - TealOp(None, Op.retsub), - TealLabel(None, subroutine1L1Label), - TealOp(None, Op.load, 255), - TealOp(None, Op.retsub), + pt.TealOp(None, pt.Op.store, 0), + pt.TealOp(None, pt.Op.load, 0), + pt.TealOp(None, pt.Op.int, 0), + pt.TealOp(None, pt.Op.eq), + pt.TealOp(None, pt.Op.bnz, subroutine1L1Label), + pt.TealOp(None, pt.Op.load, 0), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.minus), + pt.TealOp(None, pt.Op.callsub, subroutine1), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.add), + pt.TealOp(None, pt.Op.retsub), + pt.TealLabel(None, subroutine1L1Label), + pt.TealOp(None, pt.Op.load, 255), + pt.TealOp(None, pt.Op.retsub), ] - subroutine2L1Label = LabelReference("l1") + subroutine2L1Label = pt.LabelReference("l1") subroutine2Ops = [ - TealOp(None, Op.store, 1), - TealOp(None, Op.load, 1), - TealOp(None, Op.int, 0), - TealOp(None, Op.eq), - TealOp(None, Op.bnz, subroutine2L1Label), - TealOp(None, Op.load, 0), - TealOp(None, Op.int, 1), - TealOp(None, Op.minus), - TealOp(None, Op.callsub, subroutine1), - TealOp(None, Op.int, 1), - TealOp(None, Op.add), - TealOp(None, Op.retsub), - TealLabel(None, subroutine2L1Label), - TealOp(None, Op.int, 1), - TealOp(None, Op.retsub), + pt.TealOp(None, pt.Op.store, 1), + pt.TealOp(None, pt.Op.load, 1), + pt.TealOp(None, pt.Op.int, 0), + pt.TealOp(None, pt.Op.eq), + pt.TealOp(None, pt.Op.bnz, subroutine2L1Label), + pt.TealOp(None, pt.Op.load, 0), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.minus), + pt.TealOp(None, pt.Op.callsub, subroutine1), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.add), + pt.TealOp(None, pt.Op.retsub), + pt.TealLabel(None, subroutine2L1Label), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.retsub), ] subroutine3Ops = [ - TealOp(None, Op.callsub, subroutine3), - TealOp(None, Op.retsub), + pt.TealOp(None, pt.Op.callsub, subroutine3), + pt.TealOp(None, pt.Op.retsub), ] - l1Label = LabelReference("l1") + l1Label = pt.LabelReference("l1") mainOps = [ - TealOp(None, Op.int, 1), - TealOp(None, Op.store, 255), - TealOp(None, Op.txn, "Fee"), - TealOp(None, Op.int, 0), - TealOp(None, Op.eq), - TealOp(None, Op.bz, l1Label), - TealOp(None, Op.int, 100), - TealOp(None, Op.callsub, subroutine1), - TealOp(None, Op.return_), - TealLabel(None, l1Label), - TealOp(None, Op.int, 101), - TealOp(None, Op.callsub, subroutine2), - TealOp(None, Op.return_), - TealOp(None, Op.callsub, subroutine3), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.store, 255), + pt.TealOp(None, pt.Op.txn, "Fee"), + pt.TealOp(None, pt.Op.int, 0), + pt.TealOp(None, pt.Op.eq), + pt.TealOp(None, pt.Op.bz, l1Label), + pt.TealOp(None, pt.Op.int, 100), + pt.TealOp(None, pt.Op.callsub, subroutine1), + pt.TealOp(None, pt.Op.return_), + pt.TealLabel(None, l1Label), + pt.TealOp(None, pt.Op.int, 101), + pt.TealOp(None, pt.Op.callsub, subroutine2), + pt.TealOp(None, pt.Op.return_), + pt.TealOp(None, pt.Op.callsub, subroutine3), ] subroutineMapping = { @@ -633,64 +633,64 @@ def sub3Impl(a1, a2, a3): assert subroutineMapping == { None: [ - TealOp(None, Op.int, 1), - TealOp(None, Op.store, 255), - TealOp(None, Op.txn, "Fee"), - TealOp(None, Op.int, 0), - TealOp(None, Op.eq), - TealOp(None, Op.bz, l1Label), - TealOp(None, Op.int, 100), - TealOp(None, Op.callsub, subroutine1), - TealOp(None, Op.return_), - TealLabel(None, l1Label), - TealOp(None, Op.int, 101), - TealOp(None, Op.callsub, subroutine2), - TealOp(None, Op.return_), - TealOp(None, Op.callsub, subroutine3), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.store, 255), + pt.TealOp(None, pt.Op.txn, "Fee"), + pt.TealOp(None, pt.Op.int, 0), + pt.TealOp(None, pt.Op.eq), + pt.TealOp(None, pt.Op.bz, l1Label), + pt.TealOp(None, pt.Op.int, 100), + pt.TealOp(None, pt.Op.callsub, subroutine1), + pt.TealOp(None, pt.Op.return_), + pt.TealLabel(None, l1Label), + pt.TealOp(None, pt.Op.int, 101), + pt.TealOp(None, pt.Op.callsub, subroutine2), + pt.TealOp(None, pt.Op.return_), + pt.TealOp(None, pt.Op.callsub, subroutine3), ], subroutine1: [ - TealOp(None, Op.store, 0), - TealOp(None, Op.load, 0), - TealOp(None, Op.int, 0), - TealOp(None, Op.eq), - TealOp(None, Op.bnz, subroutine1L1Label), - TealOp(None, Op.load, 0), - TealOp(None, Op.int, 1), - TealOp(None, Op.minus), - TealOp(None, Op.load, 0), - TealOp(None, Op.dig, 1), - TealOp(None, Op.callsub, subroutine1), - TealOp(None, Op.swap), - TealOp(None, Op.store, 0), - TealOp(None, Op.swap), - TealOp(None, Op.pop), - TealOp(None, Op.int, 1), - TealOp(None, Op.add), - TealOp(None, Op.retsub), - TealLabel(None, subroutine1L1Label), - TealOp(None, Op.load, 255), - TealOp(None, Op.retsub), + pt.TealOp(None, pt.Op.store, 0), + pt.TealOp(None, pt.Op.load, 0), + pt.TealOp(None, pt.Op.int, 0), + pt.TealOp(None, pt.Op.eq), + pt.TealOp(None, pt.Op.bnz, subroutine1L1Label), + pt.TealOp(None, pt.Op.load, 0), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.minus), + pt.TealOp(None, pt.Op.load, 0), + pt.TealOp(None, pt.Op.dig, 1), + pt.TealOp(None, pt.Op.callsub, subroutine1), + pt.TealOp(None, pt.Op.swap), + pt.TealOp(None, pt.Op.store, 0), + pt.TealOp(None, pt.Op.swap), + pt.TealOp(None, pt.Op.pop), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.add), + pt.TealOp(None, pt.Op.retsub), + pt.TealLabel(None, subroutine1L1Label), + pt.TealOp(None, pt.Op.load, 255), + pt.TealOp(None, pt.Op.retsub), ], subroutine2: [ - TealOp(None, Op.store, 1), - TealOp(None, Op.load, 1), - TealOp(None, Op.int, 0), - TealOp(None, Op.eq), - TealOp(None, Op.bnz, subroutine2L1Label), - TealOp(None, Op.load, 0), - TealOp(None, Op.int, 1), - TealOp(None, Op.minus), - TealOp(None, Op.callsub, subroutine1), - TealOp(None, Op.int, 1), - TealOp(None, Op.add), - TealOp(None, Op.retsub), - TealLabel(None, subroutine2L1Label), - TealOp(None, Op.int, 1), - TealOp(None, Op.retsub), + pt.TealOp(None, pt.Op.store, 1), + pt.TealOp(None, pt.Op.load, 1), + pt.TealOp(None, pt.Op.int, 0), + pt.TealOp(None, pt.Op.eq), + pt.TealOp(None, pt.Op.bnz, subroutine2L1Label), + pt.TealOp(None, pt.Op.load, 0), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.minus), + pt.TealOp(None, pt.Op.callsub, subroutine1), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.add), + pt.TealOp(None, pt.Op.retsub), + pt.TealLabel(None, subroutine2L1Label), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.retsub), ], subroutine3: [ - TealOp(None, Op.callsub, subroutine3), - TealOp(None, Op.retsub), + pt.TealOp(None, pt.Op.callsub, subroutine3), + pt.TealOp(None, pt.Op.retsub), ], } @@ -705,69 +705,69 @@ def sub2Impl(a1, a2): def sub3Impl(a1, a2, a3): return None - subroutine1 = SubroutineDefinition(sub1Impl, TealType.uint64) - subroutine2 = SubroutineDefinition(sub1Impl, TealType.uint64) - subroutine3 = SubroutineDefinition(sub1Impl, TealType.none) + subroutine1 = pt.SubroutineDefinition(sub1Impl, pt.TealType.uint64) + subroutine2 = pt.SubroutineDefinition(sub1Impl, pt.TealType.uint64) + subroutine3 = pt.SubroutineDefinition(sub1Impl, pt.TealType.none) - subroutine1L1Label = LabelReference("l1") + subroutine1L1Label = pt.LabelReference("l1") subroutine1Ops = [ - TealOp(None, Op.store, 0), - TealOp(None, Op.load, 0), - TealOp(None, Op.int, 0), - TealOp(None, Op.eq), - TealOp(None, Op.bnz, subroutine1L1Label), - TealOp(None, Op.load, 0), - TealOp(None, Op.int, 1), - TealOp(None, Op.minus), - TealOp(None, Op.callsub, subroutine1), - TealOp(None, Op.int, 1), - TealOp(None, Op.add), - TealOp(None, Op.retsub), - TealLabel(None, subroutine1L1Label), - TealOp(None, Op.load, 255), - TealOp(None, Op.retsub), + pt.TealOp(None, pt.Op.store, 0), + pt.TealOp(None, pt.Op.load, 0), + pt.TealOp(None, pt.Op.int, 0), + pt.TealOp(None, pt.Op.eq), + pt.TealOp(None, pt.Op.bnz, subroutine1L1Label), + pt.TealOp(None, pt.Op.load, 0), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.minus), + pt.TealOp(None, pt.Op.callsub, subroutine1), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.add), + pt.TealOp(None, pt.Op.retsub), + pt.TealLabel(None, subroutine1L1Label), + pt.TealOp(None, pt.Op.load, 255), + pt.TealOp(None, pt.Op.retsub), ] - subroutine2L1Label = LabelReference("l1") + subroutine2L1Label = pt.LabelReference("l1") subroutine2Ops = [ - TealOp(None, Op.store, 1), - TealOp(None, Op.load, 1), - TealOp(None, Op.int, 0), - TealOp(None, Op.eq), - TealOp(None, Op.bnz, subroutine2L1Label), - TealOp(None, Op.load, 0), - TealOp(None, Op.int, 1), - TealOp(None, Op.minus), - TealOp(None, Op.callsub, subroutine1), - TealOp(None, Op.int, 1), - TealOp(None, Op.add), - TealOp(None, Op.retsub), - TealLabel(None, subroutine2L1Label), - TealOp(None, Op.int, 1), - TealOp(None, Op.retsub), + pt.TealOp(None, pt.Op.store, 1), + pt.TealOp(None, pt.Op.load, 1), + pt.TealOp(None, pt.Op.int, 0), + pt.TealOp(None, pt.Op.eq), + pt.TealOp(None, pt.Op.bnz, subroutine2L1Label), + pt.TealOp(None, pt.Op.load, 0), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.minus), + pt.TealOp(None, pt.Op.callsub, subroutine1), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.add), + pt.TealOp(None, pt.Op.retsub), + pt.TealLabel(None, subroutine2L1Label), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.retsub), ] subroutine3Ops = [ - TealOp(None, Op.callsub, subroutine3), - TealOp(None, Op.retsub), + pt.TealOp(None, pt.Op.callsub, subroutine3), + pt.TealOp(None, pt.Op.retsub), ] - l1Label = LabelReference("l1") + l1Label = pt.LabelReference("l1") mainOps = [ - TealOp(None, Op.int, 1), - TealOp(None, Op.store, 255), - TealOp(None, Op.txn, "Fee"), - TealOp(None, Op.int, 0), - TealOp(None, Op.eq), - TealOp(None, Op.bz, l1Label), - TealOp(None, Op.int, 100), - TealOp(None, Op.callsub, subroutine1), - TealOp(None, Op.return_), - TealLabel(None, l1Label), - TealOp(None, Op.int, 101), - TealOp(None, Op.callsub, subroutine2), - TealOp(None, Op.return_), - TealOp(None, Op.callsub, subroutine3), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.store, 255), + pt.TealOp(None, pt.Op.txn, "Fee"), + pt.TealOp(None, pt.Op.int, 0), + pt.TealOp(None, pt.Op.eq), + pt.TealOp(None, pt.Op.bz, l1Label), + pt.TealOp(None, pt.Op.int, 100), + pt.TealOp(None, pt.Op.callsub, subroutine1), + pt.TealOp(None, pt.Op.return_), + pt.TealLabel(None, l1Label), + pt.TealOp(None, pt.Op.int, 101), + pt.TealOp(None, pt.Op.callsub, subroutine2), + pt.TealOp(None, pt.Op.return_), + pt.TealOp(None, pt.Op.callsub, subroutine3), ] subroutineMapping = { @@ -789,62 +789,62 @@ def sub3Impl(a1, a2, a3): assert subroutineMapping == { None: [ - TealOp(None, Op.int, 1), - TealOp(None, Op.store, 255), - TealOp(None, Op.txn, "Fee"), - TealOp(None, Op.int, 0), - TealOp(None, Op.eq), - TealOp(None, Op.bz, l1Label), - TealOp(None, Op.int, 100), - TealOp(None, Op.callsub, subroutine1), - TealOp(None, Op.return_), - TealLabel(None, l1Label), - TealOp(None, Op.int, 101), - TealOp(None, Op.callsub, subroutine2), - TealOp(None, Op.return_), - TealOp(None, Op.callsub, subroutine3), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.store, 255), + pt.TealOp(None, pt.Op.txn, "Fee"), + pt.TealOp(None, pt.Op.int, 0), + pt.TealOp(None, pt.Op.eq), + pt.TealOp(None, pt.Op.bz, l1Label), + pt.TealOp(None, pt.Op.int, 100), + pt.TealOp(None, pt.Op.callsub, subroutine1), + pt.TealOp(None, pt.Op.return_), + pt.TealLabel(None, l1Label), + pt.TealOp(None, pt.Op.int, 101), + pt.TealOp(None, pt.Op.callsub, subroutine2), + pt.TealOp(None, pt.Op.return_), + pt.TealOp(None, pt.Op.callsub, subroutine3), ], subroutine1: [ - TealOp(None, Op.store, 0), - TealOp(None, Op.load, 0), - TealOp(None, Op.int, 0), - TealOp(None, Op.eq), - TealOp(None, Op.bnz, subroutine1L1Label), - TealOp(None, Op.load, 0), - TealOp(None, Op.int, 1), - TealOp(None, Op.minus), - TealOp(None, Op.load, 0), - TealOp(None, Op.swap), - TealOp(None, Op.callsub, subroutine1), - TealOp(None, Op.swap), - TealOp(None, Op.store, 0), - TealOp(None, Op.int, 1), - TealOp(None, Op.add), - TealOp(None, Op.retsub), - TealLabel(None, subroutine1L1Label), - TealOp(None, Op.load, 255), - TealOp(None, Op.retsub), + pt.TealOp(None, pt.Op.store, 0), + pt.TealOp(None, pt.Op.load, 0), + pt.TealOp(None, pt.Op.int, 0), + pt.TealOp(None, pt.Op.eq), + pt.TealOp(None, pt.Op.bnz, subroutine1L1Label), + pt.TealOp(None, pt.Op.load, 0), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.minus), + pt.TealOp(None, pt.Op.load, 0), + pt.TealOp(None, pt.Op.swap), + pt.TealOp(None, pt.Op.callsub, subroutine1), + pt.TealOp(None, pt.Op.swap), + pt.TealOp(None, pt.Op.store, 0), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.add), + pt.TealOp(None, pt.Op.retsub), + pt.TealLabel(None, subroutine1L1Label), + pt.TealOp(None, pt.Op.load, 255), + pt.TealOp(None, pt.Op.retsub), ], subroutine2: [ - TealOp(None, Op.store, 1), - TealOp(None, Op.load, 1), - TealOp(None, Op.int, 0), - TealOp(None, Op.eq), - TealOp(None, Op.bnz, subroutine2L1Label), - TealOp(None, Op.load, 0), - TealOp(None, Op.int, 1), - TealOp(None, Op.minus), - TealOp(None, Op.callsub, subroutine1), - TealOp(None, Op.int, 1), - TealOp(None, Op.add), - TealOp(None, Op.retsub), - TealLabel(None, subroutine2L1Label), - TealOp(None, Op.int, 1), - TealOp(None, Op.retsub), + pt.TealOp(None, pt.Op.store, 1), + pt.TealOp(None, pt.Op.load, 1), + pt.TealOp(None, pt.Op.int, 0), + pt.TealOp(None, pt.Op.eq), + pt.TealOp(None, pt.Op.bnz, subroutine2L1Label), + pt.TealOp(None, pt.Op.load, 0), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.minus), + pt.TealOp(None, pt.Op.callsub, subroutine1), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.add), + pt.TealOp(None, pt.Op.retsub), + pt.TealLabel(None, subroutine2L1Label), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.retsub), ], subroutine3: [ - TealOp(None, Op.callsub, subroutine3), - TealOp(None, Op.retsub), + pt.TealOp(None, pt.Op.callsub, subroutine3), + pt.TealOp(None, pt.Op.retsub), ], } @@ -853,26 +853,26 @@ def test_spillLocalSlotsDuringRecursion_recursive_many_args_no_return_v4(): def subImpl(a1, a2, a3): return None - subroutine = SubroutineDefinition(subImpl, TealType.none) + subroutine = pt.SubroutineDefinition(subImpl, pt.TealType.none) subroutineOps = [ - TealOp(None, Op.store, 0), - TealOp(None, Op.store, 1), - TealOp(None, Op.store, 2), - TealOp(None, Op.int, 1), - TealOp(None, Op.int, 2), - TealOp(None, Op.int, 3), - TealOp(None, Op.callsub, subroutine), - TealOp(None, Op.retsub), + pt.TealOp(None, pt.Op.store, 0), + pt.TealOp(None, pt.Op.store, 1), + pt.TealOp(None, pt.Op.store, 2), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.int, 2), + pt.TealOp(None, pt.Op.int, 3), + pt.TealOp(None, pt.Op.callsub, subroutine), + pt.TealOp(None, pt.Op.retsub), ] mainOps = [ - TealOp(None, Op.int, 1), - TealOp(None, Op.int, 2), - TealOp(None, Op.int, 3), - TealOp(None, Op.callsub, subroutine), - TealOp(None, Op.int, 1), - TealOp(None, Op.return_), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.int, 2), + pt.TealOp(None, pt.Op.int, 3), + pt.TealOp(None, pt.Op.callsub, subroutine), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.return_), ] subroutineMapping = { @@ -890,34 +890,34 @@ def subImpl(a1, a2, a3): assert subroutineMapping == { None: [ - TealOp(None, Op.int, 1), - TealOp(None, Op.int, 2), - TealOp(None, Op.int, 3), - TealOp(None, Op.callsub, subroutine), - TealOp(None, Op.int, 1), - TealOp(None, Op.return_), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.int, 2), + pt.TealOp(None, pt.Op.int, 3), + pt.TealOp(None, pt.Op.callsub, subroutine), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.return_), ], subroutine: [ - TealOp(None, Op.store, 0), - TealOp(None, Op.store, 1), - TealOp(None, Op.store, 2), - TealOp(None, Op.int, 1), - TealOp(None, Op.int, 2), - TealOp(None, Op.int, 3), - TealOp(None, Op.load, 0), - TealOp(None, Op.load, 1), - TealOp(None, Op.load, 2), - TealOp(None, Op.dig, 5), - TealOp(None, Op.dig, 5), - TealOp(None, Op.dig, 5), - TealOp(None, Op.callsub, subroutine), - TealOp(None, Op.store, 2), - TealOp(None, Op.store, 1), - TealOp(None, Op.store, 0), - TealOp(None, Op.pop), - TealOp(None, Op.pop), - TealOp(None, Op.pop), - TealOp(None, Op.retsub), + pt.TealOp(None, pt.Op.store, 0), + pt.TealOp(None, pt.Op.store, 1), + pt.TealOp(None, pt.Op.store, 2), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.int, 2), + pt.TealOp(None, pt.Op.int, 3), + pt.TealOp(None, pt.Op.load, 0), + pt.TealOp(None, pt.Op.load, 1), + pt.TealOp(None, pt.Op.load, 2), + pt.TealOp(None, pt.Op.dig, 5), + pt.TealOp(None, pt.Op.dig, 5), + pt.TealOp(None, pt.Op.dig, 5), + pt.TealOp(None, pt.Op.callsub, subroutine), + pt.TealOp(None, pt.Op.store, 2), + pt.TealOp(None, pt.Op.store, 1), + pt.TealOp(None, pt.Op.store, 0), + pt.TealOp(None, pt.Op.pop), + pt.TealOp(None, pt.Op.pop), + pt.TealOp(None, pt.Op.pop), + pt.TealOp(None, pt.Op.retsub), ], } @@ -926,26 +926,26 @@ def test_spillLocalSlotsDuringRecursion_recursive_many_args_no_return_v5(): def subImpl(a1, a2, a3): return None - subroutine = SubroutineDefinition(subImpl, TealType.none) + subroutine = pt.SubroutineDefinition(subImpl, pt.TealType.none) subroutineOps = [ - TealOp(None, Op.store, 0), - TealOp(None, Op.store, 1), - TealOp(None, Op.store, 2), - TealOp(None, Op.int, 1), - TealOp(None, Op.int, 2), - TealOp(None, Op.int, 3), - TealOp(None, Op.callsub, subroutine), - TealOp(None, Op.retsub), + pt.TealOp(None, pt.Op.store, 0), + pt.TealOp(None, pt.Op.store, 1), + pt.TealOp(None, pt.Op.store, 2), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.int, 2), + pt.TealOp(None, pt.Op.int, 3), + pt.TealOp(None, pt.Op.callsub, subroutine), + pt.TealOp(None, pt.Op.retsub), ] mainOps = [ - TealOp(None, Op.int, 1), - TealOp(None, Op.int, 2), - TealOp(None, Op.int, 3), - TealOp(None, Op.callsub, subroutine), - TealOp(None, Op.int, 1), - TealOp(None, Op.return_), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.int, 2), + pt.TealOp(None, pt.Op.int, 3), + pt.TealOp(None, pt.Op.callsub, subroutine), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.return_), ] subroutineMapping = { @@ -963,31 +963,31 @@ def subImpl(a1, a2, a3): assert subroutineMapping == { None: [ - TealOp(None, Op.int, 1), - TealOp(None, Op.int, 2), - TealOp(None, Op.int, 3), - TealOp(None, Op.callsub, subroutine), - TealOp(None, Op.int, 1), - TealOp(None, Op.return_), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.int, 2), + pt.TealOp(None, pt.Op.int, 3), + pt.TealOp(None, pt.Op.callsub, subroutine), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.return_), ], subroutine: [ - TealOp(None, Op.store, 0), - TealOp(None, Op.store, 1), - TealOp(None, Op.store, 2), - TealOp(None, Op.int, 1), - TealOp(None, Op.int, 2), - TealOp(None, Op.int, 3), - TealOp(None, Op.load, 0), - TealOp(None, Op.load, 1), - TealOp(None, Op.load, 2), - TealOp(None, Op.uncover, 5), - TealOp(None, Op.uncover, 5), - TealOp(None, Op.uncover, 5), - TealOp(None, Op.callsub, subroutine), - TealOp(None, Op.store, 2), - TealOp(None, Op.store, 1), - TealOp(None, Op.store, 0), - TealOp(None, Op.retsub), + pt.TealOp(None, pt.Op.store, 0), + pt.TealOp(None, pt.Op.store, 1), + pt.TealOp(None, pt.Op.store, 2), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.int, 2), + pt.TealOp(None, pt.Op.int, 3), + pt.TealOp(None, pt.Op.load, 0), + pt.TealOp(None, pt.Op.load, 1), + pt.TealOp(None, pt.Op.load, 2), + pt.TealOp(None, pt.Op.uncover, 5), + pt.TealOp(None, pt.Op.uncover, 5), + pt.TealOp(None, pt.Op.uncover, 5), + pt.TealOp(None, pt.Op.callsub, subroutine), + pt.TealOp(None, pt.Op.store, 2), + pt.TealOp(None, pt.Op.store, 1), + pt.TealOp(None, pt.Op.store, 0), + pt.TealOp(None, pt.Op.retsub), ], } @@ -996,25 +996,25 @@ def test_spillLocalSlotsDuringRecursion_recursive_many_args_return_v4(): def subImpl(a1, a2, a3): return None - subroutine = SubroutineDefinition(subImpl, TealType.uint64) + subroutine = pt.SubroutineDefinition(subImpl, pt.TealType.uint64) subroutineOps = [ - TealOp(None, Op.store, 0), - TealOp(None, Op.store, 1), - TealOp(None, Op.store, 2), - TealOp(None, Op.int, 1), - TealOp(None, Op.int, 2), - TealOp(None, Op.int, 3), - TealOp(None, Op.callsub, subroutine), - TealOp(None, Op.retsub), + pt.TealOp(None, pt.Op.store, 0), + pt.TealOp(None, pt.Op.store, 1), + pt.TealOp(None, pt.Op.store, 2), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.int, 2), + pt.TealOp(None, pt.Op.int, 3), + pt.TealOp(None, pt.Op.callsub, subroutine), + pt.TealOp(None, pt.Op.retsub), ] mainOps = [ - TealOp(None, Op.int, 1), - TealOp(None, Op.int, 2), - TealOp(None, Op.int, 3), - TealOp(None, Op.callsub, subroutine), - TealOp(None, Op.return_), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.int, 2), + pt.TealOp(None, pt.Op.int, 3), + pt.TealOp(None, pt.Op.callsub, subroutine), + pt.TealOp(None, pt.Op.return_), ] subroutineMapping = { @@ -1032,39 +1032,39 @@ def subImpl(a1, a2, a3): assert subroutineMapping == { None: [ - TealOp(None, Op.int, 1), - TealOp(None, Op.int, 2), - TealOp(None, Op.int, 3), - TealOp(None, Op.callsub, subroutine), - TealOp(None, Op.return_), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.int, 2), + pt.TealOp(None, pt.Op.int, 3), + pt.TealOp(None, pt.Op.callsub, subroutine), + pt.TealOp(None, pt.Op.return_), ], subroutine: [ - TealOp(None, Op.store, 0), - TealOp(None, Op.store, 1), - TealOp(None, Op.store, 2), - TealOp(None, Op.int, 1), - TealOp(None, Op.int, 2), - TealOp(None, Op.int, 3), - TealOp(None, Op.load, 0), - TealOp(None, Op.load, 1), - TealOp(None, Op.load, 2), - TealOp(None, Op.dig, 5), - TealOp(None, Op.dig, 5), - TealOp(None, Op.dig, 5), - TealOp(None, Op.callsub, subroutine), - TealOp(None, Op.store, 0), - TealOp(None, Op.store, 2), - TealOp(None, Op.store, 1), - TealOp(None, Op.load, 0), - TealOp(None, Op.swap), - TealOp(None, Op.store, 0), - TealOp(None, Op.swap), - TealOp(None, Op.pop), - TealOp(None, Op.swap), - TealOp(None, Op.pop), - TealOp(None, Op.swap), - TealOp(None, Op.pop), - TealOp(None, Op.retsub), + pt.TealOp(None, pt.Op.store, 0), + pt.TealOp(None, pt.Op.store, 1), + pt.TealOp(None, pt.Op.store, 2), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.int, 2), + pt.TealOp(None, pt.Op.int, 3), + pt.TealOp(None, pt.Op.load, 0), + pt.TealOp(None, pt.Op.load, 1), + pt.TealOp(None, pt.Op.load, 2), + pt.TealOp(None, pt.Op.dig, 5), + pt.TealOp(None, pt.Op.dig, 5), + pt.TealOp(None, pt.Op.dig, 5), + pt.TealOp(None, pt.Op.callsub, subroutine), + pt.TealOp(None, pt.Op.store, 0), + pt.TealOp(None, pt.Op.store, 2), + pt.TealOp(None, pt.Op.store, 1), + pt.TealOp(None, pt.Op.load, 0), + pt.TealOp(None, pt.Op.swap), + pt.TealOp(None, pt.Op.store, 0), + pt.TealOp(None, pt.Op.swap), + pt.TealOp(None, pt.Op.pop), + pt.TealOp(None, pt.Op.swap), + pt.TealOp(None, pt.Op.pop), + pt.TealOp(None, pt.Op.swap), + pt.TealOp(None, pt.Op.pop), + pt.TealOp(None, pt.Op.retsub), ], } @@ -1073,25 +1073,25 @@ def test_spillLocalSlotsDuringRecursion_recursive_many_args_return_v5(): def subImpl(a1, a2, a3): return None - subroutine = SubroutineDefinition(subImpl, TealType.uint64) + subroutine = pt.SubroutineDefinition(subImpl, pt.TealType.uint64) subroutineOps = [ - TealOp(None, Op.store, 0), - TealOp(None, Op.store, 1), - TealOp(None, Op.store, 2), - TealOp(None, Op.int, 1), - TealOp(None, Op.int, 2), - TealOp(None, Op.int, 3), - TealOp(None, Op.callsub, subroutine), - TealOp(None, Op.retsub), + pt.TealOp(None, pt.Op.store, 0), + pt.TealOp(None, pt.Op.store, 1), + pt.TealOp(None, pt.Op.store, 2), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.int, 2), + pt.TealOp(None, pt.Op.int, 3), + pt.TealOp(None, pt.Op.callsub, subroutine), + pt.TealOp(None, pt.Op.retsub), ] mainOps = [ - TealOp(None, Op.int, 1), - TealOp(None, Op.int, 2), - TealOp(None, Op.int, 3), - TealOp(None, Op.callsub, subroutine), - TealOp(None, Op.return_), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.int, 2), + pt.TealOp(None, pt.Op.int, 3), + pt.TealOp(None, pt.Op.callsub, subroutine), + pt.TealOp(None, pt.Op.return_), ] subroutineMapping = { @@ -1109,31 +1109,31 @@ def subImpl(a1, a2, a3): assert subroutineMapping == { None: [ - TealOp(None, Op.int, 1), - TealOp(None, Op.int, 2), - TealOp(None, Op.int, 3), - TealOp(None, Op.callsub, subroutine), - TealOp(None, Op.return_), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.int, 2), + pt.TealOp(None, pt.Op.int, 3), + pt.TealOp(None, pt.Op.callsub, subroutine), + pt.TealOp(None, pt.Op.return_), ], subroutine: [ - TealOp(None, Op.store, 0), - TealOp(None, Op.store, 1), - TealOp(None, Op.store, 2), - TealOp(None, Op.int, 1), - TealOp(None, Op.int, 2), - TealOp(None, Op.int, 3), - TealOp(None, Op.load, 0), - TealOp(None, Op.load, 1), - TealOp(None, Op.load, 2), - TealOp(None, Op.uncover, 5), - TealOp(None, Op.uncover, 5), - TealOp(None, Op.uncover, 5), - TealOp(None, Op.callsub, subroutine), - TealOp(None, Op.cover, 3), - TealOp(None, Op.store, 2), - TealOp(None, Op.store, 1), - TealOp(None, Op.store, 0), - TealOp(None, Op.retsub), + pt.TealOp(None, pt.Op.store, 0), + pt.TealOp(None, pt.Op.store, 1), + pt.TealOp(None, pt.Op.store, 2), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.int, 2), + pt.TealOp(None, pt.Op.int, 3), + pt.TealOp(None, pt.Op.load, 0), + pt.TealOp(None, pt.Op.load, 1), + pt.TealOp(None, pt.Op.load, 2), + pt.TealOp(None, pt.Op.uncover, 5), + pt.TealOp(None, pt.Op.uncover, 5), + pt.TealOp(None, pt.Op.uncover, 5), + pt.TealOp(None, pt.Op.callsub, subroutine), + pt.TealOp(None, pt.Op.cover, 3), + pt.TealOp(None, pt.Op.store, 2), + pt.TealOp(None, pt.Op.store, 1), + pt.TealOp(None, pt.Op.store, 0), + pt.TealOp(None, pt.Op.retsub), ], } @@ -1142,25 +1142,25 @@ def test_spillLocalSlotsDuringRecursion_recursive_more_args_than_slots_v5(): def subImpl(a1, a2, a3): return None - subroutine = SubroutineDefinition(subImpl, TealType.uint64) + subroutine = pt.SubroutineDefinition(subImpl, pt.TealType.uint64) subroutineOps = [ - TealOp(None, Op.store, 0), - TealOp(None, Op.store, 1), - TealOp(None, Op.pop), - TealOp(None, Op.int, 1), - TealOp(None, Op.int, 2), - TealOp(None, Op.int, 3), - TealOp(None, Op.callsub, subroutine), - TealOp(None, Op.retsub), + pt.TealOp(None, pt.Op.store, 0), + pt.TealOp(None, pt.Op.store, 1), + pt.TealOp(None, pt.Op.pop), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.int, 2), + pt.TealOp(None, pt.Op.int, 3), + pt.TealOp(None, pt.Op.callsub, subroutine), + pt.TealOp(None, pt.Op.retsub), ] mainOps = [ - TealOp(None, Op.int, 1), - TealOp(None, Op.int, 2), - TealOp(None, Op.int, 3), - TealOp(None, Op.callsub, subroutine), - TealOp(None, Op.return_), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.int, 2), + pt.TealOp(None, pt.Op.int, 3), + pt.TealOp(None, pt.Op.callsub, subroutine), + pt.TealOp(None, pt.Op.return_), ] subroutineMapping = { @@ -1178,28 +1178,28 @@ def subImpl(a1, a2, a3): assert subroutineMapping == { None: [ - TealOp(None, Op.int, 1), - TealOp(None, Op.int, 2), - TealOp(None, Op.int, 3), - TealOp(None, Op.callsub, subroutine), - TealOp(None, Op.return_), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.int, 2), + pt.TealOp(None, pt.Op.int, 3), + pt.TealOp(None, pt.Op.callsub, subroutine), + pt.TealOp(None, pt.Op.return_), ], subroutine: [ - TealOp(None, Op.store, 0), - TealOp(None, Op.store, 1), - TealOp(None, Op.pop), - TealOp(None, Op.int, 1), - TealOp(None, Op.int, 2), - TealOp(None, Op.int, 3), - TealOp(None, Op.load, 0), - TealOp(None, Op.cover, 3), - TealOp(None, Op.load, 1), - TealOp(None, Op.cover, 3), - TealOp(None, Op.callsub, subroutine), - TealOp(None, Op.cover, 2), - TealOp(None, Op.store, 1), - TealOp(None, Op.store, 0), - TealOp(None, Op.retsub), + pt.TealOp(None, pt.Op.store, 0), + pt.TealOp(None, pt.Op.store, 1), + pt.TealOp(None, pt.Op.pop), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.int, 2), + pt.TealOp(None, pt.Op.int, 3), + pt.TealOp(None, pt.Op.load, 0), + pt.TealOp(None, pt.Op.cover, 3), + pt.TealOp(None, pt.Op.load, 1), + pt.TealOp(None, pt.Op.cover, 3), + pt.TealOp(None, pt.Op.callsub, subroutine), + pt.TealOp(None, pt.Op.cover, 2), + pt.TealOp(None, pt.Op.store, 1), + pt.TealOp(None, pt.Op.store, 0), + pt.TealOp(None, pt.Op.retsub), ], } @@ -1208,27 +1208,27 @@ def test_spillLocalSlotsDuringRecursion_recursive_more_slots_than_args_v5(): def subImpl(a1, a2, a3): return None - subroutine = SubroutineDefinition(subImpl, TealType.uint64) + subroutine = pt.SubroutineDefinition(subImpl, pt.TealType.uint64) subroutineOps = [ - TealOp(None, Op.store, 0), - TealOp(None, Op.store, 1), - TealOp(None, Op.store, 2), - TealOp(None, Op.int, 10), - TealOp(None, Op.store, 3), - TealOp(None, Op.int, 1), - TealOp(None, Op.int, 2), - TealOp(None, Op.int, 3), - TealOp(None, Op.callsub, subroutine), - TealOp(None, Op.retsub), + pt.TealOp(None, pt.Op.store, 0), + pt.TealOp(None, pt.Op.store, 1), + pt.TealOp(None, pt.Op.store, 2), + pt.TealOp(None, pt.Op.int, 10), + pt.TealOp(None, pt.Op.store, 3), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.int, 2), + pt.TealOp(None, pt.Op.int, 3), + pt.TealOp(None, pt.Op.callsub, subroutine), + pt.TealOp(None, pt.Op.retsub), ] mainOps = [ - TealOp(None, Op.int, 1), - TealOp(None, Op.int, 2), - TealOp(None, Op.int, 3), - TealOp(None, Op.callsub, subroutine), - TealOp(None, Op.return_), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.int, 2), + pt.TealOp(None, pt.Op.int, 3), + pt.TealOp(None, pt.Op.callsub, subroutine), + pt.TealOp(None, pt.Op.return_), ] subroutineMapping = { @@ -1246,35 +1246,35 @@ def subImpl(a1, a2, a3): assert subroutineMapping == { None: [ - TealOp(None, Op.int, 1), - TealOp(None, Op.int, 2), - TealOp(None, Op.int, 3), - TealOp(None, Op.callsub, subroutine), - TealOp(None, Op.return_), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.int, 2), + pt.TealOp(None, pt.Op.int, 3), + pt.TealOp(None, pt.Op.callsub, subroutine), + pt.TealOp(None, pt.Op.return_), ], subroutine: [ - TealOp(None, Op.store, 0), - TealOp(None, Op.store, 1), - TealOp(None, Op.store, 2), - TealOp(None, Op.int, 10), - TealOp(None, Op.store, 3), - TealOp(None, Op.int, 1), - TealOp(None, Op.int, 2), - TealOp(None, Op.int, 3), - TealOp(None, Op.load, 0), - TealOp(None, Op.load, 1), - TealOp(None, Op.load, 2), - TealOp(None, Op.load, 3), - TealOp(None, Op.uncover, 6), - TealOp(None, Op.uncover, 6), - TealOp(None, Op.uncover, 6), - TealOp(None, Op.callsub, subroutine), - TealOp(None, Op.cover, 4), - TealOp(None, Op.store, 3), - TealOp(None, Op.store, 2), - TealOp(None, Op.store, 1), - TealOp(None, Op.store, 0), - TealOp(None, Op.retsub), + pt.TealOp(None, pt.Op.store, 0), + pt.TealOp(None, pt.Op.store, 1), + pt.TealOp(None, pt.Op.store, 2), + pt.TealOp(None, pt.Op.int, 10), + pt.TealOp(None, pt.Op.store, 3), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.int, 2), + pt.TealOp(None, pt.Op.int, 3), + pt.TealOp(None, pt.Op.load, 0), + pt.TealOp(None, pt.Op.load, 1), + pt.TealOp(None, pt.Op.load, 2), + pt.TealOp(None, pt.Op.load, 3), + pt.TealOp(None, pt.Op.uncover, 6), + pt.TealOp(None, pt.Op.uncover, 6), + pt.TealOp(None, pt.Op.uncover, 6), + pt.TealOp(None, pt.Op.callsub, subroutine), + pt.TealOp(None, pt.Op.cover, 4), + pt.TealOp(None, pt.Op.store, 3), + pt.TealOp(None, pt.Op.store, 2), + pt.TealOp(None, pt.Op.store, 1), + pt.TealOp(None, pt.Op.store, 0), + pt.TealOp(None, pt.Op.retsub), ], } @@ -1285,60 +1285,60 @@ def test_spillLocalSlotsDuringRecursion_recursive_with_scratchvar(): def sub1Impl(a1): return None - def sub2Impl(a1, a2: ScratchVar): + def sub2Impl(a1, a2: pt.ScratchVar): return None def sub3Impl(a1, a2, a3): return None - subroutine1 = SubroutineDefinition(sub1Impl, TealType.uint64) - subroutine2 = SubroutineDefinition(sub2Impl, TealType.uint64) - subroutine3 = SubroutineDefinition(sub3Impl, TealType.none) + subroutine1 = pt.SubroutineDefinition(sub1Impl, pt.TealType.uint64) + subroutine2 = pt.SubroutineDefinition(sub2Impl, pt.TealType.uint64) + subroutine3 = pt.SubroutineDefinition(sub3Impl, pt.TealType.none) - subroutine1L1Label = LabelReference("l1") + subroutine1L1Label = pt.LabelReference("l1") subroutine1Ops = [ - TealOp(None, Op.store, 0), - TealOp(None, Op.load, 0), - TealOp(None, Op.int, 0), - TealOp(None, Op.eq), - TealOp(None, Op.bnz, subroutine1L1Label), - TealOp(None, Op.err), - TealLabel(None, subroutine1L1Label), - TealOp(None, Op.int, 1), - TealOp(None, Op.callsub, subroutine3), - TealOp(None, Op.retsub), + pt.TealOp(None, pt.Op.store, 0), + pt.TealOp(None, pt.Op.load, 0), + pt.TealOp(None, pt.Op.int, 0), + pt.TealOp(None, pt.Op.eq), + pt.TealOp(None, pt.Op.bnz, subroutine1L1Label), + pt.TealOp(None, pt.Op.err), + pt.TealLabel(None, subroutine1L1Label), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.callsub, subroutine3), + pt.TealOp(None, pt.Op.retsub), ] - subroutine2L1Label = LabelReference("l1") + subroutine2L1Label = pt.LabelReference("l1") subroutine2Ops = [ - TealOp(None, Op.store, 1), - TealOp(None, Op.load, 1), - TealOp(None, Op.int, 0), - TealOp(None, Op.eq), - TealOp(None, Op.bnz, subroutine2L1Label), - TealOp(None, Op.err), - TealLabel(None, subroutine2L1Label), - TealOp(None, Op.int, 1), - TealOp(None, Op.retsub), + pt.TealOp(None, pt.Op.store, 1), + pt.TealOp(None, pt.Op.load, 1), + pt.TealOp(None, pt.Op.int, 0), + pt.TealOp(None, pt.Op.eq), + pt.TealOp(None, pt.Op.bnz, subroutine2L1Label), + pt.TealOp(None, pt.Op.err), + pt.TealLabel(None, subroutine2L1Label), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.retsub), ] subroutine3Ops = [ - TealOp(None, Op.retsub), + pt.TealOp(None, pt.Op.retsub), ] - l1Label = LabelReference("l1") + l1Label = pt.LabelReference("l1") mainOps = [ - TealOp(None, Op.txn, "Fee"), - TealOp(None, Op.int, 0), - TealOp(None, Op.eq), - TealOp(None, Op.bz, l1Label), - TealOp(None, Op.int, 100), - TealOp(None, Op.callsub, subroutine1), - TealOp(None, Op.return_), - TealLabel(None, l1Label), - TealOp(None, Op.int, 101), - TealOp(None, Op.callsub, subroutine2), - TealOp(None, Op.return_), + pt.TealOp(None, pt.Op.txn, "Fee"), + pt.TealOp(None, pt.Op.int, 0), + pt.TealOp(None, pt.Op.eq), + pt.TealOp(None, pt.Op.bz, l1Label), + pt.TealOp(None, pt.Op.int, 100), + pt.TealOp(None, pt.Op.callsub, subroutine1), + pt.TealOp(None, pt.Op.return_), + pt.TealLabel(None, l1Label), + pt.TealOp(None, pt.Op.int, 101), + pt.TealOp(None, pt.Op.callsub, subroutine2), + pt.TealOp(None, pt.Op.return_), ] subroutineMapping = { @@ -1356,7 +1356,7 @@ def sub3Impl(a1, a2, a3): localSlots = {None: set(), subroutine1: {0}, subroutine2: {1}, subroutine3: {}} - with pytest.raises(TealInputError) as tie: + with pytest.raises(pt.TealInputError) as tie: spillLocalSlotsDuringRecursion( 5, subroutineMapping, subroutineGraph, localSlots ) @@ -1377,75 +1377,75 @@ def sub2Impl(a1, a2): def sub3Impl(a1, a2, a3): return None - subroutine1 = SubroutineDefinition(sub1Impl, TealType.uint64) - subroutine2 = SubroutineDefinition(sub2Impl, TealType.uint64) - subroutine3 = SubroutineDefinition(sub3Impl, TealType.none) + subroutine1 = pt.SubroutineDefinition(sub1Impl, pt.TealType.uint64) + subroutine2 = pt.SubroutineDefinition(sub2Impl, pt.TealType.uint64) + subroutine3 = pt.SubroutineDefinition(sub3Impl, pt.TealType.none) - subroutine1L1Label = LabelReference("l1") + subroutine1L1Label = pt.LabelReference("l1") subroutine1Ops = [ - TealOp(None, Op.store, 0), - TealOp(None, Op.load, 0), - TealOp(None, Op.int, 0), - TealOp(None, Op.eq), - TealOp(None, Op.bnz, subroutine1L1Label), - TealOp(None, Op.load, 0), - TealOp(None, Op.int, 1), - TealOp(None, Op.minus), - TealOp(None, Op.load, 0), - TealOp(None, Op.dig, 1), - TealOp(None, Op.callsub, subroutine1), - TealOp(None, Op.swap), - TealOp(None, Op.store, 0), - TealOp(None, Op.swap), - TealOp(None, Op.pop), - TealOp(None, Op.int, 1), - TealOp(None, Op.add), - TealOp(None, Op.retsub), - TealLabel(None, subroutine1L1Label), - TealOp(None, Op.load, 255), - TealOp(None, Op.retsub), + pt.TealOp(None, pt.Op.store, 0), + pt.TealOp(None, pt.Op.load, 0), + pt.TealOp(None, pt.Op.int, 0), + pt.TealOp(None, pt.Op.eq), + pt.TealOp(None, pt.Op.bnz, subroutine1L1Label), + pt.TealOp(None, pt.Op.load, 0), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.minus), + pt.TealOp(None, pt.Op.load, 0), + pt.TealOp(None, pt.Op.dig, 1), + pt.TealOp(None, pt.Op.callsub, subroutine1), + pt.TealOp(None, pt.Op.swap), + pt.TealOp(None, pt.Op.store, 0), + pt.TealOp(None, pt.Op.swap), + pt.TealOp(None, pt.Op.pop), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.add), + pt.TealOp(None, pt.Op.retsub), + pt.TealLabel(None, subroutine1L1Label), + pt.TealOp(None, pt.Op.load, 255), + pt.TealOp(None, pt.Op.retsub), ] - subroutine2L1Label = LabelReference("l1") + subroutine2L1Label = pt.LabelReference("l1") subroutine2Ops = [ - TealOp(None, Op.store, 1), - TealOp(None, Op.load, 1), - TealOp(None, Op.int, 0), - TealOp(None, Op.eq), - TealOp(None, Op.bnz, subroutine2L1Label), - TealOp(None, Op.load, 0), - TealOp(None, Op.int, 1), - TealOp(None, Op.minus), - TealOp(None, Op.callsub, subroutine1), - TealOp(None, Op.int, 1), - TealOp(None, Op.add), - TealOp(None, Op.retsub), - TealLabel(None, subroutine2L1Label), - TealOp(None, Op.int, 1), - TealOp(None, Op.retsub), + pt.TealOp(None, pt.Op.store, 1), + pt.TealOp(None, pt.Op.load, 1), + pt.TealOp(None, pt.Op.int, 0), + pt.TealOp(None, pt.Op.eq), + pt.TealOp(None, pt.Op.bnz, subroutine2L1Label), + pt.TealOp(None, pt.Op.load, 0), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.minus), + pt.TealOp(None, pt.Op.callsub, subroutine1), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.add), + pt.TealOp(None, pt.Op.retsub), + pt.TealLabel(None, subroutine2L1Label), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.retsub), ] subroutine3Ops = [ - TealOp(None, Op.callsub, subroutine3), - TealOp(None, Op.retsub), + pt.TealOp(None, pt.Op.callsub, subroutine3), + pt.TealOp(None, pt.Op.retsub), ] - l1Label = LabelReference("l1") + l1Label = pt.LabelReference("l1") mainOps = [ - TealOp(None, Op.int, 1), - TealOp(None, Op.store, 255), - TealOp(None, Op.txn, "Fee"), - TealOp(None, Op.int, 0), - TealOp(None, Op.eq), - TealOp(None, Op.bz, l1Label), - TealOp(None, Op.int, 100), - TealOp(None, Op.callsub, subroutine1), - TealOp(None, Op.return_), - TealLabel(None, l1Label), - TealOp(None, Op.int, 101), - TealOp(None, Op.callsub, subroutine2), - TealOp(None, Op.return_), - TealOp(None, Op.callsub, subroutine3), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.store, 255), + pt.TealOp(None, pt.Op.txn, "Fee"), + pt.TealOp(None, pt.Op.int, 0), + pt.TealOp(None, pt.Op.eq), + pt.TealOp(None, pt.Op.bz, l1Label), + pt.TealOp(None, pt.Op.int, 100), + pt.TealOp(None, pt.Op.callsub, subroutine1), + pt.TealOp(None, pt.Op.return_), + pt.TealLabel(None, l1Label), + pt.TealOp(None, pt.Op.int, 101), + pt.TealOp(None, pt.Op.callsub, subroutine2), + pt.TealOp(None, pt.Op.return_), + pt.TealOp(None, pt.Op.callsub, subroutine3), ] subroutineMapping = { @@ -1464,65 +1464,65 @@ def sub3Impl(a1, a2, a3): assert actual == expected assert mainOps == [ - TealOp(None, Op.int, 1), - TealOp(None, Op.store, 255), - TealOp(None, Op.txn, "Fee"), - TealOp(None, Op.int, 0), - TealOp(None, Op.eq), - TealOp(None, Op.bz, l1Label), - TealOp(None, Op.int, 100), - TealOp(None, Op.callsub, expected[subroutine1]), - TealOp(None, Op.return_), - TealLabel(None, l1Label), - TealOp(None, Op.int, 101), - TealOp(None, Op.callsub, expected[subroutine2]), - TealOp(None, Op.return_), - TealOp(None, Op.callsub, expected[subroutine3]), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.store, 255), + pt.TealOp(None, pt.Op.txn, "Fee"), + pt.TealOp(None, pt.Op.int, 0), + pt.TealOp(None, pt.Op.eq), + pt.TealOp(None, pt.Op.bz, l1Label), + pt.TealOp(None, pt.Op.int, 100), + pt.TealOp(None, pt.Op.callsub, expected[subroutine1]), + pt.TealOp(None, pt.Op.return_), + pt.TealLabel(None, l1Label), + pt.TealOp(None, pt.Op.int, 101), + pt.TealOp(None, pt.Op.callsub, expected[subroutine2]), + pt.TealOp(None, pt.Op.return_), + pt.TealOp(None, pt.Op.callsub, expected[subroutine3]), ] assert subroutine1Ops == [ - TealOp(None, Op.store, 0), - TealOp(None, Op.load, 0), - TealOp(None, Op.int, 0), - TealOp(None, Op.eq), - TealOp(None, Op.bnz, subroutine1L1Label), - TealOp(None, Op.load, 0), - TealOp(None, Op.int, 1), - TealOp(None, Op.minus), - TealOp(None, Op.load, 0), - TealOp(None, Op.dig, 1), - TealOp(None, Op.callsub, expected[subroutine1]), - TealOp(None, Op.swap), - TealOp(None, Op.store, 0), - TealOp(None, Op.swap), - TealOp(None, Op.pop), - TealOp(None, Op.int, 1), - TealOp(None, Op.add), - TealOp(None, Op.retsub), - TealLabel(None, subroutine1L1Label), - TealOp(None, Op.load, 255), - TealOp(None, Op.retsub), + pt.TealOp(None, pt.Op.store, 0), + pt.TealOp(None, pt.Op.load, 0), + pt.TealOp(None, pt.Op.int, 0), + pt.TealOp(None, pt.Op.eq), + pt.TealOp(None, pt.Op.bnz, subroutine1L1Label), + pt.TealOp(None, pt.Op.load, 0), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.minus), + pt.TealOp(None, pt.Op.load, 0), + pt.TealOp(None, pt.Op.dig, 1), + pt.TealOp(None, pt.Op.callsub, expected[subroutine1]), + pt.TealOp(None, pt.Op.swap), + pt.TealOp(None, pt.Op.store, 0), + pt.TealOp(None, pt.Op.swap), + pt.TealOp(None, pt.Op.pop), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.add), + pt.TealOp(None, pt.Op.retsub), + pt.TealLabel(None, subroutine1L1Label), + pt.TealOp(None, pt.Op.load, 255), + pt.TealOp(None, pt.Op.retsub), ] assert subroutine2Ops == [ - TealOp(None, Op.store, 1), - TealOp(None, Op.load, 1), - TealOp(None, Op.int, 0), - TealOp(None, Op.eq), - TealOp(None, Op.bnz, subroutine2L1Label), - TealOp(None, Op.load, 0), - TealOp(None, Op.int, 1), - TealOp(None, Op.minus), - TealOp(None, Op.callsub, expected[subroutine1]), - TealOp(None, Op.int, 1), - TealOp(None, Op.add), - TealOp(None, Op.retsub), - TealLabel(None, subroutine2L1Label), - TealOp(None, Op.int, 1), - TealOp(None, Op.retsub), + pt.TealOp(None, pt.Op.store, 1), + pt.TealOp(None, pt.Op.load, 1), + pt.TealOp(None, pt.Op.int, 0), + pt.TealOp(None, pt.Op.eq), + pt.TealOp(None, pt.Op.bnz, subroutine2L1Label), + pt.TealOp(None, pt.Op.load, 0), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.minus), + pt.TealOp(None, pt.Op.callsub, expected[subroutine1]), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.add), + pt.TealOp(None, pt.Op.retsub), + pt.TealLabel(None, subroutine2L1Label), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.retsub), ] assert subroutine3Ops == [ - TealOp(None, Op.callsub, expected[subroutine3]), - TealOp(None, Op.retsub), + pt.TealOp(None, pt.Op.callsub, expected[subroutine3]), + pt.TealOp(None, pt.Op.retsub), ] diff --git a/pyteal/errors.py b/pyteal/errors.py index 4093819dd..bb238d98e 100644 --- a/pyteal/errors.py +++ b/pyteal/errors.py @@ -1,7 +1,7 @@ from typing import Optional, TYPE_CHECKING if TYPE_CHECKING: - from .ast import Expr + from pyteal.ast import Expr class TealInternalError(Exception): diff --git a/pyteal/ir/__init__.py b/pyteal/ir/__init__.py index 8107e4c30..e7db57cf8 100644 --- a/pyteal/ir/__init__.py +++ b/pyteal/ir/__init__.py @@ -1,13 +1,13 @@ -from .ops import Op, Mode +from pyteal.ir.ops import Op, Mode -from .tealcomponent import TealComponent -from .tealop import TealOp -from .teallabel import TealLabel -from .tealblock import TealBlock -from .tealsimpleblock import TealSimpleBlock -from .tealconditionalblock import TealConditionalBlock +from pyteal.ir.tealcomponent import TealComponent +from pyteal.ir.tealop import TealOp +from pyteal.ir.teallabel import TealLabel +from pyteal.ir.tealblock import TealBlock +from pyteal.ir.tealsimpleblock import TealSimpleBlock +from pyteal.ir.tealconditionalblock import TealConditionalBlock -from .labelref import LabelReference +from pyteal.ir.labelref import LabelReference __all__ = [ "Op", diff --git a/pyteal/ir/tealblock.py b/pyteal/ir/tealblock.py index 5b337967a..5fd2d5a42 100644 --- a/pyteal/ir/tealblock.py +++ b/pyteal/ir/tealblock.py @@ -1,13 +1,14 @@ from abc import ABC, abstractmethod + from typing import Dict, List, Tuple, Set, Iterator, cast, TYPE_CHECKING -from .tealop import TealOp, Op -from ..errors import TealCompileError +from pyteal.ir.tealop import TealOp, Op +from pyteal.errors import TealCompileError if TYPE_CHECKING: - from ..ast import Expr, ScratchSlot - from ..compiler import CompileOptions - from .tealsimpleblock import TealSimpleBlock + from pyteal.ast import Expr, ScratchSlot + from pyteal.compiler import CompileOptions + from pyteal.ir.tealsimpleblock import TealSimpleBlock class TealBlock(ABC): @@ -140,7 +141,7 @@ def FromOp( Returns: The starting and ending block of the path that encodes the given TealOp and arguments. """ - from .tealsimpleblock import TealSimpleBlock + from pyteal.ir.tealsimpleblock import TealSimpleBlock opBlock = TealSimpleBlock([op]) @@ -272,7 +273,7 @@ def MatchScratchSlotReferences( } for actualSlot, expectedSlot in zip(actual, expected): - if not actualSlot in mapFromActualToExpected: + if actualSlot not in mapFromActualToExpected: if expectedSlot in mapFromActualToExpected.values(): # this value was already seen return False diff --git a/pyteal/ir/tealblock_test.py b/pyteal/ir/tealblock_test.py index 04065b0cd..4d215ccac 100644 --- a/pyteal/ir/tealblock_test.py +++ b/pyteal/ir/tealblock_test.py @@ -1,130 +1,127 @@ from typing import NamedTuple, List -from .. import * +import pyteal as pt -# this is not necessary but mypy complains if it's not included -from .. import CompileOptions - -options = CompileOptions() +options = pt.CompileOptions() def test_from_op_no_args(): - op = TealOp(None, Op.int, 1) + op = pt.TealOp(None, pt.Op.int, 1) - expected = TealSimpleBlock([op]) + expected = pt.TealSimpleBlock([op]) - actual, _ = TealBlock.FromOp(options, op) + actual, _ = pt.TealBlock.FromOp(options, op) assert actual == expected def test_from_op_1_arg(): - op = TealOp(None, Op.pop) - arg_1 = Bytes("message") + op = pt.TealOp(None, pt.Op.pop) + arg_1 = pt.Bytes("message") - expected = TealSimpleBlock([TealOp(arg_1, Op.byte, '"message"'), op]) + expected = pt.TealSimpleBlock([pt.TealOp(arg_1, pt.Op.byte, '"message"'), op]) - actual, _ = TealBlock.FromOp(options, op, arg_1) + actual, _ = pt.TealBlock.FromOp(options, op, arg_1) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) actual.validateTree() assert actual == expected def test_from_op_2_args(): - op = TealOp(None, Op.app_global_put) - arg_1 = Bytes("key") - arg_2 = Int(5) + op = pt.TealOp(None, pt.Op.app_global_put) + arg_1 = pt.Bytes("key") + arg_2 = pt.Int(5) - expected = TealSimpleBlock( - [TealOp(arg_1, Op.byte, '"key"'), TealOp(arg_2, Op.int, 5), op] + expected = pt.TealSimpleBlock( + [pt.TealOp(arg_1, pt.Op.byte, '"key"'), pt.TealOp(arg_2, pt.Op.int, 5), op] ) - actual, _ = TealBlock.FromOp(options, op, arg_1, arg_2) + actual, _ = pt.TealBlock.FromOp(options, op, arg_1, arg_2) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) actual.validateTree() assert actual == expected def test_from_op_3_args(): - op = TealOp(None, Op.app_local_put) - arg_1 = Int(0) - arg_2 = Bytes("key") - arg_3 = Int(1) - arg_4 = Int(2) + op = pt.TealOp(None, pt.Op.app_local_put) + arg_1 = pt.Int(0) + arg_2 = pt.Bytes("key") + arg_3 = pt.Int(1) + arg_4 = pt.Int(2) arg_3_plus_4 = arg_3 + arg_4 - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(arg_1, Op.int, 0), - TealOp(arg_2, Op.byte, '"key"'), - TealOp(arg_3, Op.int, 1), - TealOp(arg_4, Op.int, 2), - TealOp(arg_3_plus_4, Op.add), + pt.TealOp(arg_1, pt.Op.int, 0), + pt.TealOp(arg_2, pt.Op.byte, '"key"'), + pt.TealOp(arg_3, pt.Op.int, 1), + pt.TealOp(arg_4, pt.Op.int, 2), + pt.TealOp(arg_3_plus_4, pt.Op.add), op, ] ) - actual, _ = TealBlock.FromOp(options, op, arg_1, arg_2, arg_3_plus_4) + actual, _ = pt.TealBlock.FromOp(options, op, arg_1, arg_2, arg_3_plus_4) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) actual.validateTree() assert actual == expected def test_iterate_single(): - block = TealSimpleBlock([TealOp(None, Op.int, 1)]) + block = pt.TealSimpleBlock([pt.TealOp(None, pt.Op.int, 1)]) - blocks = list(TealBlock.Iterate(block)) + blocks = list(pt.TealBlock.Iterate(block)) assert blocks == [block] def test_iterate_sequence(): - block5 = TealSimpleBlock([TealOp(None, Op.int, 5)]) - block4 = TealSimpleBlock([TealOp(None, Op.int, 4)]) + block5 = pt.TealSimpleBlock([pt.TealOp(None, pt.Op.int, 5)]) + block4 = pt.TealSimpleBlock([pt.TealOp(None, pt.Op.int, 4)]) block4.setNextBlock(block5) - block3 = TealSimpleBlock([TealOp(None, Op.int, 3)]) + block3 = pt.TealSimpleBlock([pt.TealOp(None, pt.Op.int, 3)]) block3.setNextBlock(block4) - block2 = TealSimpleBlock([TealOp(None, Op.int, 2)]) + block2 = pt.TealSimpleBlock([pt.TealOp(None, pt.Op.int, 2)]) block2.setNextBlock(block3) - block1 = TealSimpleBlock([TealOp(None, Op.int, 1)]) + block1 = pt.TealSimpleBlock([pt.TealOp(None, pt.Op.int, 1)]) block1.setNextBlock(block2) - blocks = list(TealBlock.Iterate(block1)) + blocks = list(pt.TealBlock.Iterate(block1)) assert blocks == [block1, block2, block3, block4, block5] def test_iterate_branch(): - blockTrue = TealSimpleBlock([TealOp(None, Op.byte, '"true"')]) - blockFalse = TealSimpleBlock([TealOp(None, Op.byte, '"false"')]) - block = TealConditionalBlock([TealOp(None, Op.int, 1)]) + blockTrue = pt.TealSimpleBlock([pt.TealOp(None, pt.Op.byte, '"true"')]) + blockFalse = pt.TealSimpleBlock([pt.TealOp(None, pt.Op.byte, '"false"')]) + block = pt.TealConditionalBlock([pt.TealOp(None, pt.Op.int, 1)]) block.setTrueBlock(blockTrue) block.setFalseBlock(blockFalse) - blocks = list(TealBlock.Iterate(block)) + blocks = list(pt.TealBlock.Iterate(block)) assert blocks == [block, blockTrue, blockFalse] def test_iterate_multiple_branch(): - blockTrueTrue = TealSimpleBlock([TealOp(None, Op.byte, '"true true"')]) - blockTrueFalse = TealSimpleBlock([TealOp(None, Op.byte, '"true false"')]) - blockTrueBranch = TealConditionalBlock([]) + blockTrueTrue = pt.TealSimpleBlock([pt.TealOp(None, pt.Op.byte, '"true true"')]) + blockTrueFalse = pt.TealSimpleBlock([pt.TealOp(None, pt.Op.byte, '"true false"')]) + blockTrueBranch = pt.TealConditionalBlock([]) blockTrueBranch.setTrueBlock(blockTrueTrue) blockTrueBranch.setFalseBlock(blockTrueFalse) - blockTrue = TealSimpleBlock([TealOp(None, Op.byte, '"true"')]) + blockTrue = pt.TealSimpleBlock([pt.TealOp(None, pt.Op.byte, '"true"')]) blockTrue.setNextBlock(blockTrueBranch) - blockFalse = TealSimpleBlock([TealOp(None, Op.byte, '"false"')]) - block = TealConditionalBlock([TealOp(None, Op.int, 1)]) + blockFalse = pt.TealSimpleBlock([pt.TealOp(None, pt.Op.byte, '"false"')]) + block = pt.TealConditionalBlock([pt.TealOp(None, pt.Op.int, 1)]) block.setTrueBlock(blockTrue) block.setFalseBlock(blockFalse) - blocks = list(TealBlock.Iterate(block)) + blocks = list(pt.TealBlock.Iterate(block)) assert blocks == [ block, @@ -137,149 +134,149 @@ def test_iterate_multiple_branch(): def test_iterate_branch_converge(): - blockEnd = TealSimpleBlock([TealOp(None, Op.return_)]) - blockTrue = TealSimpleBlock([TealOp(None, Op.byte, '"true"')]) + blockEnd = pt.TealSimpleBlock([pt.TealOp(None, pt.Op.return_)]) + blockTrue = pt.TealSimpleBlock([pt.TealOp(None, pt.Op.byte, '"true"')]) blockTrue.setNextBlock(blockEnd) - blockFalse = TealSimpleBlock([TealOp(None, Op.byte, '"false"')]) + blockFalse = pt.TealSimpleBlock([pt.TealOp(None, pt.Op.byte, '"false"')]) blockFalse.setNextBlock(blockEnd) - block = TealConditionalBlock([TealOp(None, Op.int, 1)]) + block = pt.TealConditionalBlock([pt.TealOp(None, pt.Op.int, 1)]) block.setTrueBlock(blockTrue) block.setFalseBlock(blockFalse) - blocks = list(TealBlock.Iterate(block)) + blocks = list(pt.TealBlock.Iterate(block)) assert blocks == [block, blockTrue, blockFalse, blockEnd] def test_normalize_single(): - original = TealSimpleBlock([TealOp(None, Op.int, 1)]) + original = pt.TealSimpleBlock([pt.TealOp(None, pt.Op.int, 1)]) - expected = TealSimpleBlock([TealOp(None, Op.int, 1)]) + expected = pt.TealSimpleBlock([pt.TealOp(None, pt.Op.int, 1)]) original.addIncoming() - actual = TealBlock.NormalizeBlocks(original) + actual = pt.TealBlock.NormalizeBlocks(original) actual.validateTree() assert actual == expected def test_normalize_sequence(): - block6 = TealSimpleBlock([]) - block5 = TealSimpleBlock([TealOp(None, Op.int, 5)]) + block6 = pt.TealSimpleBlock([]) + block5 = pt.TealSimpleBlock([pt.TealOp(None, pt.Op.int, 5)]) block5.setNextBlock(block6) - block4 = TealSimpleBlock([TealOp(None, Op.int, 4)]) + block4 = pt.TealSimpleBlock([pt.TealOp(None, pt.Op.int, 4)]) block4.setNextBlock(block5) - block3 = TealSimpleBlock([TealOp(None, Op.int, 3)]) + block3 = pt.TealSimpleBlock([pt.TealOp(None, pt.Op.int, 3)]) block3.setNextBlock(block4) - block2 = TealSimpleBlock([TealOp(None, Op.int, 2)]) + block2 = pt.TealSimpleBlock([pt.TealOp(None, pt.Op.int, 2)]) block2.setNextBlock(block3) - block1 = TealSimpleBlock([TealOp(None, Op.int, 1)]) + block1 = pt.TealSimpleBlock([pt.TealOp(None, pt.Op.int, 1)]) block1.setNextBlock(block2) - expected = TealSimpleBlock( + expected = pt.TealSimpleBlock( [ - TealOp(None, Op.int, 1), - TealOp(None, Op.int, 2), - TealOp(None, Op.int, 3), - TealOp(None, Op.int, 4), - TealOp(None, Op.int, 5), + pt.TealOp(None, pt.Op.int, 1), + pt.TealOp(None, pt.Op.int, 2), + pt.TealOp(None, pt.Op.int, 3), + pt.TealOp(None, pt.Op.int, 4), + pt.TealOp(None, pt.Op.int, 5), ] ) block1.addIncoming() - actual = TealBlock.NormalizeBlocks(block1) + actual = pt.TealBlock.NormalizeBlocks(block1) actual.validateTree() assert actual == expected def test_normalize_branch(): - blockTrueNext = TealSimpleBlock([TealOp(None, Op.int, 4)]) - blockTrue = TealSimpleBlock([TealOp(None, Op.byte, '"true"')]) + blockTrueNext = pt.TealSimpleBlock([pt.TealOp(None, pt.Op.int, 4)]) + blockTrue = pt.TealSimpleBlock([pt.TealOp(None, pt.Op.byte, '"true"')]) blockTrue.setNextBlock(blockTrueNext) - blockFalse = TealSimpleBlock([TealOp(None, Op.byte, '"false"')]) - blockBranch = TealConditionalBlock([TealOp(None, Op.int, 1)]) + blockFalse = pt.TealSimpleBlock([pt.TealOp(None, pt.Op.byte, '"false"')]) + blockBranch = pt.TealConditionalBlock([pt.TealOp(None, pt.Op.int, 1)]) blockBranch.setTrueBlock(blockTrue) blockBranch.setFalseBlock(blockFalse) - original = TealSimpleBlock([]) + original = pt.TealSimpleBlock([]) original.setNextBlock(blockBranch) - expectedTrue = TealSimpleBlock( - [TealOp(None, Op.byte, '"true"'), TealOp(None, Op.int, 4)] + expectedTrue = pt.TealSimpleBlock( + [pt.TealOp(None, pt.Op.byte, '"true"'), pt.TealOp(None, pt.Op.int, 4)] ) - expectedFalse = TealSimpleBlock([TealOp(None, Op.byte, '"false"')]) - expected = TealConditionalBlock([TealOp(None, Op.int, 1)]) + expectedFalse = pt.TealSimpleBlock([pt.TealOp(None, pt.Op.byte, '"false"')]) + expected = pt.TealConditionalBlock([pt.TealOp(None, pt.Op.int, 1)]) expected.setTrueBlock(expectedTrue) expected.setFalseBlock(expectedFalse) original.addIncoming() - actual = TealBlock.NormalizeBlocks(original) + actual = pt.TealBlock.NormalizeBlocks(original) actual.validateTree() assert actual == expected def test_normalize_branch_converge(): - blockEnd = TealSimpleBlock([]) - blockTrueNext = TealSimpleBlock([TealOp(None, Op.int, 4)]) + blockEnd = pt.TealSimpleBlock([]) + blockTrueNext = pt.TealSimpleBlock([pt.TealOp(None, pt.Op.int, 4)]) blockTrueNext.setNextBlock(blockEnd) - blockTrue = TealSimpleBlock([TealOp(None, Op.byte, '"true"')]) + blockTrue = pt.TealSimpleBlock([pt.TealOp(None, pt.Op.byte, '"true"')]) blockTrue.setNextBlock(blockTrueNext) - blockFalse = TealSimpleBlock([TealOp(None, Op.byte, '"false"')]) + blockFalse = pt.TealSimpleBlock([pt.TealOp(None, pt.Op.byte, '"false"')]) blockFalse.setNextBlock(blockEnd) - blockBranch = TealConditionalBlock([TealOp(None, Op.int, 1)]) + blockBranch = pt.TealConditionalBlock([pt.TealOp(None, pt.Op.int, 1)]) blockBranch.setTrueBlock(blockTrue) blockBranch.setFalseBlock(blockFalse) - original = TealSimpleBlock([]) + original = pt.TealSimpleBlock([]) original.setNextBlock(blockBranch) - expectedEnd = TealSimpleBlock([]) - expectedTrue = TealSimpleBlock( - [TealOp(None, Op.byte, '"true"'), TealOp(None, Op.int, 4)] + expectedEnd = pt.TealSimpleBlock([]) + expectedTrue = pt.TealSimpleBlock( + [pt.TealOp(None, pt.Op.byte, '"true"'), pt.TealOp(None, pt.Op.int, 4)] ) expectedTrue.setNextBlock(expectedEnd) - expectedFalse = TealSimpleBlock([TealOp(None, Op.byte, '"false"')]) + expectedFalse = pt.TealSimpleBlock([pt.TealOp(None, pt.Op.byte, '"false"')]) expectedFalse.setNextBlock(expectedEnd) - expected = TealConditionalBlock([TealOp(None, Op.int, 1)]) + expected = pt.TealConditionalBlock([pt.TealOp(None, pt.Op.int, 1)]) expected.setTrueBlock(expectedTrue) expected.setFalseBlock(expectedFalse) original.addIncoming() - actual = TealBlock.NormalizeBlocks(original) + actual = pt.TealBlock.NormalizeBlocks(original) actual.validateTree() assert actual == expected def test_GetReferencedScratchSlots(): - a = ScratchSlot() - b = ScratchSlot() - c = ScratchSlot() - d = ScratchSlot() + a = pt.ScratchSlot() + b = pt.ScratchSlot() + c = pt.ScratchSlot() + d = pt.ScratchSlot() - end = TealSimpleBlock([TealOp(None, Op.load, d)]) - trueBranch = TealSimpleBlock([TealOp(None, Op.load, b)]) + end = pt.TealSimpleBlock([pt.TealOp(None, pt.Op.load, d)]) + trueBranch = pt.TealSimpleBlock([pt.TealOp(None, pt.Op.load, b)]) trueBranch.setNextBlock(end) - falseBranch = TealSimpleBlock([TealOp(None, Op.load, c)]) + falseBranch = pt.TealSimpleBlock([pt.TealOp(None, pt.Op.load, c)]) falseBranch.setNextBlock(end) - splitBranch = TealConditionalBlock([TealOp(None, Op.load, a)]) + splitBranch = pt.TealConditionalBlock([pt.TealOp(None, pt.Op.load, a)]) splitBranch.setTrueBlock(trueBranch) splitBranch.setFalseBlock(falseBranch) - slotReferences = TealBlock.GetReferencedScratchSlots(splitBranch) + slotReferences = pt.TealBlock.GetReferencedScratchSlots(splitBranch) assert slotReferences == [a, b, c, d] def test_MatchScratchSlotReferences(): class MatchSlotReferenceTest(NamedTuple): - actual: List[ScratchSlot] - expected: List[ScratchSlot] + actual: List[pt.ScratchSlot] + expected: List[pt.ScratchSlot] match: bool - a = ScratchSlot() - b = ScratchSlot() - c = ScratchSlot() - d = ScratchSlot() + a = pt.ScratchSlot() + b = pt.ScratchSlot() + c = pt.ScratchSlot() + d = pt.ScratchSlot() tests: List[MatchSlotReferenceTest] = [ MatchSlotReferenceTest( @@ -351,6 +348,6 @@ class MatchSlotReferenceTest(NamedTuple): for i, test in enumerate(tests): assert ( - TealBlock.MatchScratchSlotReferences(test.actual, test.expected) + pt.TealBlock.MatchScratchSlotReferences(test.actual, test.expected) == test.match ), "Test at index {} failed".format(i) diff --git a/pyteal/ir/tealcomponent.py b/pyteal/ir/tealcomponent.py index e98c2bbe9..bb7755879 100644 --- a/pyteal/ir/tealcomponent.py +++ b/pyteal/ir/tealcomponent.py @@ -3,7 +3,7 @@ from contextlib import AbstractContextManager if TYPE_CHECKING: - from ..ast import Expr, ScratchSlot, SubroutineDefinition + from pyteal.ast import Expr, ScratchSlot, SubroutineDefinition class TealComponent(ABC): diff --git a/pyteal/ir/tealcomponent_test.py b/pyteal/ir/tealcomponent_test.py index 81ac8c7a5..acda8ccf8 100644 --- a/pyteal/ir/tealcomponent_test.py +++ b/pyteal/ir/tealcomponent_test.py @@ -1,21 +1,19 @@ -import pytest - -from .. import * +import pyteal as pt def test_EqualityContext(): - expr1 = Int(1) - expr2 = Int(1) + expr1 = pt.Int(1) + expr2 = pt.Int(1) - op1 = TealOp(expr1, Op.int, 1) - op2 = TealOp(expr2, Op.int, 1) + op1 = pt.TealOp(expr1, pt.Op.int, 1) + op2 = pt.TealOp(expr2, pt.Op.int, 1) assert op1 == op1 assert op2 == op2 assert op1 != op2 assert op2 != op1 - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert op1 == op1 assert op2 == op2 assert op1 == op2 diff --git a/pyteal/ir/tealconditionalblock.py b/pyteal/ir/tealconditionalblock.py index d86d8b035..2b4d6a790 100644 --- a/pyteal/ir/tealconditionalblock.py +++ b/pyteal/ir/tealconditionalblock.py @@ -1,7 +1,7 @@ -from typing import Optional, List, cast +from typing import Optional, List -from .tealop import TealOp -from .tealblock import TealBlock +from pyteal.ir.tealop import TealOp +from pyteal.ir.tealblock import TealBlock class TealConditionalBlock(TealBlock): diff --git a/pyteal/ir/tealconditionalblock_test.py b/pyteal/ir/tealconditionalblock_test.py index 7139adc24..3177ff390 100644 --- a/pyteal/ir/tealconditionalblock_test.py +++ b/pyteal/ir/tealconditionalblock_test.py @@ -1,51 +1,57 @@ -from .. import * +import pyteal as pt def test_constructor(): - block1 = TealConditionalBlock([]) + block1 = pt.TealConditionalBlock([]) assert block1.ops == [] assert block1.trueBlock is None assert block1.falseBlock is None - block2 = TealConditionalBlock([TealOp(None, Op.int, 1)]) - assert block2.ops == [TealOp(None, Op.int, 1)] + block2 = pt.TealConditionalBlock([pt.TealOp(None, pt.Op.int, 1)]) + assert block2.ops == [pt.TealOp(None, pt.Op.int, 1)] assert block2.trueBlock is None assert block2.falseBlock is None def test_true_block(): - block = TealConditionalBlock([]) - block.setTrueBlock(TealSimpleBlock([TealOp(None, Op.substring3)])) - assert block.trueBlock == TealSimpleBlock([TealOp(None, Op.substring3)]) - assert block.getOutgoing() == [TealSimpleBlock([TealOp(None, Op.substring3)])] + block = pt.TealConditionalBlock([]) + block.setTrueBlock(pt.TealSimpleBlock([pt.TealOp(None, pt.Op.substring3)])) + assert block.trueBlock == pt.TealSimpleBlock([pt.TealOp(None, pt.Op.substring3)]) + assert block.getOutgoing() == [ + pt.TealSimpleBlock([pt.TealOp(None, pt.Op.substring3)]) + ] def test_false_block(): - block = TealConditionalBlock([]) - block.setFalseBlock(TealSimpleBlock([TealOp(None, Op.substring3)])) - assert block.falseBlock == TealSimpleBlock([TealOp(None, Op.substring3)]) + block = pt.TealConditionalBlock([]) + block.setFalseBlock(pt.TealSimpleBlock([pt.TealOp(None, pt.Op.substring3)])) + assert block.falseBlock == pt.TealSimpleBlock([pt.TealOp(None, pt.Op.substring3)]) def test_outgoing(): - emptyBlock = TealConditionalBlock([]) + emptyBlock = pt.TealConditionalBlock([]) assert emptyBlock.getOutgoing() == [] - trueBlock = TealConditionalBlock([]) - trueBlock.setTrueBlock(TealSimpleBlock([TealOp(None, Op.byte, '"true"')])) + trueBlock = pt.TealConditionalBlock([]) + trueBlock.setTrueBlock(pt.TealSimpleBlock([pt.TealOp(None, pt.Op.byte, '"true"')])) assert trueBlock.getOutgoing() == [ - TealSimpleBlock([TealOp(None, Op.byte, '"true"')]) + pt.TealSimpleBlock([pt.TealOp(None, pt.Op.byte, '"true"')]) ] - falseBlock = TealConditionalBlock([]) - falseBlock.setFalseBlock(TealSimpleBlock([TealOp(None, Op.byte, '"false"')])) + falseBlock = pt.TealConditionalBlock([]) + falseBlock.setFalseBlock( + pt.TealSimpleBlock([pt.TealOp(None, pt.Op.byte, '"false"')]) + ) assert falseBlock.getOutgoing() == [ - TealSimpleBlock([TealOp(None, Op.byte, '"false"')]) + pt.TealSimpleBlock([pt.TealOp(None, pt.Op.byte, '"false"')]) ] - bothBlock = TealConditionalBlock([]) - bothBlock.setTrueBlock(TealSimpleBlock([TealOp(None, Op.byte, '"true"')])) - bothBlock.setFalseBlock(TealSimpleBlock([TealOp(None, Op.byte, '"false"')])) + bothBlock = pt.TealConditionalBlock([]) + bothBlock.setTrueBlock(pt.TealSimpleBlock([pt.TealOp(None, pt.Op.byte, '"true"')])) + bothBlock.setFalseBlock( + pt.TealSimpleBlock([pt.TealOp(None, pt.Op.byte, '"false"')]) + ) assert bothBlock.getOutgoing() == [ - TealSimpleBlock([TealOp(None, Op.byte, '"true"')]), - TealSimpleBlock([TealOp(None, Op.byte, '"false"')]), + pt.TealSimpleBlock([pt.TealOp(None, pt.Op.byte, '"true"')]), + pt.TealSimpleBlock([pt.TealOp(None, pt.Op.byte, '"false"')]), ] diff --git a/pyteal/ir/teallabel.py b/pyteal/ir/teallabel.py index 12319269d..6c6cea98e 100644 --- a/pyteal/ir/teallabel.py +++ b/pyteal/ir/teallabel.py @@ -1,10 +1,10 @@ from typing import Optional, TYPE_CHECKING -from .tealcomponent import TealComponent -from .labelref import LabelReference +from pyteal.ir.tealcomponent import TealComponent +from pyteal.ir.labelref import LabelReference if TYPE_CHECKING: - from ..ast import Expr + from pyteal.ast import Expr class TealLabel(TealComponent): diff --git a/pyteal/ir/tealop.py b/pyteal/ir/tealop.py index b5ca1ce31..3f4e8a2a8 100644 --- a/pyteal/ir/tealop.py +++ b/pyteal/ir/tealop.py @@ -1,12 +1,12 @@ from typing import Union, List, Optional, TYPE_CHECKING -from .tealcomponent import TealComponent -from .labelref import LabelReference -from .ops import Op -from ..errors import TealInternalError +from pyteal.ir.tealcomponent import TealComponent +from pyteal.ir.labelref import LabelReference +from pyteal.ir.ops import Op +from pyteal.errors import TealInternalError if TYPE_CHECKING: - from ..ast import Expr, ScratchSlot, SubroutineDefinition + from pyteal.ast import Expr, ScratchSlot, SubroutineDefinition class TealOp(TealComponent): @@ -24,7 +24,7 @@ def getOp(self) -> Op: return self.op def getSlots(self) -> List["ScratchSlot"]: - from ..ast import ScratchSlot + from pyteal.ast import ScratchSlot return [arg for arg in self.args if isinstance(arg, ScratchSlot)] @@ -34,7 +34,7 @@ def assignSlot(self, slot: "ScratchSlot", location: int) -> None: self.args[i] = location def getSubroutines(self) -> List["SubroutineDefinition"]: - from ..ast import SubroutineDefinition + from pyteal.ast import SubroutineDefinition return [arg for arg in self.args if isinstance(arg, SubroutineDefinition)] @@ -44,7 +44,7 @@ def resolveSubroutine(self, subroutine: "SubroutineDefinition", label: str) -> N self.args[i] = label def assemble(self) -> str: - from ..ast import ScratchSlot, SubroutineDefinition + from pyteal.ast import ScratchSlot, SubroutineDefinition parts = [str(self.op)] for arg in self.args: @@ -81,7 +81,7 @@ def __eq__(self, other: object) -> bool: return False if not TealComponent.Context.checkScratchSlotEquality: - from ..ast import ScratchSlot + from pyteal import ScratchSlot if len(self.args) != len(other.args): return False diff --git a/pyteal/ir/tealsimpleblock.py b/pyteal/ir/tealsimpleblock.py index 485e4c6e6..e59c74377 100644 --- a/pyteal/ir/tealsimpleblock.py +++ b/pyteal/ir/tealsimpleblock.py @@ -1,7 +1,7 @@ -from typing import Optional, List, cast +from typing import Optional, List -from .tealop import TealOp -from .tealblock import TealBlock +from pyteal.ir.tealop import TealOp +from pyteal.ir.tealblock import TealBlock class TealSimpleBlock(TealBlock): diff --git a/pyteal/ir/tealsimpleblock_test.py b/pyteal/ir/tealsimpleblock_test.py index 78893d60f..8ad2465c2 100644 --- a/pyteal/ir/tealsimpleblock_test.py +++ b/pyteal/ir/tealsimpleblock_test.py @@ -1,28 +1,28 @@ -from .. import * +import pyteal as pt def test_constructor(): - block1 = TealSimpleBlock([]) + block1 = pt.TealSimpleBlock([]) assert block1.ops == [] assert block1.nextBlock is None - block2 = TealSimpleBlock([TealOp(None, Op.int, 1)]) - assert block2.ops == [TealOp(None, Op.int, 1)] + block2 = pt.TealSimpleBlock([pt.TealOp(None, pt.Op.int, 1)]) + assert block2.ops == [pt.TealOp(None, pt.Op.int, 1)] assert block2.nextBlock is None def test_next_block(): - block = TealSimpleBlock([]) - block.setNextBlock(TealSimpleBlock([TealOp(None, Op.substring3)])) - assert block.nextBlock == TealSimpleBlock([TealOp(None, Op.substring3)]) + block = pt.TealSimpleBlock([]) + block.setNextBlock(pt.TealSimpleBlock([pt.TealOp(None, pt.Op.substring3)])) + assert block.nextBlock == pt.TealSimpleBlock([pt.TealOp(None, pt.Op.substring3)]) def test_outgoing(): - emptyBlock = TealSimpleBlock([]) + emptyBlock = pt.TealSimpleBlock([]) assert emptyBlock.getOutgoing() == [] - block = TealSimpleBlock([]) - block.setNextBlock(TealSimpleBlock([TealOp(None, Op.byte, '"nextBlock"')])) + block = pt.TealSimpleBlock([]) + block.setNextBlock(pt.TealSimpleBlock([pt.TealOp(None, pt.Op.byte, '"nextBlock"')])) assert block.getOutgoing() == [ - TealSimpleBlock([TealOp(None, Op.byte, '"nextBlock"')]) + pt.TealSimpleBlock([pt.TealOp(None, pt.Op.byte, '"nextBlock"')]) ] diff --git a/pyteal/types.py b/pyteal/types.py index 169507ffa..63b61bd54 100644 --- a/pyteal/types.py +++ b/pyteal/types.py @@ -2,7 +2,7 @@ from enum import Enum from typing import Any -from .errors import TealTypeError, TealInputError +from pyteal.errors import TealTypeError, TealInputError class TealType(Enum): diff --git a/pyteal/types_test.py b/pyteal/types_test.py index c6d38d2a9..d42e60498 100644 --- a/pyteal/types_test.py +++ b/pyteal/types_test.py @@ -1,13 +1,14 @@ -from . import * -from .types import require_type import pytest +import pyteal as pt +from pyteal.types import require_type + def test_require_type(): - require_type(Bytes("str"), TealType.bytes) + require_type(pt.Bytes("str"), pt.TealType.bytes) assert True def test_require_type_invalid(): with pytest.raises(TypeError): - App.globalGet(["This is certainly invalid"]) + pt.App.globalGet(["This is certainly invalid"]) diff --git a/pyteal/util.py b/pyteal/util.py index 6dbbdb62e..614919fc6 100644 --- a/pyteal/util.py +++ b/pyteal/util.py @@ -1,4 +1,4 @@ -from .errors import TealInternalError +from pyteal.errors import TealInternalError def escapeStr(s: str) -> str: diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index c0d217cf1..000000000 --- a/requirements.txt +++ /dev/null @@ -1,5 +0,0 @@ -black==22.3.0 -mypy==0.931 -pytest -pytest-timeout -py-algorand-sdk diff --git a/scripts/generate_init.py b/scripts/generate_init.py index e9a1c85e6..8f5730e04 100644 --- a/scripts/generate_init.py +++ b/scripts/generate_init.py @@ -1,5 +1,8 @@ -import argparse, os, sys, difflib +import argparse from collections import Counter +import difflib +import os +import sys from pyteal import __all__ as static_all diff --git a/setup.py b/setup.py index c761683e5..c09becbd9 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ setuptools.setup( name="pyteal", - version="0.11.0", + version="0.11.1", author="Algorand", author_email="pypiservice@algorand.com", description="Algorand Smart Contracts in Python", @@ -16,6 +16,17 @@ url="https://github.com/algorand/pyteal", packages=setuptools.find_packages(), install_requires=["py-algorand-sdk"], + extras_require={ + "development": [ + "black==22.3.0", + "flake8==4.0.1", + "flake8-tidy-imports==4.6.0", + "mypy==0.942", + "pytest==7.1.1", + "pytest-cov==3.0.0", + "pytest-timeout==2.1.0", + ], + }, classifiers=[ "Programming Language :: Python :: 3", "License :: OSI Approved :: MIT License", diff --git a/tests/compile_test.py b/tests/compile_test.py index 282c8d123..f6ba6d030 100644 --- a/tests/compile_test.py +++ b/tests/compile_test.py @@ -1,7 +1,7 @@ import os import pytest -from pyteal import * +import pyteal as pt def test_basic_bank(): @@ -16,7 +16,7 @@ def test_basic_bank(): ) with open(target_path, "r") as target_file: target = "".join(target_file.readlines()).strip() - assert compileTeal(program, mode=Mode.Signature, version=3) == target + assert pt.compileTeal(program, mode=pt.Mode.Signature, version=3) == target def test_atomic_swap(): @@ -29,7 +29,7 @@ def test_atomic_swap(): ) with open(target_path, "r") as target_file: target = "".join(target_file.readlines()).strip() - assert compileTeal(program, mode=Mode.Signature, version=2) == target + assert pt.compileTeal(program, mode=pt.Mode.Signature, version=2) == target def test_periodic_payment(): @@ -42,7 +42,7 @@ def test_periodic_payment(): ) with open(target_path, "r") as target_file: target = "".join(target_file.readlines()).strip() - assert compileTeal(program, mode=Mode.Signature, version=2) == target + assert pt.compileTeal(program, mode=pt.Mode.Signature, version=2) == target def test_split(): @@ -55,7 +55,7 @@ def test_split(): ) with open(target_path, "r") as target_file: target = "".join(target_file.readlines()).strip() - assert compileTeal(program, mode=Mode.Signature, version=2) == target + assert pt.compileTeal(program, mode=pt.Mode.Signature, version=2) == target def test_dutch_auction(): @@ -68,7 +68,7 @@ def test_dutch_auction(): ) with open(target_path, "r") as target_file: target = "".join(target_file.readlines()).strip() - assert compileTeal(program, mode=Mode.Signature, version=2) == target + assert pt.compileTeal(program, mode=pt.Mode.Signature, version=2) == target def test_recurring_swap(): @@ -81,7 +81,7 @@ def test_recurring_swap(): ) with open(target_path, "r") as target_file: target = "".join(target_file.readlines()).strip() - assert compileTeal(program, mode=Mode.Signature, version=2) == target + assert pt.compileTeal(program, mode=pt.Mode.Signature, version=2) == target def test_asset(): @@ -91,8 +91,8 @@ def test_asset(): clear_state = clear_state_program() # only checking for successful compilation for now - compileTeal(approval, mode=Mode.Application, version=2) - compileTeal(clear_state, mode=Mode.Application, version=2) + pt.compileTeal(approval, mode=pt.Mode.Application, version=2) + pt.compileTeal(clear_state, mode=pt.Mode.Application, version=2) def test_security_token(): @@ -105,8 +105,8 @@ def test_security_token(): clear_state = clear_state_program() # only checking for successful compilation for now - compileTeal(approval, mode=Mode.Application, version=2) - compileTeal(clear_state, mode=Mode.Application, version=2) + pt.compileTeal(approval, mode=pt.Mode.Application, version=2) + pt.compileTeal(clear_state, mode=pt.Mode.Application, version=2) def test_vote(): @@ -116,40 +116,40 @@ def test_vote(): clear_state = clear_state_program() # only checking for successful compilation for now - compileTeal(approval, mode=Mode.Application, version=2) - compileTeal(clear_state, mode=Mode.Application, version=2) + pt.compileTeal(approval, mode=pt.Mode.Application, version=2) + pt.compileTeal(clear_state, mode=pt.Mode.Application, version=2) def test_cond(): - cond1 = Txn.fee() < Int(2000) - cond2 = Txn.amount() > Int(5000) - cond3 = Txn.receiver() == Txn.sender() - core = Cond( - [Global.group_size() == Int(2), cond1], - [Global.group_size() == Int(3), cond2], - [Global.group_size() == Int(4), cond3], + cond1 = pt.Txn.fee() < pt.Int(2000) + cond2 = pt.Txn.amount() > pt.Int(5000) + cond3 = pt.Txn.receiver() == pt.Txn.sender() + core = pt.Cond( + [pt.Global.group_size() == pt.Int(2), cond1], + [pt.Global.group_size() == pt.Int(3), cond2], + [pt.Global.group_size() == pt.Int(4), cond3], ) - compileTeal(core, mode=Mode.Signature, version=2) + pt.compileTeal(core, mode=pt.Mode.Signature, version=2) @pytest.mark.timeout(2) def test_many_ifs(): """ - Test with many If statements to trigger potential corner cases in code generation. + Test with many pt.If statements to trigger potential corner cases in code generation. Previous versions of PyTeal took an exponential time to generate the TEAL code for this PyTEAL. """ - sv = ScratchVar(TealType.uint64) - s = Seq( + sv = pt.ScratchVar(pt.TealType.uint64) + s = pt.Seq( [ - If( - Int(3 * i) == Int(3 * i), - sv.store(Int(3 * i + 1)), - sv.store(Int(3 * i + 2)), + pt.If( + pt.Int(3 * i) == pt.Int(3 * i), + sv.store(pt.Int(3 * i + 1)), + sv.store(pt.Int(3 * i + 2)), ) for i in range(30) ] - + [Return(sv.load())] + + [pt.Return(sv.load())] ) - compileTeal(s, mode=Mode.Signature, version=2) + pt.compileTeal(s, mode=pt.Mode.Signature, version=2) diff --git a/tests/pass_by_ref_test.py b/tests/pass_by_ref_test.py index e0c12d000..f92300caa 100644 --- a/tests/pass_by_ref_test.py +++ b/tests/pass_by_ref_test.py @@ -1,176 +1,178 @@ -from typing import List, Tuple - import pytest -from pyteal import * +import pyteal as pt -from .compile_asserts import assert_new_v_old, compile_and_save +from .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 -#### TESTS FOR NEW PyTEAL THAT USES PASS-BY-REF / DYNAMIC -@Subroutine(TealType.none) -def logcat_dynamic(first: ScratchVar, an_int): - return Seq( - first.store(Concat(first.load(), Itob(an_int))), - Log(first.load()), +# ### TESTS FOR NEW PyTEAL THAT USES PASS-BY-REF / DYNAMIC +@pt.Subroutine(pt.TealType.none) +def logcat_dynamic(first: pt.ScratchVar, an_int): + return pt.Seq( + first.store(pt.Concat(first.load(), pt.Itob(an_int))), + pt.Log(first.load()), ) def sub_logcat_dynamic(): - first = ScratchVar(TealType.bytes) - return Seq( - first.store(Bytes("hello")), - logcat_dynamic(first, Int(42)), - Assert(Bytes("hello42") == first.load()), - Int(1), + first = pt.ScratchVar(pt.TealType.bytes) + return pt.Seq( + first.store(pt.Bytes("hello")), + logcat_dynamic(first, pt.Int(42)), + pt.Assert(pt.Bytes("hello42") == first.load()), + pt.Int(1), ) def wilt_the_stilt(): - player_score = DynamicScratchVar(TealType.uint64) + player_score = pt.DynamicScratchVar(pt.TealType.uint64) - wilt = ScratchVar(TealType.uint64, 129) - kobe = ScratchVar(TealType.uint64) - dt = ScratchVar(TealType.uint64, 131) + wilt = pt.ScratchVar(pt.TealType.uint64, 129) + kobe = pt.ScratchVar(pt.TealType.uint64) + dt = pt.ScratchVar(pt.TealType.uint64, 131) - return Seq( + return pt.Seq( player_score.set_index(wilt), - player_score.store(Int(100)), + player_score.store(pt.Int(100)), player_score.set_index(kobe), - player_score.store(Int(81)), + player_score.store(pt.Int(81)), player_score.set_index(dt), - player_score.store(Int(73)), - Assert(player_score.load() == Int(73)), - Assert(player_score.index() == Int(131)), + player_score.store(pt.Int(73)), + pt.Assert(player_score.load() == pt.Int(73)), + pt.Assert(player_score.index() == pt.Int(131)), player_score.set_index(wilt), - Assert(player_score.load() == Int(100)), - Assert(player_score.index() == Int(129)), - Int(100), + pt.Assert(player_score.load() == pt.Int(100)), + pt.Assert(player_score.index() == pt.Int(129)), + pt.Int(100), ) -@Subroutine(TealType.none) -def swap(x: ScratchVar, y: ScratchVar): - z = ScratchVar(TealType.anytype) - return Seq( +@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()), ) -@Subroutine(TealType.none) +@pt.Subroutine(pt.TealType.none) def cat(x, y): - return Pop(Concat(x, y)) + return pt.Pop(pt.Concat(x, y)) def swapper(): - a = ScratchVar(TealType.bytes) - b = ScratchVar(TealType.bytes) - return Seq( - a.store(Bytes("hello")), - b.store(Bytes("goodbye")), + a = pt.ScratchVar(pt.TealType.bytes) + b = pt.ScratchVar(pt.TealType.bytes) + return pt.Seq( + a.store(pt.Bytes("hello")), + b.store(pt.Bytes("goodbye")), cat(a.load(), b.load()), swap(a, b), - Assert(a.load() == Bytes("goodbye")), - Assert(b.load() == Bytes("hello")), - Int(1000), + pt.Assert(a.load() == pt.Bytes("goodbye")), + pt.Assert(b.load() == pt.Bytes("hello")), + pt.Int(1000), ) -@Subroutine(TealType.uint64) -def mixed_annotations(x: Expr, y: Expr, z: ScratchVar) -> Expr: - return Seq( +@pt.Subroutine(pt.TealType.uint64) +def mixed_annotations(x: pt.Expr, y: pt.Expr, z: pt.ScratchVar) -> pt.Expr: + return pt.Seq( z.store(x), - Log(Concat(y, Bytes("="), Itob(x))), + pt.Log(pt.Concat(y, pt.Bytes("="), pt.Itob(x))), x, ) def sub_mixed(): - x = Int(42) - y = Bytes("x") - z = ScratchVar(TealType.uint64) + x = pt.Int(42) + y = pt.Bytes("x") + z = pt.ScratchVar(pt.TealType.uint64) return mixed_annotations(x, y, z) def lots_o_vars(): - z = Int(0) - one = ScratchVar() - two = ScratchVar() - three = ScratchVar() - four = ScratchVar() - five = Bytes("five") - six = Bytes("six") - seven = Bytes("seven") - eight = Bytes("eight") - nine = Bytes("nine") - ten = Bytes("ten") - eleven = Bytes("eleven") - twelve = Bytes("twelve") - int_cursor = DynamicScratchVar(TealType.uint64) - bytes_cursor = DynamicScratchVar(TealType.bytes) - thirteen = ScratchVar(TealType.uint64, 13) - fourteen = ScratchVar(TealType.bytes, 14) - fifteen = ScratchVar(TealType.uint64) - sixteen = ScratchVar(TealType.bytes) - leet = Int(1337) - ngl = Bytes("NGL: ") + z = pt.Int(0) + one = pt.ScratchVar() + two = pt.ScratchVar() + three = pt.ScratchVar() + four = pt.ScratchVar() + five = pt.Bytes("five") + six = pt.Bytes("six") + seven = pt.Bytes("seven") + eight = pt.Bytes("eight") + nine = pt.Bytes("nine") + ten = pt.Bytes("ten") + eleven = pt.Bytes("eleven") + twelve = pt.Bytes("twelve") + int_cursor = pt.DynamicScratchVar(pt.TealType.uint64) + bytes_cursor = pt.DynamicScratchVar(pt.TealType.bytes) + thirteen = pt.ScratchVar(pt.TealType.uint64, 13) + fourteen = pt.ScratchVar(pt.TealType.bytes, 14) + fifteen = pt.ScratchVar(pt.TealType.uint64) + sixteen = pt.ScratchVar(pt.TealType.bytes) + leet = pt.Int(1337) + ngl = pt.Bytes("NGL: ") return ( - If(Or(App.id() == Int(0), Txn.application_args.length() == Int(0))) - .Then(Int(1)) + pt.If( + pt.Or( + pt.App.id() == pt.Int(0), pt.Txn.application_args.length() == pt.Int(0) + ) + ) + .Then(pt.Int(1)) .Else( - Seq( - one.store(Int(1)), - two.store(Bytes("two")), - three.store(Int(3)), - four.store(Bytes("four")), - App.localPut(z, five, Int(5)), - App.localPut(z, six, six), - App.localPut(z, seven, Int(7)), - App.localPut(z, eight, eight), - App.globalPut(nine, Int(9)), - App.globalPut(ten, ten), - App.globalPut(eleven, Int(11)), - App.globalPut(twelve, twelve), + pt.Seq( + one.store(pt.Int(1)), + two.store(pt.Bytes("two")), + three.store(pt.Int(3)), + four.store(pt.Bytes("four")), + pt.App.localPut(z, five, pt.Int(5)), + pt.App.localPut(z, six, six), + pt.App.localPut(z, seven, pt.Int(7)), + pt.App.localPut(z, eight, eight), + pt.App.globalPut(nine, pt.Int(9)), + pt.App.globalPut(ten, ten), + pt.App.globalPut(eleven, pt.Int(11)), + pt.App.globalPut(twelve, twelve), one.store(one.load() + leet), - two.store(Concat(ngl, two.load())), + two.store(pt.Concat(ngl, two.load())), three.store(three.load() + leet), - four.store(Concat(ngl, four.load())), - App.localPut(z, five, leet + App.localGet(z, five)), - App.localPut(z, six, Concat(ngl, App.localGet(z, six))), - App.localPut(z, seven, App.localGet(z, seven)), - App.localPut(z, eight, Concat(ngl, App.localGet(z, eight))), - App.globalPut(nine, leet + App.globalGet(nine)), - App.globalPut(ten, Concat(ngl, App.globalGet(ten))), - App.globalPut(eleven, leet + App.globalGet(eleven)), - App.globalPut(twelve, Concat(ngl, App.globalGet(twelve))), - thirteen.store(Btoi(Txn.application_args[0])), - fourteen.store(Txn.application_args[1]), - fifteen.store(Btoi(Txn.application_args[2])), - sixteen.store(Txn.application_args[3]), - Pop(one.load()), - Pop(two.load()), - Pop(three.load()), - Pop(four.load()), - Pop(App.localGet(z, five)), - Pop(App.localGet(z, six)), - Pop(App.localGet(z, seven)), - Pop(App.localGet(z, eight)), - Pop(App.globalGet(nine)), - Pop(App.globalGet(ten)), - Pop(App.globalGet(eleven)), - Pop(App.globalGet(twelve)), + four.store(pt.Concat(ngl, four.load())), + pt.App.localPut(z, five, leet + pt.App.localGet(z, five)), + pt.App.localPut(z, six, pt.Concat(ngl, pt.App.localGet(z, six))), + pt.App.localPut(z, seven, pt.App.localGet(z, seven)), + pt.App.localPut(z, eight, pt.Concat(ngl, pt.App.localGet(z, eight))), + pt.App.globalPut(nine, leet + pt.App.globalGet(nine)), + pt.App.globalPut(ten, pt.Concat(ngl, pt.App.globalGet(ten))), + pt.App.globalPut(eleven, leet + pt.App.globalGet(eleven)), + pt.App.globalPut(twelve, pt.Concat(ngl, pt.App.globalGet(twelve))), + thirteen.store(pt.Btoi(pt.Txn.application_args[0])), + fourteen.store(pt.Txn.application_args[1]), + fifteen.store(pt.Btoi(pt.Txn.application_args[2])), + sixteen.store(pt.Txn.application_args[3]), + pt.Pop(one.load()), + pt.Pop(two.load()), + pt.Pop(three.load()), + pt.Pop(four.load()), + pt.Pop(pt.App.localGet(z, five)), + pt.Pop(pt.App.localGet(z, six)), + pt.Pop(pt.App.localGet(z, seven)), + pt.Pop(pt.App.localGet(z, eight)), + pt.Pop(pt.App.globalGet(nine)), + pt.Pop(pt.App.globalGet(ten)), + pt.Pop(pt.App.globalGet(eleven)), + pt.Pop(pt.App.globalGet(twelve)), int_cursor.set_index(thirteen), - Log(Itob(int_cursor.load())), + pt.Log(pt.Itob(int_cursor.load())), bytes_cursor.set_index(fourteen), - Log(bytes_cursor.load()), + pt.Log(bytes_cursor.load()), int_cursor.set_index(fifteen), - Log(Itob(int_cursor.load())), + pt.Log(pt.Itob(int_cursor.load())), bytes_cursor.set_index(sixteen), - Log(bytes_cursor.load()), + pt.Log(bytes_cursor.load()), leet, ) ) @@ -178,33 +180,33 @@ def lots_o_vars(): def empty_scratches(): - cursor = DynamicScratchVar() - i1 = ScratchVar(TealType.uint64, 0) - i2 = ScratchVar(TealType.uint64, 2) - i3 = ScratchVar(TealType.uint64, 4) - s1 = ScratchVar(TealType.bytes, 1) - s2 = ScratchVar(TealType.bytes, 3) - s3 = ScratchVar(TealType.bytes, 5) - return Seq( + cursor = pt.DynamicScratchVar() + i1 = pt.ScratchVar(pt.TealType.uint64, 0) + i2 = pt.ScratchVar(pt.TealType.uint64, 2) + i3 = pt.ScratchVar(pt.TealType.uint64, 4) + s1 = pt.ScratchVar(pt.TealType.bytes, 1) + s2 = pt.ScratchVar(pt.TealType.bytes, 3) + s3 = pt.ScratchVar(pt.TealType.bytes, 5) + return pt.Seq( cursor.set_index(i1), - cursor.store(Int(0)), + cursor.store(pt.Int(0)), cursor.set_index(s1), - cursor.store(Bytes("")), + cursor.store(pt.Bytes("")), cursor.set_index(i2), - cursor.store(Int(0)), + cursor.store(pt.Int(0)), cursor.set_index(s2), - cursor.store(Bytes("")), + cursor.store(pt.Bytes("")), cursor.set_index(i3), - cursor.store(Int(0)), + cursor.store(pt.Int(0)), cursor.set_index(s3), - cursor.store(Bytes("")), - Int(42), + cursor.store(pt.Bytes("")), + pt.Int(42), ) -@Subroutine(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))) CASES = ( @@ -223,41 +225,41 @@ def test_teal_output_is_unchanged(pt): assert_new_v_old(pt, 6) -##### Subroutine Definitions for pass-by-ref guardrails testing ##### -@Subroutine(TealType.uint64) +# #### pt.Subroutine Definitions for pass-by-ref guardrails testing ##### +@pt.Subroutine(pt.TealType.uint64) def ok(x): # not really ok at runtime... but should be ok at compile time return ok(x) -@Subroutine(TealType.uint64) -def ok_byref(x: ScratchVar): - return Int(42) +@pt.Subroutine(pt.TealType.uint64) +def ok_byref(x: pt.ScratchVar): + return pt.Int(42) -@Subroutine(TealType.uint64) +@pt.Subroutine(pt.TealType.uint64) def ok_indirect1(x): return ok_indirect2(x) -@Subroutine(TealType.uint64) +@pt.Subroutine(pt.TealType.uint64) def ok_indirect2(x): return ok_indirect1(x) -@Subroutine(TealType.uint64) -def not_ok(x: ScratchVar): +@pt.Subroutine(pt.TealType.uint64) +def not_ok(x: pt.ScratchVar): # not ok both at compile and runtime return not_ok(x) -@Subroutine(TealType.uint64) -def not_ok_indirect1(x: ScratchVar): +@pt.Subroutine(pt.TealType.uint64) +def not_ok_indirect1(x: pt.ScratchVar): return not_ok_indirect2(x) -@Subroutine(TealType.uint64) -def not_ok_indirect2(x: ScratchVar): +@pt.Subroutine(pt.TealType.uint64) +def not_ok_indirect2(x: pt.ScratchVar): return not_ok_indirect1(x) @@ -278,96 +280,96 @@ def not_ok_indirect2(x: ScratchVar): """ -@Subroutine(TealType.uint64) +@pt.Subroutine(pt.TealType.uint64) def a(x): - tmp = ScratchVar(TealType.uint64) - return Seq(tmp.store(x), b(x) + c(tmp) + d(x)) + tmp = pt.ScratchVar(pt.TealType.uint64) + return pt.Seq(tmp.store(x), b(x) + c(tmp) + d(x)) -@Subroutine(TealType.uint64) +@pt.Subroutine(pt.TealType.uint64) def b(x): return a(x) + e(x) * f(x) -@Subroutine(TealType.uint64) -def c(x: ScratchVar): - return g(Int(42)) - h(x.load()) +@pt.Subroutine(pt.TealType.uint64) +def c(x: pt.ScratchVar): + return g(pt.Int(42)) - h(x.load()) -@Subroutine(TealType.uint64) +@pt.Subroutine(pt.TealType.uint64) def d(x): - return d(x) + i(Int(11)) * j(x) + return d(x) + i(pt.Int(11)) * j(x) -@Subroutine(TealType.uint64) +@pt.Subroutine(pt.TealType.uint64) def e(x): - return Int(42) + return pt.Int(42) -@Subroutine(TealType.uint64) +@pt.Subroutine(pt.TealType.uint64) def f(x): - return Int(42) + return pt.Int(42) -@Subroutine(TealType.uint64) +@pt.Subroutine(pt.TealType.uint64) def g(x): - return a(Int(17)) + return a(pt.Int(17)) -@Subroutine(TealType.uint64) +@pt.Subroutine(pt.TealType.uint64) def h(x): - return Int(42) + return pt.Int(42) -@Subroutine(TealType.uint64) +@pt.Subroutine(pt.TealType.uint64) def i(x): - return Int(42) + return pt.Int(42) -@Subroutine(TealType.uint64) +@pt.Subroutine(pt.TealType.uint64) def j(x): - return Int(42) + return pt.Int(42) -@Subroutine(TealType.none) -def tally(n, result: ScratchVar): +@pt.Subroutine(pt.TealType.none) +def tally(n, result: pt.ScratchVar): return ( - If(n == Int(0)) - .Then(result.store(Bytes(""))) + pt.If(n == pt.Int(0)) + .Then(result.store(pt.Bytes(""))) .Else( - Seq( - tally(n - Int(1), result), - result.store(Concat(result.load(), Bytes("a"))), + pt.Seq( + tally(n - pt.Int(1), result), + result.store(pt.Concat(result.load(), pt.Bytes("a"))), ) ) ) -@Subroutine(TealType.none) -def subr_string_mult(s: ScratchVar, n): - tmp = ScratchVar(TealType.bytes) +@pt.Subroutine(pt.TealType.none) +def subr_string_mult(s: pt.ScratchVar, n): + tmp = pt.ScratchVar(pt.TealType.bytes) return ( - If(n == Int(0)) - .Then(s.store(Bytes(""))) + pt.If(n == pt.Int(0)) + .Then(s.store(pt.Bytes(""))) .Else( - Seq( + pt.Seq( tmp.store(s.load()), - subr_string_mult(s, n - Int(1)), - s.store(Concat(s.load(), tmp.load())), + subr_string_mult(s, n - pt.Int(1)), + s.store(pt.Concat(s.load(), tmp.load())), ) ) ) -@Subroutine(TealType.none) -def factorial_BAD(n: ScratchVar): - tmp = ScratchVar(TealType.uint64) +@pt.Subroutine(pt.TealType.none) +def factorial_BAD(n: pt.ScratchVar): + tmp = pt.ScratchVar(pt.TealType.uint64) return ( - If(n.load() <= Int(1)) - .Then(n.store(Int(1))) + pt.If(n.load() <= pt.Int(1)) + .Then(n.store(pt.Int(1))) .Else( - Seq( - tmp.store(n.load() - Int(1)), + pt.Seq( + tmp.store(n.load() - pt.Int(1)), factorial_BAD(tmp), n.store(n.load() * tmp.load()), ) @@ -375,16 +377,16 @@ def factorial_BAD(n: ScratchVar): ) -@Subroutine(TealType.none) -def factorial(n: ScratchVar): - tmp = ScratchVar(TealType.uint64) +@pt.Subroutine(pt.TealType.none) +def factorial(n: pt.ScratchVar): + tmp = pt.ScratchVar(pt.TealType.uint64) return ( - If(n.load() <= Int(1)) - .Then(n.store(Int(1))) + pt.If(n.load() <= pt.Int(1)) + .Then(n.store(pt.Int(1))) .Else( - Seq( + pt.Seq( tmp.store(n.load()), - n.store(n.load() - Int(1)), + n.store(n.load() - pt.Int(1)), factorial(n), n.store(n.load() * tmp.load()), ) @@ -392,82 +394,90 @@ def factorial(n: ScratchVar): ) -@Subroutine(TealType.none) -def plus_one(n: ScratchVar): - tmp = ScratchVar(TealType.uint64) +@pt.Subroutine(pt.TealType.none) +def plus_one(n: pt.ScratchVar): + tmp = pt.ScratchVar(pt.TealType.uint64) return ( - If(n.load() == Int(0)) - .Then(n.store(Int(1))) + pt.If(n.load() == pt.Int(0)) + .Then(n.store(pt.Int(1))) .Else( - Seq( - tmp.store(n.load() - Int(1)), + pt.Seq( + tmp.store(n.load() - pt.Int(1)), plus_one(tmp), - n.store(tmp.load() + Int(1)), + n.store(tmp.load() + pt.Int(1)), ) ) ) -##### Approval PyTEAL Expressions (COPACETIC) ##### +# #### Approval PyTEAL Expressions (COPACETIC) ##### -approval_ok = ok(Int(42)) +approval_ok = ok(pt.Int(42)) -x_scratchvar = ScratchVar(TealType.uint64) +x_scratchvar = pt.ScratchVar(pt.TealType.uint64) -approval_ok_byref = Seq(x_scratchvar.store(Int(42)), ok_byref(x_scratchvar)) +approval_ok_byref = pt.Seq(x_scratchvar.store(pt.Int(42)), ok_byref(x_scratchvar)) -approval_ok_indirect = ok_indirect1(Int(42)) +approval_ok_indirect = ok_indirect1(pt.Int(42)) -##### BANNED Approval PyTEAL Expressions (wrapped in a function) ##### +# #### BANNED Approval PyTEAL Expressions (wrapped in a function) ##### -approval_not_ok = lambda: Seq(x_scratchvar.store(Int(42)), not_ok(x_scratchvar)) -approval_not_ok_indirect = lambda: Seq( - x_scratchvar.store(Int(42)), not_ok_indirect1(x_scratchvar) -) +def approval_not_ok(): + return pt.Seq(x_scratchvar.store(pt.Int(42)), not_ok(x_scratchvar)) + + +def approval_not_ok_indirect(): + return pt.Seq(x_scratchvar.store(pt.Int(42)), not_ok_indirect1(x_scratchvar)) -approval_its_complicated = lambda: a(Int(42)) + +def approval_its_complicated(): + return a(pt.Int(42)) def tallygo(): - result = ScratchVar(TealType.bytes) - # If-Then is a hook for creating + opting in without providing any args + result = pt.ScratchVar(pt.TealType.bytes) + # pt.If-Then is a hook for creating + opting in without providing any args return ( - If(Or(App.id() == Int(0), Txn.application_args.length() == Int(0))) - .Then(Int(1)) + pt.If( + pt.Or( + pt.App.id() == pt.Int(0), pt.Txn.application_args.length() == pt.Int(0) + ) + ) + .Then(pt.Int(1)) .Else( - Seq( - result.store(Bytes("dummy")), - tally(Int(4), result), - Btoi(result.load()), + pt.Seq( + result.store(pt.Bytes("dummy")), + tally(pt.Int(4), result), + pt.Btoi(result.load()), ) ) ) def string_mult(): - s = ScratchVar(TealType.bytes) - return Seq( - s.store(Txn.application_args[0]), - subr_string_mult(s, Btoi(Txn.application_args[1])), - Log(s.load()), - Int(100), + s = pt.ScratchVar(pt.TealType.bytes) + return pt.Seq( + s.store(pt.Txn.application_args[0]), + subr_string_mult(s, pt.Btoi(pt.Txn.application_args[1])), + pt.Log(s.load()), + pt.Int(100), ) def fac_by_ref_BAD(): - n = ScratchVar(TealType.uint64) - return Seq( - n.store(Int(10)), + n = pt.ScratchVar(pt.TealType.uint64) + return pt.Seq( + n.store(pt.Int(10)), factorial_BAD(n), n.load(), ) def fac_by_ref(): - n = ScratchVar(TealType.uint64) - return Seq( - n.store(Int(10)), + n = pt.ScratchVar(pt.TealType.uint64) + return pt.Seq( + n.store(pt.Int(10)), factorial(n), n.load(), ) @@ -475,13 +485,17 @@ def fac_by_ref(): # Proved correct via blackbox testing, but BANNING for now def fac_by_ref_args(): - n = ScratchVar(TealType.uint64) - return Seq( - If(Or(App.id() == Int(0), Txn.application_args.length() == Int(0))) - .Then(Int(1)) + 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( - Seq( - n.store(Btoi(Txn.application_args[0])), + pt.Seq( + n.store(pt.Btoi(pt.Txn.application_args[0])), factorial(n), n.load(), ) @@ -490,8 +504,8 @@ def fac_by_ref_args(): def increment(): - n = ScratchVar(TealType.uint64) - return Seq(n.store(Int(4)), plus_one(n), Int(1)) + n = pt.ScratchVar(pt.TealType.uint64) + return pt.Seq(n.store(pt.Int(4)), plus_one(n), pt.Int(1)) COPACETIC_APPROVALS = [approval_ok, approval_ok_byref, approval_ok_indirect] @@ -511,14 +525,14 @@ def increment(): @pytest.mark.parametrize("approval", COPACETIC_APPROVALS) def test_pass_by_ref_guardrails_COPACETIC(approval): - assert compileTeal(approval, Mode.Application, version=6) + 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 - with pytest.raises(TealInputError) as err: - compileTeal(approval(), Mode.Application, version=6) + with pytest.raises(pt.TealInputError) as err: + pt.compileTeal(approval(), 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) diff --git a/tests/user_guide_test.py b/tests/user_guide_test.py index 57134ac3f..feb309161 100644 --- a/tests/user_guide_test.py +++ b/tests/user_guide_test.py @@ -1,55 +1,54 @@ -import re import pytest -from pyteal import * +import pyteal as pt -from .compile_asserts import assert_new_v_old, compile_and_save +from .compile_asserts import assert_new_v_old -def user_guide_snippet_dynamic_scratch_var() -> Expr: +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. + 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 = ScratchVar(TealType.uint64) - d = DynamicScratchVar(TealType.uint64) + s = pt.ScratchVar(pt.TealType.uint64) + d = pt.DynamicScratchVar(pt.TealType.uint64) - return Seq( + return pt.Seq( d.set_index(s), - s.store(Int(7)), - d.store(d.load() + Int(3)), - Assert(s.load() == Int(10)), - Int(1), + 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(): - @Subroutine(TealType.uint64) + @pt.Subroutine(pt.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))) + 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(Int(15)) + return recursiveIsEven(pt.Int(15)) def user_guide_snippet_ILLEGAL_recursion(): - @Subroutine(TealType.none) - def ILLEGAL_recursion(i: ScratchVar): + @pt.Subroutine(pt.TealType.none) + def ILLEGAL_recursion(i: pt.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))) + 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 = ScratchVar(TealType.uint64) - return Seq(i.store(Int(15)), ILLEGAL_recursion(i), Int(1)) + i = pt.ScratchVar(pt.TealType.uint64) + return pt.Seq(i.store(pt.Int(15)), ILLEGAL_recursion(i), pt.Int(1)) USER_GUIDE_SNIPPETS_COPACETIC = [ @@ -65,7 +64,7 @@ def test_user_guide_snippets_good(snippet): USER_GUIDE_SNIPPETS_ERRORING = { user_guide_snippet_ILLEGAL_recursion: ( - TealInputError, + pt.TealInputError, "ScratchVar arguments not allowed in recursive subroutines, but a recursive call-path was detected: ILLEGAL_recursion()-->ILLEGAL_recursion()", ) } @@ -80,6 +79,6 @@ def test_user_guide_snippets_bad(snippet_etype_e): f"Test case function=[{snippet.__name__}]. Expecting error of type {etype} with message <{e}>" ) with pytest.raises(etype) as tie: - compileTeal(snippet(), mode=Mode.Application, version=6) + pt.compileTeal(snippet(), mode=pt.Mode.Application, version=6) assert e in str(tie) From a18c47d3919db412953804873623ac9155a68337 Mon Sep 17 00:00:00 2001 From: Ben Guidarelli Date: Thu, 28 Apr 2022 11:07:10 -0400 Subject: [PATCH 073/188] Export Router (#306) * Merge branch 'master' into feature/abi (#284) * Move to pyteal as pt in ABI tests with concise prefix (#286) * ABI Strings (#278) * Move to pyteal as pt in #278 (#287) * Merge absolute imports into feature/abi (#288) * Remove temporary I252 ignore on pyteal.ast.abi (#290) * Fix abi import (#303) * Fix abi import * ignore flake8 * move router to ast Co-authored-by: Michael Diamant Co-authored-by: Jason Paulos --- pyteal/__init__.pyi | 1 + pyteal/ast/__init__.py | 3 +++ pyteal/ast/{abi => }/router.py | 2 +- pyteal/ast/{abi => }/router_test.py | 0 4 files changed, 5 insertions(+), 1 deletion(-) rename pyteal/ast/{abi => }/router.py (100%) rename pyteal/ast/{abi => }/router_test.py (100%) diff --git a/pyteal/__init__.pyi b/pyteal/__init__.pyi index 1e1d18553..1373403bd 100644 --- a/pyteal/__init__.pyi +++ b/pyteal/__init__.pyi @@ -140,6 +140,7 @@ __all__ = [ "RETURN_METHOD_SELECTOR", "Reject", "Return", + "Router", "ScratchIndex", "ScratchLoad", "ScratchSlot", diff --git a/pyteal/ast/__init__.py b/pyteal/ast/__init__.py index 43efc9004..342e73d44 100644 --- a/pyteal/ast/__init__.py +++ b/pyteal/ast/__init__.py @@ -136,6 +136,8 @@ from pyteal.ast.maybe import MaybeValue from pyteal.ast.multi import MultiValue +from pyteal.ast.router import Router + # abi import pyteal.ast.abi as abi # noqa: I250 @@ -273,5 +275,6 @@ "For", "Break", "Continue", + "Router", "abi", ] diff --git a/pyteal/ast/abi/router.py b/pyteal/ast/router.py similarity index 100% rename from pyteal/ast/abi/router.py rename to pyteal/ast/router.py index 375820eca..ca1364282 100644 --- a/pyteal/ast/abi/router.py +++ b/pyteal/ast/router.py @@ -4,12 +4,12 @@ from pyteal.config import METHOD_ARG_NUM_LIMIT from pyteal.errors import TealInputError from pyteal.types import TealType +from pyteal.ast.subroutine import SubroutineFnWrapper from pyteal.ast.cond import Cond from pyteal.ast.expr import Expr from pyteal.ast.app import OnComplete, EnumInt from pyteal.ast.int import Int from pyteal.ast.seq import Seq -from pyteal.ast.subroutine import SubroutineFnWrapper from pyteal.ast.methodsig import MethodSignature from pyteal.ast.naryexpr import And, Or from pyteal.ast.txn import Txn diff --git a/pyteal/ast/abi/router_test.py b/pyteal/ast/router_test.py similarity index 100% rename from pyteal/ast/abi/router_test.py rename to pyteal/ast/router_test.py From 5d78129093cc71f4544c239804d6661e2ddde044 Mon Sep 17 00:00:00 2001 From: Ben Guidarelli Date: Thu, 28 Apr 2022 11:23:00 -0400 Subject: [PATCH 074/188] fix spacing issue --- pyteal/__init__.py | 1 - pyteal/__init__.pyi | 1 - pyteal/ast/__init__.py | 1 - 3 files changed, 3 deletions(-) diff --git a/pyteal/__init__.py b/pyteal/__init__.py index d754ea90b..cc4e7ca76 100644 --- a/pyteal/__init__.py +++ b/pyteal/__init__.py @@ -24,7 +24,6 @@ METHOD_ARG_NUM_LIMIT, ) - # begin __all__ __all__ = ( ast_all diff --git a/pyteal/__init__.pyi b/pyteal/__init__.pyi index 06859021c..1373403bd 100644 --- a/pyteal/__init__.pyi +++ b/pyteal/__init__.pyi @@ -27,7 +27,6 @@ from pyteal.config import ( METHOD_ARG_NUM_LIMIT, ) - __all__ = [ "AccountParam", "Add", diff --git a/pyteal/ast/__init__.py b/pyteal/ast/__init__.py index e71376812..342e73d44 100644 --- a/pyteal/ast/__init__.py +++ b/pyteal/ast/__init__.py @@ -138,7 +138,6 @@ from pyteal.ast.router import Router - # abi import pyteal.ast.abi as abi # noqa: I250 From 96f7ede0211b9a032972be2b23af2751ef95921b Mon Sep 17 00:00:00 2001 From: Michael Diamant Date: Fri, 29 Apr 2022 12:35:57 -0400 Subject: [PATCH 075/188] 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 076/188] 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 077/188] 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 078/188] 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 079/188] 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 080/188] 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 081/188] 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 082/188] 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 083/188] 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 084/188] 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 085/188] 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 69d389b1d19a934d3a540c5cebc54425a21c4365 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Wed, 4 May 2022 14:21:38 -0400 Subject: [PATCH 086/188] 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 f42339d52acfb5f0559da52cbac68aa67f78353d Mon Sep 17 00:00:00 2001 From: Hang Su Date: Wed, 4 May 2022 18:07:32 -0400 Subject: [PATCH 087/188] 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 59226b25ab7e07396f4fffd75c251606193f0748 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Thu, 5 May 2022 10:41:08 -0400 Subject: [PATCH 088/188] 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 089/188] 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 090/188] 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 594110823f3e0f7d1ea71f33c350f4042af488a3 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Thu, 5 May 2022 12:36:55 -0400 Subject: [PATCH 091/188] 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 092/188] 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 093/188] 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 a262e3dd3294a49401b59578c8ec714031d04aba Mon Sep 17 00:00:00 2001 From: Hang Su Date: Fri, 6 May 2022 11:59:59 -0400 Subject: [PATCH 094/188] squash merge abi subroutine atm, merge again after abi subroutine into feature/abi --- pyteal/__init__.pyi | 1 + pyteal/ast/__init__.py | 4 +- pyteal/ast/abi/__init__.py | 3 +- pyteal/ast/abi/type.py | 21 +- pyteal/ast/abi/util.py | 2 +- pyteal/ast/router.py | 4 +- pyteal/ast/subroutine.py | 443 ++++++++++++++++++++++------- pyteal/ast/subroutine_test.py | 498 +++++++++++++++++++++++++++++---- pyteal/compiler/compiler.py | 2 +- pyteal/compiler/subroutines.py | 2 +- 10 files changed, 818 insertions(+), 162 deletions(-) diff --git a/pyteal/__init__.pyi b/pyteal/__init__.pyi index 953eb5c9e..9e9721b52 100644 --- a/pyteal/__init__.pyi +++ b/pyteal/__init__.pyi @@ -28,6 +28,7 @@ from pyteal.config import ( ) __all__ = [ + "ABIReturnSubroutine", "AccountParam", "Add", "Addr", diff --git a/pyteal/ast/__init__.py b/pyteal/ast/__init__.py index cfb410b0e..f02d72c4c 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, Add, Mul, Or, Concat +from pyteal.ast.naryexpr import NaryExpr, Add, And, Mul, Or, Concat from pyteal.ast.widemath import WideRatio # control flow @@ -118,6 +118,7 @@ SubroutineDeclaration, SubroutineCall, SubroutineFnWrapper, + ABIReturnSubroutine, ) from pyteal.ast.while_ import While from pyteal.ast.for_ import For @@ -242,6 +243,7 @@ "SubroutineDeclaration", "SubroutineCall", "SubroutineFnWrapper", + "ABIReturnSubroutine", "ScratchIndex", "ScratchLoad", "ScratchSlot", diff --git a/pyteal/ast/abi/__init__.py b/pyteal/ast/abi/__init__.py index 47e61cbab..b9cb974ce 100644 --- a/pyteal/ast/abi/__init__.py +++ b/pyteal/ast/abi/__init__.py @@ -4,7 +4,7 @@ Address, AddressLength, ) -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, @@ -47,6 +47,7 @@ "TypeSpec", "BaseType", "ComputedValue", + "ReturnedValue", "BoolTypeSpec", "Bool", "UintTypeSpec", diff --git a/pyteal/ast/abi/type.py b/pyteal/ast/abi/type.py index 97304062f..4832545e8 100644 --- a/pyteal/ast/abi/type.py +++ b/pyteal/ast/abi/type.py @@ -147,7 +147,7 @@ class ComputedValue(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 @@ -182,3 +182,22 @@ def use(self, action: Callable[[T], Expr]) -> Expr: ComputedValue.__module__ = "pyteal" + + +class ReturnedValue(ComputedValue): + 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.type_spec + + def store_into(self, output: BaseType) -> Expr: + if output.type_spec() != self.produced_type_spec(): + raise TealInputError( + f"expected type_spec {self.produced_type_spec()} but get {output.type_spec()}" + ) + return output.stored_value.store(self.computation) + + +ReturnedValue.__module__ = "pyteal" diff --git a/pyteal/ast/abi/util.py b/pyteal/ast/abi/util.py index d3393f3e7..6cd3aa7c9 100644 --- a/pyteal/ast/abi/util.py +++ b/pyteal/ast/abi/util.py @@ -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: diff --git a/pyteal/ast/router.py b/pyteal/ast/router.py index ca1364282..dc1b3afac 100644 --- a/pyteal/ast/router.py +++ b/pyteal/ast/router.py @@ -76,7 +76,7 @@ def __parseConditions( And( Txn.application_args[0] == MethodSignature(mReg.name()), Txn.application_args.length() - == Int(1 + max(mReg.subroutine.argumentCount(), METHOD_ARG_NUM_LIMIT)), + == Int(1 + max(mReg.subroutine.argument_count(), METHOD_ARG_NUM_LIMIT)), ) if mReg is not None else Txn.application_args.length() == Int(0) @@ -137,7 +137,7 @@ def __wrapHandler(isMethod: bool, branch: Union[SubroutineFnWrapper, Expr]) -> E if isinstance(branch, SubroutineFnWrapper) and branch.has_return(): # TODO need to encode/decode things # execBranchArgs: List[Expr] = [] - if branch.subroutine.argumentCount() >= METHOD_ARG_NUM_LIMIT: + if branch.subroutine.argument_count() >= METHOD_ARG_NUM_LIMIT: # NOTE decode (if arg num > 15 need to de-tuple 15th (last) argument) pass else: diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index 41aafe7e2..0d43ab2c4 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -1,20 +1,17 @@ -from inspect import get_annotations, Parameter, signature +from dataclasses import dataclass +from inspect import isclass, Parameter, signature, get_annotations +from types import MappingProxyType from typing import ( Callable, - Dict, - List, Optional, - Union, TYPE_CHECKING, - Tuple, 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 pyteal.ast.scratchvar import DynamicScratchVar, ScratchVar from pyteal.errors import TealInputError, verifyTealVersion @@ -37,6 +34,7 @@ def __init__( implementation: Callable[..., Expr], return_type: TealType, name_str: Optional[str] = None, + abi_output_arg_name: Optional[str] = None, ) -> None: """ Args: @@ -44,6 +42,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. """ super().__init__() self.id = SubroutineDefinition.nextSubroutineId @@ -53,38 +52,35 @@ def __init__( self.declaration: Optional["SubroutineDeclaration"] = None self.implementation: Callable = implementation + self.abi_output_arg_name: Optional[str] = abi_output_arg_name + + 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] ( - impl_params, - annotations, - expected_arg_types, - by_ref_args, - abi_args, + self.implementation_params, + self.annotations, + self.expected_arg_types, + self.by_ref_args, + self.abi_args, + self.output_kwarg, ) = self._validate() - self.implementation_params: MappingProxyType[str, Parameter] = impl_params - self.annotations: dict[str, Expr | ScratchVar] = annotations - self.expected_arg_types: list[type | TypeSpec] = expected_arg_types - self.by_ref_args: set[str] = by_ref_args - self.abi_args: Dict[str, abi.TypeSpec] = abi_args self.__name: str = name_str if name_str else self.implementation.__name__ - @staticmethod - def is_abi_annotation(obj: Any) -> bool: - try: - abi.type_spec_from_annotation(obj) - return True - except TypeError: - return False - def _validate( self, input_types: list[TealType] = None ) -> tuple[ MappingProxyType[str, Parameter], - dict[str, Expr | ScratchVar], - list[type | TypeSpec], + dict[str, type], + list[type[Expr] | type[ScratchVar] | abi.TypeSpec], set[str], - Dict[str, abi.TypeSpec], + dict[str, abi.TypeSpec], + dict[str, abi.TypeSpec], ]: """Validate the full function signature and annotations for subroutine definition. @@ -108,35 +104,58 @@ 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, containing the TealType of input expression. + 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. """ - implementation = self.implementation - if not callable(implementation): + + if not callable(self.implementation): raise TealInputError("Input to SubroutineDefinition is not callable") - implementation_params: MappingProxyType[str, Parameter] = signature( - implementation + impl_params: MappingProxyType[str, Parameter] = signature( + self.implementation ).parameters - annotations: dict[str, Expr | ScratchVar] = get_annotations(implementation) - expected_arg_types: list[type | TypeSpec] = [] + 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] = {} - if input_types is not None and len(input_types) != len(implementation_params): - raise TealInputError( - f"Provided number of input_types ({len(input_types)}) " - f"does not match detected number of parameters ({len(implementation_params)})" - ) + if input_types is not None: + 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 i, (name, param) in enumerate(implementation_params.items()): + 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 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: parameter {name} with type {param.kind}" @@ -147,35 +166,47 @@ def _validate( f"Function has a parameter with a default value, which is not allowed in a subroutine: {name}" ) - if input_types: - intype = input_types[i] - if not isinstance(intype, TealType): + 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 has input type {intype} for parameter {name} which is not a TealType" + f"Function keyword parameter {name} has type {expected_arg_type}" ) + abi_output_kwarg[name] = expected_arg_type + continue - expected_arg_type = self._validate_parameter_type(annotations, name) - - 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 ( - implementation_params, + impl_params, annotations, - expected_arg_types, + arg_types, by_ref_args, abi_args, + abi_output_kwarg, ) @staticmethod - def _validate_parameter_type( - user_defined_annotations: dict, parameter_name: str - ) -> type | TypeSpec: - ptype = user_defined_annotations.get(parameter_name, None) + 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: @@ -188,40 +219,46 @@ 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 - else: - if ptype in (Expr, ScratchVar): - return ptype - - if SubroutineDefinition.is_abi_annotation(ptype): - return abi.type_spec_from_annotation(ptype) - + 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 disallowed type {ptype}. " - f"Only the types {(Expr, ScratchVar, 'ABI')} are allowed" + 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 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: - return len(self.implementation_params) + def argument_count(self) -> int: + return len(self.arguments()) - def arguments(self) -> List[str]: - return list(self.implementation_params.keys()) + def arguments(self) -> list[str]: + syntax_args = list(self.implementation_params.keys()) + syntax_args = [ + arg_name for arg_name in syntax_args if arg_name not in self.output_kwarg + ] + return syntax_args def invoke( - self, args: List[Union[Expr, ScratchVar, abi.BaseType]] + self, + args: list[Expr | ScratchVar | abi.BaseType], ) -> "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.arguments()} arguments, got {len(args)} arguments" ) for i, arg in enumerate(args): @@ -245,7 +282,9 @@ def invoke( f"should have ABI typespec {arg_type} but got {arg.type_spec()}" ) - return SubroutineCall(self, args) + return SubroutineCall( + self, args, output_kwarg=OutputKwArgInfo.from_dict(self.output_kwarg) + ) def __str__(self): return f"subroutine#{self.id}" @@ -284,15 +323,39 @@ 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"]: + 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" + + class SubroutineCall(Expr): def __init__( self, subroutine: SubroutineDefinition, - args: List[Union[Expr, ScratchVar, abi.BaseType]], + args: list[Expr | ScratchVar | abi.BaseType], + *, + output_kwarg: OutputKwArgInfo = 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): @@ -315,7 +378,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 @@ -325,6 +388,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) 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, @@ -332,7 +399,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): @@ -345,14 +412,15 @@ def handle_arg(arg: Union[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)) + return TealBlock.FromOp(options, op, *[handle_arg(x) for x in self.args]) 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.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): return self.subroutine.return_type @@ -377,7 +445,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. " @@ -389,15 +457,143 @@ 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" +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 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.make(abi.DynamicArray[abi.Uint64])).decode( + Txn.application_args[1] + ), + (res := abi.Uint64()).set(abi_sum(to_sum_arr)), + abi.MethodReturn(res), + Int(1), + ) + """ + + 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.subroutine = SubroutineDefinition( + fn_implementation, + return_type=internal_subroutine_ret_type, + abi_output_arg_name=output_kwarg_name, + ) + + @staticmethod + 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 = get_annotations(fn_implementation) + + potential_abi_arg_names = [ + k for k, v in sig.parameters.items() if v.kind == Parameter.KEYWORD_ONLY + ] + + match potential_abi_arg_names: + 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 return 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"multiple output arguments ({len(potential_abi_arg_names)}) " + f"with type annotations {potential_abi_arg_names}" + ) + + def __call__( + 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())}" + ) + + 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 abi.ReturnedValue(self.output_kwarg_info.abi_type, invoked) + + def name(self) -> str: + return self.subroutine.name() + + 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.argument_count() + + +ABIReturnSubroutine.__module__ = "pyteal" + + class Subroutine: """Used to create a PyTeal subroutine from a Python function. @@ -436,7 +632,7 @@ def __call__(self, fn_implementation: Callable[..., Expr]) -> SubroutineFnWrappe Subroutine.__module__ = "pyteal" -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, @@ -446,7 +642,7 @@ def evaluateSubroutine(subroutine: SubroutineDefinition) -> SubroutineDeclaratio 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. @@ -461,53 +657,92 @@ 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 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 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) 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) 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, Union[ScratchVar, abi.BaseType, Expr]]: - loaded: Union[ScratchVar, abi.BaseType, Expr] - argVar: ScratchVar + ) -> tuple[ScratchVar, ScratchVar | abi.BaseType | Expr]: + loaded_var: ScratchVar | abi.BaseType | Expr + argument_var: ScratchVar if param in subroutine.by_ref_args: - argVar = DynamicScratchVar(TealType.anytype) - loaded = argVar + argument_var = DynamicScratchVar(TealType.anytype) + loaded_var = argument_var elif param in subroutine.abi_args: internal_abi_var = subroutine.abi_args[param].new_instance() - argVar = internal_abi_var.stored_value - loaded = internal_abi_var + argument_var = internal_abi_var.stored_value + loaded_var = internal_abi_var else: - argVar = ScratchVar(TealType.anytype) - loaded = argVar.load() + argument_var = ScratchVar(TealType.anytype) + loaded_var = argument_var.load() - return argVar, loaded + 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() - argumentVars, loadedArgs = zip(*map(var_n_loaded, args)) if args else ([], []) + + arg_vars: list[ScratchVar] = [] + loaded_args: list[ScratchVar | Expr | abi.BaseType] = [] + for arg in args: + 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] = {} + 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(*loadedArgs) + 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: + 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()) ) # Arg usage "A" to be pick up and store in scratch parameters that have been placed on the stack # need to reverse order of argumentVars because the last argument will be on top of the stack - bodyOps = [var.slot.store() for var in argumentVars[::-1]] - bodyOps.append(subroutineBody) + body_ops = [var.slot.store() for var in arg_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 77ba08922..3e66ecaf5 100644 --- a/pyteal/ast/subroutine_test.py +++ b/pyteal/ast/subroutine_test.py @@ -2,11 +2,12 @@ from typing import List, Literal import pytest +from dataclasses import dataclass import pyteal as pt -from pyteal.ast.subroutine import evaluateSubroutine +from pyteal.ast.subroutine import evaluate_subroutine -options = pt.CompileOptions(version=4) +options = pt.CompileOptions(version=5) def test_subroutine_definition(): @@ -58,7 +59,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: @@ -79,15 +80,130 @@ def fnWithPartialExprAnnotations(a, b: pt.Expr) -> pt.Expr: assert invocation.args == args +@dataclass +class ABISubroutineTC: + 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(*, 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)]) + + 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(), + ), + ) + + for case in cases: + 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): + case.definition(*case.arg_instances[:-1]) + + with pytest.raises(pt.TealInputError): + case.definition(*(case.arg_instances + [pt.abi.Uint64()])) + + 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 case.definition.is_registrable() == all( + map(lambda x: isinstance(x, pt.abi.BaseType), case.arg_instances) + ) + + def test_subroutine_definition_validate(): """ DFS through SubroutineDefinition.validate()'s logic """ - def mock_subroutine_definition(implementation): + 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") @@ -98,6 +214,8 @@ def mock_subroutine_definition(implementation): "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: @@ -107,14 +225,22 @@ def mock_subroutine_definition(implementation): "Provided number of input_types (2) does not match detected number of parameters (3)" ) - params, anns, arg_types, byrefs, abis = three_params._validate() + 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 abis == {} - - # return validation: + assert abi_args == {} + assert output_kwarg == {} def bad_return_impl() -> str: return pt.Return(pt.Int(1)) # type: ignore @@ -127,25 +253,42 @@ def bad_return_impl() -> str: "Function has return of disallowed type . Only Expr is allowed" ) - # param validation: + # now we iterate through the implementation params validating each as we go - var_positional_or_kw = three_params - params, anns, arg_types, byrefs, abis = var_positional_or_kw._validate() - assert len(params) == 3 - assert anns == {} - assert all(at is pt.Expr for at in arg_types) - assert byrefs == set() - assert abis == {} + 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() - var_positional_only = mock_subroutine_definition( - lambda x, y, /, z: pt.Return(pt.Int(1)) + assert tie.value == pt.TealInputError( + "Function has a parameter type that is not allowed in a subroutine: parameter z with type KEYWORD_ONLY" ) - params, anns, arg_types, byrefs, abis = var_positional_only._validate() - assert len(params) == 3 - assert anns == {} + + # 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 abis == {} + 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: @@ -188,38 +331,44 @@ def bad_return_impl() -> str: "Function has input type for parameter y which is not a TealType" ) - # Now we get to _validate_parameter_type(): + # Now we get to _validate_annotation(): one_vanilla = mock_subroutine_definition(lambda x: pt.Return(pt.Int(1))) - params, anns, arg_types, byrefs, abis = one_vanilla._validate() + 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 abis == {} + 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, abis = one_expr._validate() + 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 abis == {} + 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, abis = one_scratchvar._validate() + 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 abis == {} + 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)) @@ -228,7 +377,7 @@ def one_nontype_impl(x: "blahBlah"): # type: ignore # noqa: F821 one_nontype._validate() assert tie.value == pt.TealInputError( - "Function has parameter x of disallowed type blahBlah. Only the types (, , 'ABI') are allowed" + "Function has parameter x of declared type blahBlah which is not a class" ) def one_dynscratchvar_impl(x: pt.DynamicScratchVar): @@ -243,17 +392,21 @@ def one_dynscratchvar_impl(x: pt.DynamicScratchVar): ) # Now we're back to validate() and everything should be copacetic - - # input type handling: for x, y, z in product(pt.TealType, pt.TealType, pt.TealType): - params, anns, arg_types, byrefs, abis = three_params._validate( - input_types=[x, y, z] - ) + ( + 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 abis == {} + assert abi_args == {} + assert output_kwarg == {} # annotation / abi type handling: abi_annotation_examples = { @@ -283,9 +436,9 @@ 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 = mocker._validate() + params, anns, arg_types, byrefs, abis, output_kwarg = mocker._validate() print( - f"{x_ann=}, {z_ann=}, {params=}, {anns=}, {arg_types=}, {byrefs=}, {abis=}" + f"{x_ann=}, {z_ann=}, {params=}, {anns=}, {arg_types=}, {byrefs=}, {abis=}, {output_kwarg=}" ) assert len(params) == 3 @@ -451,7 +604,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: @@ -475,11 +628,220 @@ 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(): + @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, *, output: pt.abi.Uint64 + ) -> pt.Expr: + return output.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]], + c: pt.abi.DynamicArray[pt.abi.Bool], + ) -> 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]], + c: pt.abi.DynamicArray[pt.abi.Bool], + *, + output: pt.abi.Byte, + ): + return output.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, *, output: pt.abi.Uint64 + ) -> pt.Expr: + return pt.Seq( + a.store(c.get() * pt.Int(0x0FF1CE) * b), + output.set(a.load()), + ) + + @pt.ABIReturnSubroutine + 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(), + ) + + @pt.ABIReturnSubroutine + def fn_mixed_annotation_1_with_ret( + a: pt.ScratchVar, b: pt.abi.Uint64, *, output: pt.abi.Bool + ) -> pt.Expr: + return output.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.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) + + 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, definition, args, ret_type, err in cases: + assert definition.subroutine.argument_count() == len(args), case_name + assert ( + definition.name() == definition.subroutine.implementation.__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() - 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): return pt.Return() def fnWithVariableArgs(a, *b): @@ -513,57 +875,92 @@ 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 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 return subroutine output-kwarg name must be `output` at this moment", + ), + ( + fnWithMultipleABIKeywordArgs, + "TealInputError('Function has a parameter type that is not allowed in a subroutine: parameter b with type", + "multiple output arguments (2) 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 disallowed type TealType.uint64. Only the types", + "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), "failed for case [{}]".format(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) + + assert abi_sub_def_msg in str(e), f"failed for case[{fn.__name__}]" def test_subroutine_declaration(): @@ -670,8 +1067,8 @@ def mySubroutine(): return returnValue definition = pt.SubroutineDefinition(mySubroutine, returnType) + declaration = evaluate_subroutine(definition) - declaration = evaluateSubroutine(definition) assert isinstance(declaration, pt.SubroutineDeclaration) assert declaration.subroutine is definition @@ -704,8 +1101,8 @@ def mySubroutine(a1): return returnValue definition = pt.SubroutineDefinition(mySubroutine, returnType) + declaration = evaluate_subroutine(definition) - declaration = evaluateSubroutine(definition) assert isinstance(declaration, pt.SubroutineDeclaration) assert declaration.subroutine is definition @@ -748,7 +1145,8 @@ 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 @@ -793,8 +1191,8 @@ def mySubroutine(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10): return returnValue definition = pt.SubroutineDefinition(mySubroutine, returnType) + declaration = evaluate_subroutine(definition) - declaration = evaluateSubroutine(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 2690233848751b332b3751a9c51e44f29ee23dc6 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Fri, 6 May 2022 12:27:28 -0400 Subject: [PATCH 095/188] conform to pep8, reconstructing --- pyteal/ast/router.py | 146 ++++++++++++++++++++++++------------------- 1 file changed, 81 insertions(+), 65 deletions(-) diff --git a/pyteal/ast/router.py b/pyteal/ast/router.py index dc1b3afac..a0dc3f98e 100644 --- a/pyteal/ast/router.py +++ b/pyteal/ast/router.py @@ -1,10 +1,12 @@ -from typing import List, Tuple, Union, cast +from typing import cast from dataclasses import dataclass from pyteal.config import METHOD_ARG_NUM_LIMIT from pyteal.errors import TealInputError from pyteal.types import TealType from pyteal.ast.subroutine import SubroutineFnWrapper + +# from pyteal.ast import abi from pyteal.ast.cond import Cond from pyteal.ast.expr import Expr from pyteal.ast.app import OnComplete, EnumInt @@ -15,9 +17,6 @@ from pyteal.ast.txn import Txn from pyteal.ast.return_ import Approve -# from pyteal.ast.abi.method_return import MethodReturn - -# NOTE this should sit in `abi` directory, still waiting on abi to be merged in """ Notes: @@ -50,72 +49,83 @@ class ProgramNode: branch: Expr +ProgramNode.__module__ = "pyteal" + + class Router: """ """ def __init__(self) -> None: - self.approvalIfThen: List[ProgramNode] = [] - self.clearStateIfThen: List[ProgramNode] = [] + self.approval_if_then: list[ProgramNode] = [] + self.clear_state_if_then: list[ProgramNode] = [] @staticmethod - def __parseConditions( - mReg: Union[SubroutineFnWrapper, None], - onCompletes: List[EnumInt], + def __parse_conditions( + method_to_register: SubroutineFnWrapper | None, + on_completes: list[EnumInt], creation: bool, - ) -> Tuple[List[Expr], List[Expr]]: + ) -> tuple[list[Expr], list[Expr]]: """ """ # Check if it is a *CREATION* - approvalConds: List[Expr] = [Txn.application_id() == Int(0)] if creation else [] - clearStateConds: List[Expr] = [] + approval_conds: list[Expr] = ( + [Txn.application_id() == Int(0)] if creation else [] + ) + clear_state_conds: list[Expr] = [] # Check: # - if current condition is for *ABI METHOD* # (method selector && numAppArg == max(METHOD_APP_ARG_NUM_LIMIT, 1 + subroutineSyntaxArgNum)) # - or *BARE APP CALL* (numAppArg == 0) - methodOrBareCondition = ( + method_or_bare_condition = ( And( - Txn.application_args[0] == MethodSignature(mReg.name()), + Txn.application_args[0] == MethodSignature(method_to_register.name()), Txn.application_args.length() - == Int(1 + max(mReg.subroutine.argument_count(), METHOD_ARG_NUM_LIMIT)), + == Int( + 1 + + max( + method_to_register.subroutine.argument_count(), + METHOD_ARG_NUM_LIMIT, + ) + ), ) - if mReg is not None + if method_to_register is not None else Txn.application_args.length() == Int(0) ) - approvalConds.append(methodOrBareCondition) + approval_conds.append(method_or_bare_condition) # Check the existence of OC.CloseOut - closeOutExist = any(map(lambda x: x == OnComplete.CloseOut, onCompletes)) + close_out_exist = any(map(lambda x: x == OnComplete.CloseOut, on_completes)) # Check the existence of OC.ClearState (needed later) - clearStateExist = any(map(lambda x: x == OnComplete.ClearState, onCompletes)) + clear_state_exist = any(map(lambda x: x == OnComplete.ClearState, on_completes)) # Ill formed report if app create with existence of OC.CloseOut or OC.ClearState - if creation and (closeOutExist or clearStateExist): + if creation and (close_out_exist or clear_state_exist): raise TealInputError( "OnComplete ClearState/CloseOut may be ill-formed with app creation" ) # if OC.ClearState exists, add method-or-bare-condition since it is only needed in ClearStateProgram - if clearStateExist: - clearStateConds.append(methodOrBareCondition) + if clear_state_exist: + clear_state_conds.append(method_or_bare_condition) - # Check onComplete conditions for approvalConds, filter out *ClearState* - approvalOcConds: List[Expr] = [ + # Check onComplete conditions for approval_conds, filter out *ClearState* + approval_oc_conds: list[Expr] = [ Txn.on_completion() == oc - for oc in onCompletes + for oc in on_completes if oc != OnComplete.ClearState ] - # if approval OC condition is not empty, append Or to approvalConds - if len(approvalOcConds) > 0: - approvalConds.append(Or(*approvalOcConds)) + # if approval OC condition is not empty, append Or to approval_conds + if len(approval_oc_conds) > 0: + approval_conds.append(Or(*approval_oc_conds)) # what we have here is: # list of conds for approval program on one branch: creation?, method/bare, Or[OCs] # list of conds for clearState program on one branch: method/bare - return approvalConds, clearStateConds + return approval_conds, clear_state_conds @staticmethod - def __wrapHandler(isMethod: bool, branch: Union[SubroutineFnWrapper, Expr]) -> Expr: + def __wrap_handler(isMethod: bool, branch: SubroutineFnWrapper | Expr) -> Expr: """""" - exprList: List[Expr] = [] + exprList: list[Expr] = [] if not isMethod: if ( isinstance(branch, Seq) @@ -156,76 +166,82 @@ def __wrapHandler(isMethod: bool, branch: Union[SubroutineFnWrapper, Expr]) -> E exprList.append(Approve()) return Seq(*exprList) - def __appendToAST( - self, approvalConds: List[Expr], clearConds: List[Expr], branch: Expr + def __append_to_ast( + self, approval_conds: list[Expr], clear_state_conds: list[Expr], branch: Expr ) -> None: """ """ - if len(approvalConds) > 0: - self.approvalIfThen.append( + if len(approval_conds) > 0: + self.approval_if_then.append( ProgramNode( - And(*approvalConds) if len(approvalConds) > 1 else approvalConds[0], + And(*approval_conds) + if len(approval_conds) > 1 + else approval_conds[0], branch, ) ) - if len(clearConds) > 0: - self.clearStateIfThen.append( + if len(clear_state_conds) > 0: + self.clear_state_if_then.append( ProgramNode( - And(*clearConds) if len(clearConds) > 1 else clearConds[0], + And(*clear_state_conds) + if len(clear_state_conds) > 1 + else clear_state_conds[0], branch, ) ) - def onBareAppCall( + def on_bare_app_call( self, - bareAppCall: Union[SubroutineFnWrapper, Expr], - onCompletes: Union[EnumInt, List[EnumInt]], + bare_app_call: SubroutineFnWrapper | Expr, + on_completes: EnumInt | list[EnumInt], *, creation: bool = False, ) -> None: """ """ - ocList: List[EnumInt] = ( - cast(List[EnumInt], onCompletes) - if isinstance(onCompletes, list) - else [cast(EnumInt, onCompletes)] + ocList: list[EnumInt] = ( + cast(list[EnumInt], on_completes) + if isinstance(on_completes, list) + else [cast(EnumInt, on_completes)] ) - approvalConds, clearConds = Router.__parseConditions( - mReg=None, onCompletes=ocList, creation=creation + approval_conds, clear_state_conds = Router.__parse_conditions( + method_to_register=None, on_completes=ocList, creation=creation ) - branch = Router.__wrapHandler(False, bareAppCall) - self.__appendToAST(approvalConds, clearConds, branch) + branch = Router.__wrap_handler(False, bare_app_call) + self.__append_to_ast(approval_conds, clear_state_conds, branch) - def onMethodCall( + def on_method_call( self, - methodAppCall: SubroutineFnWrapper, + method_signature: str, + method_app_call: SubroutineFnWrapper, *, - onComplete: EnumInt = OnComplete.NoOp, + on_complete: EnumInt = OnComplete.NoOp, creation: bool = False, ) -> None: """ """ - ocList: List[EnumInt] = [cast(EnumInt, onComplete)] - approvalConds, clearConds = Router.__parseConditions( - mReg=methodAppCall, onCompletes=ocList, creation=creation + oc_list: list[EnumInt] = [cast(EnumInt, on_complete)] + approval_conds, clear_state_conds = Router.__parse_conditions( + method_to_register=method_app_call, on_completes=oc_list, creation=creation ) - branch = Router.__wrapHandler(True, methodAppCall) - self.__appendToAST(approvalConds, clearConds, branch) + branch = Router.__wrap_handler(True, method_app_call) + self.__append_to_ast(approval_conds, clear_state_conds, branch) @staticmethod - def __astConstruct( - astList: List[ProgramNode], + def __ast_construct( + ast_list: list[ProgramNode], ) -> Expr: """ """ - if len(astList) == 0: + if len(ast_list) == 0: raise TealInputError("ABIRouter: Cannot build program with an empty AST") - program: Cond = Cond(*[[node.condition, node.branch] for node in astList]) + program: Cond = Cond(*[[node.condition, node.branch] for node in ast_list]) return program - def buildProgram(self) -> Tuple[Expr, Expr]: + def build_program(self) -> tuple[Expr, Expr]: + # TODO need JSON object """ """ return ( - Router.__astConstruct(self.approvalIfThen), - Router.__astConstruct(self.clearStateIfThen), + Router.__ast_construct(self.approval_if_then), + Router.__ast_construct(self.clear_state_if_then), ) From c0c37f88b36eceb1bccf6b5c56a7adaf14d5e236 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Fri, 6 May 2022 15:34:21 -0400 Subject: [PATCH 096/188] update json generator --- pyteal/ast/router.py | 52 ++++++++++++++++++++++++++++++-------------- 1 file changed, 36 insertions(+), 16 deletions(-) diff --git a/pyteal/ast/router.py b/pyteal/ast/router.py index a0dc3f98e..5fb033b39 100644 --- a/pyteal/ast/router.py +++ b/pyteal/ast/router.py @@ -1,12 +1,13 @@ -from typing import cast +from algosdk.abi import Contract, Method from dataclasses import dataclass +from typing import Any, cast, Optional from pyteal.config import METHOD_ARG_NUM_LIMIT from pyteal.errors import TealInputError from pyteal.types import TealType -from pyteal.ast.subroutine import SubroutineFnWrapper # from pyteal.ast import abi +from pyteal.ast.subroutine import SubroutineFnWrapper from pyteal.ast.cond import Cond from pyteal.ast.expr import Expr from pyteal.ast.app import OnComplete, EnumInt @@ -47,6 +48,7 @@ class ProgramNode: condition: Expr branch: Expr + method_info: Optional[Method] ProgramNode.__module__ = "pyteal" @@ -55,7 +57,8 @@ class ProgramNode: class Router: """ """ - def __init__(self) -> None: + def __init__(self, name: str = None) -> None: + self.name = "Contract" if name is None else name self.approval_if_then: list[ProgramNode] = [] self.clear_state_if_then: list[ProgramNode] = [] @@ -167,25 +170,31 @@ def __wrap_handler(isMethod: bool, branch: SubroutineFnWrapper | Expr) -> Expr: return Seq(*exprList) def __append_to_ast( - self, approval_conds: list[Expr], clear_state_conds: list[Expr], branch: Expr + self, + approval_conditions: list[Expr], + clear_state_conditions: list[Expr], + branch: Expr, + method_obj: Optional[Method] = None, ) -> None: """ """ - if len(approval_conds) > 0: + if len(approval_conditions) > 0: self.approval_if_then.append( ProgramNode( - And(*approval_conds) - if len(approval_conds) > 1 - else approval_conds[0], + And(*approval_conditions) + if len(approval_conditions) > 1 + else approval_conditions[0], branch, + method_obj, ) ) - if len(clear_state_conds) > 0: + if len(clear_state_conditions) > 0: self.clear_state_if_then.append( ProgramNode( - And(*clear_state_conds) - if len(clear_state_conds) > 1 - else clear_state_conds[0], + And(*clear_state_conditions) + if len(clear_state_conditions) > 1 + else clear_state_conditions[0], branch, + method_obj, ) ) @@ -206,7 +215,7 @@ def on_bare_app_call( method_to_register=None, on_completes=ocList, creation=creation ) branch = Router.__wrap_handler(False, bare_app_call) - self.__append_to_ast(approval_conds, clear_state_conds, branch) + self.__append_to_ast(approval_conds, clear_state_conds, branch, None) def on_method_call( self, @@ -222,7 +231,12 @@ def on_method_call( method_to_register=method_app_call, on_completes=oc_list, creation=creation ) branch = Router.__wrap_handler(True, method_app_call) - self.__append_to_ast(approval_conds, clear_state_conds, branch) + self.__append_to_ast( + approval_conds, + clear_state_conds, + branch, + Method.from_signature(method_signature), + ) @staticmethod def __ast_construct( @@ -236,12 +250,18 @@ def __ast_construct( return program - def build_program(self) -> tuple[Expr, Expr]: - # TODO need JSON object + def __contract_construct(self) -> dict[str, Any]: + method_collections = [ + node.method_info for node in self.approval_if_then if node.method_info + ] + return Contract(self.name, method_collections).dictify() + + def build_program(self) -> tuple[Expr, Expr, dict[str, Any]]: """ """ return ( Router.__ast_construct(self.approval_if_then), Router.__ast_construct(self.clear_state_if_then), + self.__contract_construct(), ) From bba1709ef8ea06c769b9c0ba482b08bae2a602a4 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Mon, 9 May 2022 11:01:39 -0400 Subject: [PATCH 097/188] 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 098/188] 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 95caac58819cacc00dbbca743f97a7091f7b417d Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Mon, 9 May 2022 11:02:11 -0700 Subject: [PATCH 099/188] 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 0169078967946e1684f00f8c5cf16a16a57c56c8 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Thu, 12 May 2022 13:39:09 -0400 Subject: [PATCH 100/188] updating wrap handler --- pyteal/ast/router.py | 96 ++++++++++++++++++++++++++++---------------- 1 file changed, 62 insertions(+), 34 deletions(-) diff --git a/pyteal/ast/router.py b/pyteal/ast/router.py index 5fb033b39..ed18635a9 100644 --- a/pyteal/ast/router.py +++ b/pyteal/ast/router.py @@ -7,7 +7,7 @@ from pyteal.types import TealType # from pyteal.ast import abi -from pyteal.ast.subroutine import SubroutineFnWrapper +from pyteal.ast.subroutine import SubroutineFnWrapper, ABIReturnSubroutine from pyteal.ast.cond import Cond from pyteal.ast.expr import Expr from pyteal.ast.app import OnComplete, EnumInt @@ -57,14 +57,14 @@ class ProgramNode: class Router: """ """ - def __init__(self, name: str = None) -> None: - self.name = "Contract" if name is None else name + def __init__(self, name: Optional[str] = None) -> None: + self.name: str = "Contract" if name is None else name self.approval_if_then: list[ProgramNode] = [] self.clear_state_if_then: list[ProgramNode] = [] @staticmethod def __parse_conditions( - method_to_register: SubroutineFnWrapper | None, + method_to_register: ABIReturnSubroutine | None, on_completes: list[EnumInt], creation: bool, ) -> tuple[list[Expr], list[Expr]]: @@ -126,31 +126,61 @@ def __parse_conditions( return approval_conds, clear_state_conds @staticmethod - def __wrap_handler(isMethod: bool, branch: SubroutineFnWrapper | Expr) -> Expr: + def __wrap_handler( + is_abi_method: bool, handler: ABIReturnSubroutine | SubroutineFnWrapper | Expr + ) -> Expr: """""" - exprList: list[Expr] = [] - if not isMethod: - if ( - isinstance(branch, Seq) - and not branch.has_return() - and branch.type_of() == TealType.none - ): - exprList.append(branch) - elif ( - isinstance(branch, SubroutineFnWrapper) - and branch.has_return() - and branch.type_of() == TealType.none - ): - exprList.append(branch()) - else: - raise TealInputError( - "bare appcall can only accept: none type + Seq (no ret) or Subroutine (with ret)" - ) + expr_list: list[Expr] = [] + if not is_abi_method: + match handler: + case Expr(): + if handler.type_of() != TealType.none: + raise TealInputError( + "bare appcall handler should have type to be none." + ) + return handler if handler.has_return() else Seq(handler, Approve()) + case SubroutineFnWrapper(): + if handler.type_of() != TealType.none: + raise TealInputError( + f"subroutine call should be returning none not {handler.type_of()}." + ) + if handler.subroutine.argument_count() != 0: + raise TealInputError( + f"subroutine call should take 0 arg for bare-app call. " + f"this subroutine takes {handler.subroutine.argument_count()}." + ) + return Seq(handler(), Approve()) + case ABIReturnSubroutine(): + if handler.type_of() != "void": + raise TealInputError( + f"abi-returning subroutine call should be returning void not {handler.type_of()}." + ) + if handler.subroutine.argument_count() != 0: + raise TealInputError( + f"abi-returning subroutine call should take 0 arg for bare-app call. " + f"this abi-returning subroutine takes {handler.subroutine.argument_count()}." + ) + return Seq(cast(Expr, handler()), Approve()) + case _: + raise TealInputError( + "bare appcall can only accept: none type + Seq (no ret) or Subroutine (with ret)" + ) else: - if isinstance(branch, SubroutineFnWrapper) and branch.has_return(): + match handler: + case Expr(): + pass + case SubroutineFnWrapper(): + pass + case ABIReturnSubroutine(): + pass + case _: + raise TealInputError( + "For method call: should only register Subroutine with return" + ) + if isinstance(handler, SubroutineFnWrapper) and handler.has_return(): # TODO need to encode/decode things # execBranchArgs: List[Expr] = [] - if branch.subroutine.argument_count() >= METHOD_ARG_NUM_LIMIT: + if handler.subroutine.argument_count() >= METHOD_ARG_NUM_LIMIT: # NOTE decode (if arg num > 15 need to de-tuple 15th (last) argument) pass else: @@ -162,12 +192,7 @@ def __wrap_handler(isMethod: bool, branch: SubroutineFnWrapper | Expr) -> Expr: # if branch.type_of() != TealType.none # else branch(*execBranchArgs) # ) - else: - raise TealInputError( - "For method call: should only register Subroutine with return" - ) - exprList.append(Approve()) - return Seq(*exprList) + return Seq(*expr_list) def __append_to_ast( self, @@ -200,7 +225,7 @@ def __append_to_ast( def on_bare_app_call( self, - bare_app_call: SubroutineFnWrapper | Expr, + bare_app_call: ABIReturnSubroutine | SubroutineFnWrapper | Expr, on_completes: EnumInt | list[EnumInt], *, creation: bool = False, @@ -220,7 +245,7 @@ def on_bare_app_call( def on_method_call( self, method_signature: str, - method_app_call: SubroutineFnWrapper, + method_app_call: ABIReturnSubroutine, *, on_complete: EnumInt = OnComplete.NoOp, creation: bool = False, @@ -257,7 +282,10 @@ def __contract_construct(self) -> dict[str, Any]: return Contract(self.name, method_collections).dictify() def build_program(self) -> tuple[Expr, Expr, dict[str, Any]]: - """ """ + """This method construct ASTs for both the approval and clear programs based on the inputs to the router, + also dump a JSON object of contract to allow client read and call the methods easily. + + """ return ( Router.__ast_construct(self.approval_if_then), Router.__ast_construct(self.clear_state_if_then), From a04a897f1e3bc5991e4fcc2a941165cc357d2abe Mon Sep 17 00:00:00 2001 From: Hang Su Date: Thu, 12 May 2022 14:56:43 -0400 Subject: [PATCH 101/188] change how to de-tuple args --- pyteal/ast/router.py | 100 ++++++++++++++++++++++++++++++------------- 1 file changed, 70 insertions(+), 30 deletions(-) diff --git a/pyteal/ast/router.py b/pyteal/ast/router.py index ed18635a9..e49437529 100644 --- a/pyteal/ast/router.py +++ b/pyteal/ast/router.py @@ -6,8 +6,12 @@ from pyteal.errors import TealInputError from pyteal.types import TealType -# from pyteal.ast import abi -from pyteal.ast.subroutine import SubroutineFnWrapper, ABIReturnSubroutine +from pyteal.ast import abi +from pyteal.ast.subroutine import ( + OutputKwArgInfo, + SubroutineFnWrapper, + ABIReturnSubroutine, +) from pyteal.ast.cond import Cond from pyteal.ast.expr import Expr from pyteal.ast.app import OnComplete, EnumInt @@ -130,7 +134,6 @@ def __wrap_handler( is_abi_method: bool, handler: ABIReturnSubroutine | SubroutineFnWrapper | Expr ) -> Expr: """""" - expr_list: list[Expr] = [] if not is_abi_method: match handler: case Expr(): @@ -166,33 +169,70 @@ def __wrap_handler( "bare appcall can only accept: none type + Seq (no ret) or Subroutine (with ret)" ) else: - match handler: - case Expr(): - pass - case SubroutineFnWrapper(): - pass - case ABIReturnSubroutine(): - pass - case _: - raise TealInputError( - "For method call: should only register Subroutine with return" - ) - if isinstance(handler, SubroutineFnWrapper) and handler.has_return(): - # TODO need to encode/decode things - # execBranchArgs: List[Expr] = [] - if handler.subroutine.argument_count() >= METHOD_ARG_NUM_LIMIT: - # NOTE decode (if arg num > 15 need to de-tuple 15th (last) argument) - pass - else: - pass - - # exprList.append( - # TODO this line can be changed to method-return in ABI side - # MethodReturn(branch(*execBranchArgs)) - # if branch.type_of() != TealType.none - # else branch(*execBranchArgs) - # ) - return Seq(*expr_list) + if not isinstance(handler, ABIReturnSubroutine): + raise TealInputError( + f"method call should be only registering ABIReturnSubroutine, got {type(handler)}." + ) + if not handler.is_registrable(): + raise TealInputError( + f"method call ABIReturnSubroutine is not registable" + f"got {handler.subroutine.argument_count()} args with {len(handler.subroutine.abi_args)} ABI args." + ) + + arg_type_specs: list[abi.TypeSpec] = cast( + list[abi.TypeSpec], handler.subroutine.expected_arg_types + ) + if handler.subroutine.argument_count() > METHOD_ARG_NUM_LIMIT: + to_be_tupled_specs = arg_type_specs[METHOD_ARG_NUM_LIMIT - 1 :] + arg_type_specs = arg_type_specs[: METHOD_ARG_NUM_LIMIT - 1] + tupled_spec = abi.TupleTypeSpec(*to_be_tupled_specs) + arg_type_specs.append(tupled_spec) + + arg_abi_vars: list[abi.BaseType] = [ + arg_type_specs[i].new_instance() + for i in range(handler.subroutine.argument_count()) + ] + decode_instructions: list[Expr] = [ + arg_abi_vars[i].decode(Txn.application_args[i + 1]) + for i in range(handler.subroutine.argument_count()) + ] + + if handler.subroutine.argument_count() > METHOD_ARG_NUM_LIMIT: + tuple_arg_type_specs: list[abi.TypeSpec] = cast( + list[abi.TypeSpec], + handler.subroutine.expected_arg_types[METHOD_ARG_NUM_LIMIT - 1 :], + ) + tuple_abi_args: list[abi.BaseType] = [ + t_arg_ts.new_instance() for t_arg_ts in tuple_arg_type_specs + ] + tupled_arg: abi.Tuple = cast(abi.Tuple, arg_abi_vars[-1]) + de_tuple_instructions: list[Expr] = [ + tupled_arg[i].store_into(tuple_abi_args[i]) + for i in range(len(tuple_arg_type_specs)) + ] + decode_instructions += de_tuple_instructions + arg_abi_vars = arg_abi_vars[:-1] + tuple_abi_args + + # NOTE: does not have to have return, can be void method + if handler.type_of() == "void": + return Seq( + *decode_instructions, + cast(Expr, handler(*arg_abi_vars)), + Approve(), + ) + else: + output_temp: abi.BaseType = cast( + OutputKwArgInfo, handler.output_kwarg_info + ).abi_type.new_instance() + subroutine_call: abi.ReturnedValue = cast( + abi.ReturnedValue, handler(*arg_abi_vars) + ) + return Seq( + *decode_instructions, + subroutine_call.store_into(output_temp), + abi.MethodReturn(output_temp), + Approve(), + ) def __append_to_ast( self, From bc6b8dc645d2cd7d10c3e0200739887fe4e247b0 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Thu, 12 May 2022 14:58:41 -0400 Subject: [PATCH 102/188] update checkbox --- pyteal/ast/router.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pyteal/ast/router.py b/pyteal/ast/router.py index e49437529..ccedc7c07 100644 --- a/pyteal/ast/router.py +++ b/pyteal/ast/router.py @@ -33,12 +33,12 @@ - On Method Call, check - [x] txna ApplicationArgs 0 == method "method-signature" - [x] On-Completion should match (only one On-Completion specified here?) - - [?] non void method call should log with 0x151f7c75 return-method-specifier + - [x] non void method call should log with 0x151f7c75 return-method-specifier (kinda done in another PR to ABI-Type) - - [?] redirect the method arguments and pass them to handler function + - [x] redirect the method arguments and pass them to handler function (kinda done, but need to do with extraction and (en/de)-code) - - [ ] Must execute actions required to invoke the method - - [ ] extract arguments if needed + - [x] Must execute actions required to invoke the method + - [x] extract arguments if needed (decode txna ApplicationArgs 15 if there exists, and extract arguments to feed method) Notes for OC: From 5516d8912dced909dfa4f940945293db6e7187ca Mon Sep 17 00:00:00 2001 From: Hang Su Date: Thu, 12 May 2022 17:05:41 -0400 Subject: [PATCH 103/188] minor --- pyteal/ast/router.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyteal/ast/router.py b/pyteal/ast/router.py index ccedc7c07..6dd530d6d 100644 --- a/pyteal/ast/router.py +++ b/pyteal/ast/router.py @@ -89,7 +89,7 @@ def __parse_conditions( Txn.application_args.length() == Int( 1 - + max( + + min( method_to_register.subroutine.argument_count(), METHOD_ARG_NUM_LIMIT, ) From b8e8d1fb2321224dc1fa53475f22a42a9d9281e4 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Thu, 12 May 2022 17:52:41 -0400 Subject: [PATCH 104/188] bug fixes --- pyteal/ast/router.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/pyteal/ast/router.py b/pyteal/ast/router.py index 6dd530d6d..bc5303005 100644 --- a/pyteal/ast/router.py +++ b/pyteal/ast/router.py @@ -81,7 +81,7 @@ def __parse_conditions( # Check: # - if current condition is for *ABI METHOD* - # (method selector && numAppArg == max(METHOD_APP_ARG_NUM_LIMIT, 1 + subroutineSyntaxArgNum)) + # (method selector && numAppArg == 1 + min(METHOD_APP_ARG_NUM_LIMIT, subroutineSyntaxArgNum)) # - or *BARE APP CALL* (numAppArg == 0) method_or_bare_condition = ( And( @@ -101,9 +101,13 @@ def __parse_conditions( approval_conds.append(method_or_bare_condition) # Check the existence of OC.CloseOut - close_out_exist = any(map(lambda x: x == OnComplete.CloseOut, on_completes)) + close_out_exist = any( + map(lambda x: str(x) == str(OnComplete.CloseOut), on_completes) + ) # Check the existence of OC.ClearState (needed later) - clear_state_exist = any(map(lambda x: x == OnComplete.ClearState, on_completes)) + clear_state_exist = any( + map(lambda x: str(x) == str(OnComplete.ClearState), on_completes) + ) # Ill formed report if app create with existence of OC.CloseOut or OC.ClearState if creation and (close_out_exist or clear_state_exist): raise TealInputError( @@ -117,7 +121,7 @@ def __parse_conditions( approval_oc_conds: list[Expr] = [ Txn.on_completion() == oc for oc in on_completes - if oc != OnComplete.ClearState + if str(oc) != str(OnComplete.ClearState) ] # if approval OC condition is not empty, append Or to approval_conds @@ -328,7 +332,9 @@ def build_program(self) -> tuple[Expr, Expr, dict[str, Any]]: """ return ( Router.__ast_construct(self.approval_if_then), - Router.__ast_construct(self.clear_state_if_then), + Router.__ast_construct(self.clear_state_if_then) + if self.clear_state_if_then + else Approve(), self.__contract_construct(), ) From 16199ec13ca0abc4b52a568cf3a75b4996c4c5d0 Mon Sep 17 00:00:00 2001 From: Ben Guidarelli Date: Fri, 13 May 2022 09:36:52 -0400 Subject: [PATCH 105/188] Method sig for router (#340) * adding method_signature to ABIReturnSubroutine --- pyteal/ast/router.py | 24 ++++++++++++++++++++---- pyteal/ast/subroutine.py | 9 +++++++++ pyteal/ast/subroutine_test.py | 32 ++++++++++++++++++++++++++++---- 3 files changed, 57 insertions(+), 8 deletions(-) diff --git a/pyteal/ast/router.py b/pyteal/ast/router.py index bc5303005..7eb2863da 100644 --- a/pyteal/ast/router.py +++ b/pyteal/ast/router.py @@ -68,6 +68,7 @@ def __init__(self, name: Optional[str] = None) -> None: @staticmethod def __parse_conditions( + method_signature: str, method_to_register: ABIReturnSubroutine | None, on_completes: list[EnumInt], creation: bool, @@ -79,13 +80,18 @@ def __parse_conditions( ) clear_state_conds: list[Expr] = [] + if method_signature == "" and method_to_register is not None: + raise TealInputError( + "A method_signature must only be provided if method_to_register is not None" + ) + # Check: # - if current condition is for *ABI METHOD* # (method selector && numAppArg == 1 + min(METHOD_APP_ARG_NUM_LIMIT, subroutineSyntaxArgNum)) # - or *BARE APP CALL* (numAppArg == 0) method_or_bare_condition = ( And( - Txn.application_args[0] == MethodSignature(method_to_register.name()), + Txn.application_args[0] == MethodSignature(method_signature), Txn.application_args.length() == Int( 1 @@ -281,23 +287,33 @@ def on_bare_app_call( else [cast(EnumInt, on_completes)] ) approval_conds, clear_state_conds = Router.__parse_conditions( - method_to_register=None, on_completes=ocList, creation=creation + method_signature="", + method_to_register=None, + on_completes=ocList, + creation=creation, ) branch = Router.__wrap_handler(False, bare_app_call) self.__append_to_ast(approval_conds, clear_state_conds, branch, None) def on_method_call( self, - method_signature: str, method_app_call: ABIReturnSubroutine, *, + method_signature: str = None, on_complete: EnumInt = OnComplete.NoOp, creation: bool = False, ) -> None: """ """ oc_list: list[EnumInt] = [cast(EnumInt, on_complete)] + + if method_signature is None: + method_signature = method_app_call.method_signature() + approval_conds, clear_state_conds = Router.__parse_conditions( - method_to_register=method_app_call, on_completes=oc_list, creation=creation + method_signature=method_signature, + method_to_register=method_app_call, + on_completes=oc_list, + creation=creation, ) branch = Router.__wrap_handler(True, method_app_call) self.__append_to_ast( diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index bd6af5918..bbae5825e 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -573,6 +573,15 @@ def __call__( def name(self) -> str: return self.subroutine.name() + def method_signature(self) -> str: + if not self.is_registrable(): + raise TealInputError( + "Only registrable methods may return a method signature" + ) + + args = [str(v) for v in self.subroutine.abi_args.values()] + return f"{self.name()}({','.join(args)}){self.type_of()}" + def type_of(self) -> str | abi.TypeSpec: return ( "void" diff --git a/pyteal/ast/subroutine_test.py b/pyteal/ast/subroutine_test.py index 0a90f33ee..f0a31e25b 100644 --- a/pyteal/ast/subroutine_test.py +++ b/pyteal/ast/subroutine_test.py @@ -86,6 +86,7 @@ class ABISubroutineTC: arg_instances: list[pt.Expr | pt.abi.BaseType] name: str ret_type: str | pt.abi.TypeSpec + signature: str def test_abi_subroutine_definition(): @@ -130,13 +131,27 @@ def fn_2arg_1ret_with_expr( return output.set(b[a % pt.Int(10)]) cases = ( - ABISubroutineTC(fn_0arg_0ret, [], "fn_0arg_0ret", "void"), + ABISubroutineTC(fn_0arg_0ret, [], "fn_0arg_0ret", "void", "fn_0arg_0ret()void"), ABISubroutineTC( - fn_0arg_uint64_ret, [], "fn_0arg_uint64_ret", pt.abi.Uint64TypeSpec() + fn_0arg_uint64_ret, + [], + "fn_0arg_uint64_ret", + pt.abi.Uint64TypeSpec(), + "fn_0arg_uint64_ret()uint64", ), - ABISubroutineTC(fn_1arg_0ret, [pt.abi.Uint64()], "fn_1arg_0ret", "void"), ABISubroutineTC( - fn_1arg_1ret, [pt.abi.Uint64()], "fn_1arg_1ret", pt.abi.Uint64TypeSpec() + fn_1arg_0ret, + [pt.abi.Uint64()], + "fn_1arg_0ret", + "void", + "fn_1arg_0ret(uint64)void", + ), + ABISubroutineTC( + fn_1arg_1ret, + [pt.abi.Uint64()], + "fn_1arg_1ret", + pt.abi.Uint64TypeSpec(), + "fn_1arg_1ret(uint64)uint64", ), ABISubroutineTC( fn_2arg_0ret, @@ -148,6 +163,7 @@ def fn_2arg_1ret_with_expr( ], "fn_2arg_0ret", "void", + "fn_2arg_0ret(uint64,byte[10])void", ), ABISubroutineTC( fn_2arg_1ret, @@ -159,6 +175,7 @@ def fn_2arg_1ret_with_expr( ], "fn_2arg_1ret", pt.abi.ByteTypeSpec(), + "fn_2arg_1ret(uint64,byte[10])byte", ), ABISubroutineTC( fn_2arg_1ret_with_expr, @@ -170,6 +187,7 @@ def fn_2arg_1ret_with_expr( ], "fn_2arg_1ret_with_expr", pt.abi.ByteTypeSpec(), + None, ), ) @@ -193,6 +211,12 @@ def fn_2arg_1ret_with_expr( map(lambda x: isinstance(x, pt.abi.BaseType), case.arg_instances) ) + if case.definition.is_registrable(): + assert case.definition.method_signature() == case.signature + else: + with pytest.raises(pt.TealInputError): + case.definition.method_signature() + def test_subroutine_definition_validate(): """ From d8b7b3dfaff2c62db3d7a920efd5afba848eb486 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Fri, 13 May 2022 13:31:29 -0400 Subject: [PATCH 106/188] minor, renaming --- pyteal/ast/router.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyteal/ast/router.py b/pyteal/ast/router.py index 7eb2863da..522eaf340 100644 --- a/pyteal/ast/router.py +++ b/pyteal/ast/router.py @@ -295,7 +295,7 @@ def on_bare_app_call( branch = Router.__wrap_handler(False, bare_app_call) self.__append_to_ast(approval_conds, clear_state_conds, branch, None) - def on_method_call( + def add_method_handler( self, method_app_call: ABIReturnSubroutine, *, From d911e4250dae8a118fe465d8b3aa63b19f1dbbab Mon Sep 17 00:00:00 2001 From: Hang Su Date: Fri, 13 May 2022 14:24:19 -0400 Subject: [PATCH 107/188] minor --- pyteal/ast/router.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pyteal/ast/router.py b/pyteal/ast/router.py index 522eaf340..47daf5fab 100644 --- a/pyteal/ast/router.py +++ b/pyteal/ast/router.py @@ -22,7 +22,6 @@ from pyteal.ast.txn import Txn from pyteal.ast.return_ import Approve - """ Notes: - On a BareApp Call, check @@ -273,7 +272,7 @@ def __append_to_ast( ) ) - def on_bare_app_call( + def add_bare_call( self, bare_app_call: ABIReturnSubroutine | SubroutineFnWrapper | Expr, on_completes: EnumInt | list[EnumInt], From 179ac5d520eeb76b9b54e0a60af71de36495687f Mon Sep 17 00:00:00 2001 From: Hang Su Date: Fri, 13 May 2022 14:30:27 -0400 Subject: [PATCH 108/188] simplify --- pyteal/ast/abi/string.py | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/pyteal/ast/abi/string.py b/pyteal/ast/abi/string.py index 3bfacf2eb..79c3963a8 100644 --- a/pyteal/ast/abi/string.py +++ b/pyteal/ast/abi/string.py @@ -1,4 +1,4 @@ -from typing import Union, TypeVar, Sequence, cast +from typing import Union, Sequence, cast from collections.abc import Sequence as CollectionSequence from pyteal.ast.abi.uint import Byte @@ -20,9 +20,6 @@ def encoded_string(s: Expr): return Concat(Suffix(Itob(Len(s)), Int(6)), s) -T = TypeVar("T", bound=BaseType) - - class StringTypeSpec(DynamicArrayTypeSpec): def __init__(self) -> None: super().__init__(ByteTypeSpec()) @@ -67,12 +64,7 @@ def set( match value: case ComputedValue(): - if value.produced_type_spec() == StringTypeSpec(): - return value.store_into(self) - - raise TealInputError( - f"Got ComputedValue with type spec {value.produced_type_spec()}, expected StringTypeSpec" - ) + return self._set_with_computed_type(value) case BaseType(): if value.type_spec() == StringTypeSpec() or ( value.type_spec() == DynamicArrayTypeSpec(ByteTypeSpec()) From 1e7d779944313ba789c994ce7dd3cb7fbb70a2ff Mon Sep 17 00:00:00 2001 From: Hang Su Date: Fri, 13 May 2022 15:14:44 -0400 Subject: [PATCH 109/188] compiler test adding... --- pyteal/ast/router.py | 5 +- pyteal/compiler/compiler_test.py | 441 +++++++++++++++++++++++++++++++ 2 files changed, 443 insertions(+), 3 deletions(-) diff --git a/pyteal/ast/router.py b/pyteal/ast/router.py index 47daf5fab..44ec3c07b 100644 --- a/pyteal/ast/router.py +++ b/pyteal/ast/router.py @@ -198,12 +198,11 @@ def __wrap_handler( arg_type_specs.append(tupled_spec) arg_abi_vars: list[abi.BaseType] = [ - arg_type_specs[i].new_instance() - for i in range(handler.subroutine.argument_count()) + type_spec.new_instance() for type_spec in arg_type_specs ] decode_instructions: list[Expr] = [ arg_abi_vars[i].decode(Txn.application_args[i + 1]) - for i in range(handler.subroutine.argument_count()) + for i in range(len(arg_type_specs)) ] if handler.subroutine.argument_count() > METHOD_ARG_NUM_LIMIT: diff --git a/pyteal/compiler/compiler_test.py b/pyteal/compiler/compiler_test.py index a89f075fc..7c10ead1f 100644 --- a/pyteal/compiler/compiler_test.py +++ b/pyteal/compiler/compiler_test.py @@ -2193,3 +2193,444 @@ def access_b4_store(magic_num: pt.abi.Uint64, *, output: pt.abi.Uint64): with pytest.raises(pt.TealInternalError): pt.compileTeal(program_access_b4_store_broken, pt.Mode.Application, version=6) + + +def test_router_app(): + router = pt.Router() + + @router.add_method_handler + @pt.ABIReturnSubroutine + def add(a: pt.abi.Uint64, b: pt.abi.Uint64, *, output: pt.abi.Uint64) -> pt.Expr: + return output.set(a.get() + b.get()) + + @router.add_method_handler + @pt.ABIReturnSubroutine + def sub(a: pt.abi.Uint64, b: pt.abi.Uint64, *, output: pt.abi.Uint64) -> pt.Expr: + return output.set(a.get() - b.get()) + + @router.add_method_handler + @pt.ABIReturnSubroutine + def mul(a: pt.abi.Uint64, b: pt.abi.Uint64, *, output: pt.abi.Uint64) -> pt.Expr: + return output.set(a.get() * b.get()) + + @router.add_method_handler + @pt.ABIReturnSubroutine + def div(a: pt.abi.Uint64, b: pt.abi.Uint64, *, output: pt.abi.Uint64) -> pt.Expr: + return output.set(a.get() / b.get()) + + @router.add_method_handler + @pt.ABIReturnSubroutine + def mod(a: pt.abi.Uint64, b: pt.abi.Uint64, *, output: pt.abi.Uint64) -> pt.Expr: + return output.set(a.get() % b.get()) + + @router.add_method_handler + @pt.ABIReturnSubroutine + def all_laid_to_args( + _a: pt.abi.Uint64, + _b: pt.abi.Uint64, + _c: pt.abi.Uint64, + _d: pt.abi.Uint64, + _e: pt.abi.Uint64, + _f: pt.abi.Uint64, + _g: pt.abi.Uint64, + _h: pt.abi.Uint64, + _i: pt.abi.Uint64, + _j: pt.abi.Uint64, + _k: pt.abi.Uint64, + _l: pt.abi.Uint64, + _m: pt.abi.Uint64, + _n: pt.abi.Uint64, + _o: pt.abi.Uint64, + _p: pt.abi.Uint64, + *, + output: pt.abi.Uint64, + ): + return output.set( + _a.get() + + _b.get() + + _c.get() + + _d.get() + + _e.get() + + _f.get() + + _g.get() + + _h.get() + + _i.get() + + _j.get() + + _k.get() + + _l.get() + + _m.get() + + _n.get() + + _o.get() + + _p.get() + ) + + router.add_bare_call(pt.Approve(), pt.OnComplete.ClearState) + + ap, csp, contract = router.build_program() + actual_ap_compiled = pt.compileTeal( + ap, pt.Mode.Application, version=6, assembleConstants=True + ) + _ = pt.compileTeal(csp, pt.Mode.Application, version=6, assembleConstants=True) + + expected_ap = """#pragma version 6 +intcblock 0 1 3 +bytecblock 0x151f7c75 +txna ApplicationArgs 0 +pushbytes 0xfe6bdf69 // "add(uint64,uint64)uint64" +== +txn NumAppArgs +intc_2 // 3 +== +&& +txn OnCompletion +intc_0 // NoOp +== +&& +bnz main_l14 +txna ApplicationArgs 0 +pushbytes 0x78b488b7 // "sub(uint64,uint64)uint64" +== +txn NumAppArgs +intc_2 // 3 +== +&& +txn OnCompletion +intc_0 // NoOp +== +&& +bnz main_l13 +txna ApplicationArgs 0 +pushbytes 0xe2f188c5 // "mul(uint64,uint64)uint64" +== +txn NumAppArgs +intc_2 // 3 +== +&& +txn OnCompletion +intc_0 // NoOp +== +&& +bnz main_l12 +txna ApplicationArgs 0 +pushbytes 0x16e80f08 // "div(uint64,uint64)uint64" +== +txn NumAppArgs +intc_2 // 3 +== +&& +txn OnCompletion +intc_0 // NoOp +== +&& +bnz main_l11 +txna ApplicationArgs 0 +pushbytes 0x4dfc58ae // "mod(uint64,uint64)uint64" +== +txn NumAppArgs +intc_2 // 3 +== +&& +txn OnCompletion +intc_0 // NoOp +== +&& +bnz main_l10 +txna ApplicationArgs 0 +pushbytes 0x487ce2fd // "all_laid_to_args(uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64)uint64" +== +txn NumAppArgs +pushint 16 // 16 +== +&& +txn OnCompletion +intc_0 // NoOp +== +&& +bnz main_l9 +txn NumAppArgs +intc_0 // 0 +== +bnz main_l8 +err +main_l8: +intc_1 // 1 +return +main_l9: +txna ApplicationArgs 1 +btoi +store 30 +txna ApplicationArgs 2 +btoi +store 31 +txna ApplicationArgs 3 +btoi +store 32 +txna ApplicationArgs 4 +btoi +store 33 +txna ApplicationArgs 5 +btoi +store 34 +txna ApplicationArgs 6 +btoi +store 35 +txna ApplicationArgs 7 +btoi +store 36 +txna ApplicationArgs 8 +btoi +store 37 +txna ApplicationArgs 9 +btoi +store 38 +txna ApplicationArgs 10 +btoi +store 39 +txna ApplicationArgs 11 +btoi +store 40 +txna ApplicationArgs 12 +btoi +store 41 +txna ApplicationArgs 13 +btoi +store 42 +txna ApplicationArgs 14 +btoi +store 43 +txna ApplicationArgs 15 +store 44 +load 44 +intc_0 // 0 +extract_uint64 +store 45 +load 44 +pushint 8 // 8 +extract_uint64 +store 46 +load 30 +load 31 +load 32 +load 33 +load 34 +load 35 +load 36 +load 37 +load 38 +load 39 +load 40 +load 41 +load 42 +load 43 +load 45 +load 46 +callsub alllaidtoargs_5 +store 47 +bytec_0 // 0x151F7C75 +load 47 +itob +concat +log +intc_1 // 1 +return +main_l10: +txna ApplicationArgs 1 +btoi +store 24 +txna ApplicationArgs 2 +btoi +store 25 +load 24 +load 25 +callsub mod_4 +store 26 +bytec_0 // 0x151F7C75 +load 26 +itob +concat +log +intc_1 // 1 +return +main_l11: +txna ApplicationArgs 1 +btoi +store 18 +txna ApplicationArgs 2 +btoi +store 19 +load 18 +load 19 +callsub div_3 +store 20 +bytec_0 // 0x151F7C75 +load 20 +itob +concat +log +intc_1 // 1 +return +main_l12: +txna ApplicationArgs 1 +btoi +store 12 +txna ApplicationArgs 2 +btoi +store 13 +load 12 +load 13 +callsub mul_2 +store 14 +bytec_0 // 0x151F7C75 +load 14 +itob +concat +log +intc_1 // 1 +return +main_l13: +txna ApplicationArgs 1 +btoi +store 6 +txna ApplicationArgs 2 +btoi +store 7 +load 6 +load 7 +callsub sub_1 +store 8 +bytec_0 // 0x151F7C75 +load 8 +itob +concat +log +intc_1 // 1 +return +main_l14: +txna ApplicationArgs 1 +btoi +store 0 +txna ApplicationArgs 2 +btoi +store 1 +load 0 +load 1 +callsub add_0 +store 2 +bytec_0 // 0x151F7C75 +load 2 +itob +concat +log +intc_1 // 1 +return + +// add +add_0: +store 4 +store 3 +load 3 +load 4 ++ +store 5 +load 5 +retsub + +// sub +sub_1: +store 10 +store 9 +load 9 +load 10 +- +store 11 +load 11 +retsub + +// mul +mul_2: +store 16 +store 15 +load 15 +load 16 +* +store 17 +load 17 +retsub + +// div +div_3: +store 22 +store 21 +load 21 +load 22 +/ +store 23 +load 23 +retsub + +// mod +mod_4: +store 28 +store 27 +load 27 +load 28 +% +store 29 +load 29 +retsub + +// all_laid_to_args +alllaidtoargs_5: +store 63 +store 62 +store 61 +store 60 +store 59 +store 58 +store 57 +store 56 +store 55 +store 54 +store 53 +store 52 +store 51 +store 50 +store 49 +store 48 +load 48 +load 49 ++ +load 50 ++ +load 51 ++ +load 52 ++ +load 53 ++ +load 54 ++ +load 55 ++ +load 56 ++ +load 57 ++ +load 58 ++ +load 59 ++ +load 60 ++ +load 61 ++ +load 62 ++ +load 63 ++ +store 64 +load 64 +retsub""".strip() + assert expected_ap == actual_ap_compiled + + # TODO in construction From 377cb22c3dd5ead17687b074a1ec8ec7cdca2771 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Mon, 16 May 2022 15:06:22 -0400 Subject: [PATCH 110/188] documentation --- pyteal/ast/int.py | 3 + pyteal/ast/router.py | 173 +++++++++++++++++++++++++++++++++++-------- 2 files changed, 146 insertions(+), 30 deletions(-) diff --git a/pyteal/ast/int.py b/pyteal/ast/int.py index 26ef8cff5..cc06710eb 100644 --- a/pyteal/ast/int.py +++ b/pyteal/ast/int.py @@ -64,5 +64,8 @@ def __str__(self): def type_of(self): return TealType.uint64 + def __hash__(self) -> int: + return hash(str(self)) + EnumInt.__module__ = "pyteal" diff --git a/pyteal/ast/router.py b/pyteal/ast/router.py index 44ec3c07b..893305976 100644 --- a/pyteal/ast/router.py +++ b/pyteal/ast/router.py @@ -49,6 +49,15 @@ @dataclass class ProgramNode: + """ + This class contains a condition branch in program AST, with + - `condition`: logical condition of entering such AST branch + - `branch`: steps to execute the branch after entering + - `method_info` (optional): only needed in approval program node, constructed from + - SDK's method + - ABIReturnSubroutine's method signature + """ + condition: Expr branch: Expr method_info: Optional[Method] @@ -58,9 +67,18 @@ class ProgramNode: class Router: - """ """ + """ + Class that help constructs: + - an ARC-4 app's approval/clear-state programs + - and a contract JSON object allowing for easily read and call methods in the contract + """ def __init__(self, name: Optional[str] = None) -> None: + """ + Args: + name (optional): the name of the smart contract, used in the JSON object. + Default name is `contract` + """ self.name: str = "Contract" if name is None else name self.approval_if_then: list[ProgramNode] = [] self.clear_state_if_then: list[ProgramNode] = [] @@ -72,16 +90,46 @@ def __parse_conditions( on_completes: list[EnumInt], creation: bool, ) -> tuple[list[Expr], list[Expr]]: - """ """ - # Check if it is a *CREATION* - approval_conds: list[Expr] = ( - [Txn.application_id() == Int(0)] if creation else [] - ) - clear_state_conds: list[Expr] = [] + """This is a helper function in inferring valid approval/clear-state program condition. + + It starts with some initialization check to resolve some confliction: + - `creation` option is contradicting with OnCompletion.CloseOut and OnCompletion.ClearState + - if there is `method_to_register` existing, then `method_signature` should appear + + Then this function appends conditions to approval/clear-state program condition: + - if `creation` is true, then append `Txn.application_id() == 0` to approval conditions + - if it is handling abi-method, then + `Txn.application_arg[0] == hash(method_signature) && + Txn.application_arg_num == 1 + min(METHOD_ARG_NUM_LIMIT, method's arg num)` + where `METHOD_ARG_NUM_LIMIT == 15`. + - if it is handling conditions for other cases, then `Txn.application_arg_num == 0` + + Args: + method_signature: a string representing method signature for ABI method + method_to_register: an ABIReturnSubroutine if exists, or None + on_completes: a list of OnCompletion args + creation: a boolean variable indicating if this condition is triggered on creation + Returns: + approval_conds: A list of exprs for approval program's condition on: creation?, method/bare, Or[OCs] + clear_state_conds: A list of exprs for clear-state program's condition on: method/bare + """ + + # check that the onComplete has no duplicates + if len(on_completes) != len(set(on_completes)): + raise TealInputError(f"input {on_completes} has duplicates.") - if method_signature == "" and method_to_register is not None: + # Check the existence of OC.CloseOut + close_out_exist = any( + map(lambda x: str(x) == str(OnComplete.CloseOut), on_completes) + ) + # Check the existence of OC.ClearState (needed later) + clear_state_exist = any( + map(lambda x: str(x) == str(OnComplete.ClearState), on_completes) + ) + # Ill formed report if app create with existence of OC.CloseOut or OC.ClearState + if creation and (close_out_exist or clear_state_exist): raise TealInputError( - "A method_signature must only be provided if method_to_register is not None" + "OnComplete ClearState/CloseOut may be ill-formed with app creation" ) # Check: @@ -103,21 +151,20 @@ def __parse_conditions( if method_to_register is not None else Txn.application_args.length() == Int(0) ) - approval_conds.append(method_or_bare_condition) - # Check the existence of OC.CloseOut - close_out_exist = any( - map(lambda x: str(x) == str(OnComplete.CloseOut), on_completes) - ) - # Check the existence of OC.ClearState (needed later) - clear_state_exist = any( - map(lambda x: str(x) == str(OnComplete.ClearState), on_completes) + # Check if it is a *CREATION* + approval_conds: list[Expr] = ( + [Txn.application_id() == Int(0)] if creation else [] ) - # Ill formed report if app create with existence of OC.CloseOut or OC.ClearState - if creation and (close_out_exist or clear_state_exist): + clear_state_conds: list[Expr] = [] + + if method_to_register is not None and not method_signature: raise TealInputError( - "OnComplete ClearState/CloseOut may be ill-formed with app creation" + "A method_signature must only be provided if method_to_register is not None" ) + + approval_conds.append(method_or_bare_condition) + # if OC.ClearState exists, add method-or-bare-condition since it is only needed in ClearStateProgram if clear_state_exist: clear_state_conds.append(method_or_bare_condition) @@ -142,7 +189,25 @@ def __parse_conditions( def __wrap_handler( is_abi_method: bool, handler: ABIReturnSubroutine | SubroutineFnWrapper | Expr ) -> Expr: - """""" + """This is a helper function that handles transaction arguments passing in bare-appcall/abi-method handlers. + + If `is_abi_method` is True, then it can only be `ABIReturnSubroutine`, + otherwise: + - both `ABIReturnSubroutine` and `Subroutine` takes 0 argument on the stack. + - all three cases have none (or void) type. + + On ABI method case, if the ABI method has more than 15 args, this function manages to detuple + the last (16-th) Txn app-arg into a list of ABI method arguments, and pass in to the the ABI method. + + Args: + is_abi_method: a boolean value that specify if the handler is an ABI method. + handler: an `ABIReturnSubroutine`, or `SubroutineFnWrapper` (for `Subroutine` case), or an `Expr`. + Returns: + Expr: + - for bare-appcall it returns an expression that the handler takes no txn arg and Approve + - for abi-method it returns the txn args correctly decomposed into ABI variables, + passed in ABIReturnSubroutine and logged, then approve. + """ if not is_abi_method: match handler: case Expr(): @@ -249,7 +314,15 @@ def __append_to_ast( branch: Expr, method_obj: Optional[Method] = None, ) -> None: - """ """ + """ + A helper function that appends conditions and exeuction of branches into AST. + + Args: + approval_conds: A list of exprs for approval program's condition on: creation?, method/bare, Or[OCs] + clear_state_conds: A list of exprs for clear-state program's condition on: method/bare + branch: A branch of contract executing the registered method + method_obj: SDK's Method object to construct Contract JSON object + """ if len(approval_conditions) > 0: self.approval_if_then.append( ProgramNode( @@ -278,7 +351,15 @@ def add_bare_call( *, creation: bool = False, ) -> None: - """ """ + """ + Registering a bare-appcall to the router. + + Args: + bare_app_call: either an `ABIReturnSubroutine`, or `SubroutineFnWrapper`, or `Expr`. + must take no arguments and evaluate to none (void). + on_completes: a list of OnCompletion args + creation: a boolean variable indicating if this condition is triggered on creation + """ ocList: list[EnumInt] = ( cast(list[EnumInt], on_completes) if isinstance(on_completes, list) @@ -301,7 +382,15 @@ def add_method_handler( on_complete: EnumInt = OnComplete.NoOp, creation: bool = False, ) -> None: - """ """ + """ + Registering an ABI method call to the router. + + Args: + method_app_call: an `ABIReturnSubroutine` that is registrable + method_signature: a method signature string + on_completes: a list of OnCompletion args + creation: a boolean variable indicating if this condition is triggered on creation + """ oc_list: list[EnumInt] = [cast(EnumInt, on_complete)] if method_signature is None: @@ -325,7 +414,19 @@ def add_method_handler( def __ast_construct( ast_list: list[ProgramNode], ) -> Expr: - """ """ + """A helper function in constructing approval/clear-state programs. + + It takes a list of `ProgramNode`s, which contains conditions of entering a condition branch + and the execution of the branch. + + It constructs the program's AST from the list of `ProgramNode`. + + Args: + ast_list: a non-empty list of `ProgramNode`'s containing conditions of entering such branch + and execution of the branch. + Returns: + program: the Cond AST of (approval/clear-state) program from the list of `ProgramNode`. + """ if len(ast_list) == 0: raise TealInputError("ABIRouter: Cannot build program with an empty AST") @@ -334,21 +435,33 @@ def __ast_construct( return program def __contract_construct(self) -> dict[str, Any]: + """A helper function in constructing contract JSON object. + + It takes out the method signatures from approval program `ProgramNode`'s, + and constructs an `Contract` object. + + Returns: + contract: a dictified `Contract` object constructed from + approval program's method signatures and `self.name`. + """ method_collections = [ node.method_info for node in self.approval_if_then if node.method_info ] return Contract(self.name, method_collections).dictify() def build_program(self) -> tuple[Expr, Expr, dict[str, Any]]: - """This method construct ASTs for both the approval and clear programs based on the inputs to the router, - also dump a JSON object of contract to allow client read and call the methods easily. + """ + Connstructs ASTs for approval and clear-state programs from the registered methods in the router, + also generates a JSON object of contract to allow client read and call the methods easily. + Returns: + approval_program: AST for approval program + clear_state_program: AST for clear-state program + contract: JSON object of contract to allow client start off-chain call """ return ( Router.__ast_construct(self.approval_if_then), - Router.__ast_construct(self.clear_state_if_then) - if self.clear_state_if_then - else Approve(), + Router.__ast_construct(self.clear_state_if_then), self.__contract_construct(), ) From 407f3d1d6db96e2778e805a7a9162366f2814c28 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Mon, 16 May 2022 16:50:31 -0400 Subject: [PATCH 111/188] start testcases --- pyteal/ast/router_test.py | 112 +++++++++++++++++++++++++++++++++++++- 1 file changed, 111 insertions(+), 1 deletion(-) diff --git a/pyteal/ast/router_test.py b/pyteal/ast/router_test.py index 464090415..b08b40a84 100644 --- a/pyteal/ast/router_test.py +++ b/pyteal/ast/router_test.py @@ -1 +1,111 @@ -# TODO +# import pytest +import pyteal as pt + +options = pt.CompileOptions(version=5) + + +@pt.ABIReturnSubroutine +def add(a: pt.abi.Uint64, b: pt.abi.Uint64, *, output: pt.abi.Uint64) -> pt.Expr: + return output.set(a.get() + b.get()) + + +@pt.ABIReturnSubroutine +def sub(a: pt.abi.Uint64, b: pt.abi.Uint64, *, output: pt.abi.Uint64) -> pt.Expr: + return output.set(a.get() - b.get()) + + +@pt.ABIReturnSubroutine +def mul(a: pt.abi.Uint64, b: pt.abi.Uint64, *, output: pt.abi.Uint64) -> pt.Expr: + return output.set(a.get() * b.get()) + + +@pt.ABIReturnSubroutine +def div(a: pt.abi.Uint64, b: pt.abi.Uint64, *, output: pt.abi.Uint64) -> pt.Expr: + return output.set(a.get() / b.get()) + + +@pt.ABIReturnSubroutine +def mod(a: pt.abi.Uint64, b: pt.abi.Uint64, *, output: pt.abi.Uint64) -> pt.Expr: + return output.set(a.get() % b.get()) + + +@pt.ABIReturnSubroutine +def qrem( + a: pt.abi.Uint64, + b: pt.abi.Uint64, + *, + output: pt.abi.Tuple2[pt.abi.Uint64, pt.abi.Uint64], +) -> pt.Expr: + return pt.Seq( + (q := pt.abi.Uint64()).set(a.get() / b.get()), + (rem := pt.abi.Uint64()).set(a.get() % b.get()), + output.set(q, rem), + ) + + +@pt.ABIReturnSubroutine +def reverse(a: pt.abi.String, *, output: pt.abi.String) -> pt.Expr: + idx = pt.ScratchVar() + buff = pt.ScratchVar() + + init = idx.store(pt.Int(0)) + cond = idx.load() < a.length() + iter = idx.store(idx.load() + pt.Int(1)) + return pt.Seq( + buff.store(pt.Bytes("")), + pt.For(init, cond, iter).Do( + a[idx.load()].use(lambda v: buff.store(pt.Concat(v.encode(), buff.load()))) + ), + output.set(buff.load()), + ) + + +@pt.ABIReturnSubroutine +def concat_strings( + b: pt.abi.DynamicArray[pt.abi.String], *, output: pt.abi.String +) -> pt.Expr: + idx = pt.ScratchVar() + buff = pt.ScratchVar() + + init = idx.store(pt.Int(0)) + cond = idx.load() < b.length() + iter = idx.store(idx.load() + pt.Int(1)) + return pt.Seq( + buff.store(pt.Bytes("")), + pt.For(init, cond, iter).Do( + b[idx.load()].use(lambda s: buff.store(pt.Concat(buff.load(), s.get()))) + ), + output.set(buff.load()), + ) + + +@pt.ABIReturnSubroutine +def manyargs( + a: pt.abi.Uint64, + b: pt.abi.Uint64, + c: pt.abi.Uint64, + d: pt.abi.Uint64, + e: pt.abi.Uint64, + f: pt.abi.Uint64, + g: pt.abi.Uint64, + h: pt.abi.Uint64, + i: pt.abi.Uint64, + j: pt.abi.Uint64, + k: pt.abi.Uint64, + l: pt.abi.Uint64, + m: pt.abi.Uint64, + n: pt.abi.Uint64, + o: pt.abi.Uint64, + p: pt.abi.Uint64, + q: pt.abi.Uint64, + r: pt.abi.Uint64, + s: pt.abi.Uint64, + t: pt.abi.Uint64, + *, + output: pt.abi.Uint64, +) -> pt.Expr: + return output.set(a.get()) + + +def test(): + pass From 9fbdfecc299408cb4dbf4c9f5620be3667513cdc Mon Sep 17 00:00:00 2001 From: Hang Su Date: Mon, 16 May 2022 17:44:07 -0400 Subject: [PATCH 112/188] update router testcase --- pyteal/ast/router.py | 6 +-- pyteal/ast/router_test.py | 87 +++++++++++++++++++++++++++------------ 2 files changed, 63 insertions(+), 30 deletions(-) diff --git a/pyteal/ast/router.py b/pyteal/ast/router.py index 893305976..44a733008 100644 --- a/pyteal/ast/router.py +++ b/pyteal/ast/router.py @@ -84,7 +84,7 @@ def __init__(self, name: Optional[str] = None) -> None: self.clear_state_if_then: list[ProgramNode] = [] @staticmethod - def __parse_conditions( + def parse_conditions( method_signature: str, method_to_register: ABIReturnSubroutine | None, on_completes: list[EnumInt], @@ -365,7 +365,7 @@ def add_bare_call( if isinstance(on_completes, list) else [cast(EnumInt, on_completes)] ) - approval_conds, clear_state_conds = Router.__parse_conditions( + approval_conds, clear_state_conds = Router.parse_conditions( method_signature="", method_to_register=None, on_completes=ocList, @@ -396,7 +396,7 @@ def add_method_handler( if method_signature is None: method_signature = method_app_call.method_signature() - approval_conds, clear_state_conds = Router.__parse_conditions( + approval_conds, clear_state_conds = Router.parse_conditions( method_signature=method_signature, method_to_register=method_app_call, on_completes=oc_list, diff --git a/pyteal/ast/router_test.py b/pyteal/ast/router_test.py index b08b40a84..06a92da83 100644 --- a/pyteal/ast/router_test.py +++ b/pyteal/ast/router_test.py @@ -1,6 +1,8 @@ # import pytest import pyteal as pt +from pyteal.compiler.compiler import compileTeal + options = pt.CompileOptions(version=5) @@ -50,10 +52,10 @@ def reverse(a: pt.abi.String, *, output: pt.abi.String) -> pt.Expr: init = idx.store(pt.Int(0)) cond = idx.load() < a.length() - iter = idx.store(idx.load() + pt.Int(1)) + _iter = idx.store(idx.load() + pt.Int(1)) return pt.Seq( buff.store(pt.Bytes("")), - pt.For(init, cond, iter).Do( + pt.For(init, cond, _iter).Do( a[idx.load()].use(lambda v: buff.store(pt.Concat(v.encode(), buff.load()))) ), output.set(buff.load()), @@ -69,10 +71,10 @@ def concat_strings( init = idx.store(pt.Int(0)) cond = idx.load() < b.length() - iter = idx.store(idx.load() + pt.Int(1)) + _iter = idx.store(idx.load() + pt.Int(1)) return pt.Seq( buff.store(pt.Bytes("")), - pt.For(init, cond, iter).Do( + pt.For(init, cond, _iter).Do( b[idx.load()].use(lambda s: buff.store(pt.Concat(buff.load(), s.get()))) ), output.set(buff.load()), @@ -80,32 +82,63 @@ def concat_strings( @pt.ABIReturnSubroutine -def manyargs( - a: pt.abi.Uint64, - b: pt.abi.Uint64, - c: pt.abi.Uint64, - d: pt.abi.Uint64, - e: pt.abi.Uint64, - f: pt.abi.Uint64, - g: pt.abi.Uint64, - h: pt.abi.Uint64, - i: pt.abi.Uint64, - j: pt.abi.Uint64, - k: pt.abi.Uint64, - l: pt.abi.Uint64, - m: pt.abi.Uint64, - n: pt.abi.Uint64, - o: pt.abi.Uint64, - p: pt.abi.Uint64, - q: pt.abi.Uint64, - r: pt.abi.Uint64, - s: pt.abi.Uint64, - t: pt.abi.Uint64, +def many_args( + _a: pt.abi.Uint64, + _b: pt.abi.Uint64, + _c: pt.abi.Uint64, + _d: pt.abi.Uint64, + _e: pt.abi.Uint64, + _f: pt.abi.Uint64, + _g: pt.abi.Uint64, + _h: pt.abi.Uint64, + _i: pt.abi.Uint64, + _j: pt.abi.Uint64, + _k: pt.abi.Uint64, + _l: pt.abi.Uint64, + _m: pt.abi.Uint64, + _n: pt.abi.Uint64, + _o: pt.abi.Uint64, + _p: pt.abi.Uint64, + _q: pt.abi.Uint64, + _r: pt.abi.Uint64, + _s: pt.abi.Uint64, + _t: pt.abi.Uint64, *, output: pt.abi.Uint64, ) -> pt.Expr: - return output.set(a.get()) + return output.set(_t.get()) + + +def test_parse_conditions(): + approval_conds, clear_state_conds = pt.Router.parse_conditions( + concat_strings.method_signature(), + concat_strings, + [ + pt.OnComplete.ClearState, + pt.OnComplete.CloseOut, + pt.OnComplete.NoOp, + pt.OnComplete.OptIn, + ], + creation=False, + ) + print() + print([str(x) for x in approval_conds]) + print([str(x) for x in clear_state_conds]) + pass def test(): - pass + router = pt.Router() + router.add_method_handler(many_args) + router.add_bare_call( + pt.Approve(), [pt.OnComplete.ClearState, pt.OnComplete.DeleteApplication] + ) + ap, _, _ = router.build_program() + print( + compileTeal( + ap, + version=6, + mode=pt.Mode.Application, + # optimize=pt.OptimizeOptions(scratch_slots=True), + ) + ) From b348c6563609a218ca4ce962242a7370906ccad5 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Mon, 16 May 2022 17:45:29 -0400 Subject: [PATCH 113/188] docstring tweak --- pyteal/ast/router.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyteal/ast/router.py b/pyteal/ast/router.py index 44a733008..f84dfe921 100644 --- a/pyteal/ast/router.py +++ b/pyteal/ast/router.py @@ -318,8 +318,8 @@ def __append_to_ast( A helper function that appends conditions and exeuction of branches into AST. Args: - approval_conds: A list of exprs for approval program's condition on: creation?, method/bare, Or[OCs] - clear_state_conds: A list of exprs for clear-state program's condition on: method/bare + approval_conditions: A list of exprs for approval program's condition on: creation?, method/bare, Or[OCs] + clear_state_conditions: A list of exprs for clear-state program's condition on: method/bare branch: A branch of contract executing the registered method method_obj: SDK's Method object to construct Contract JSON object """ @@ -388,7 +388,7 @@ def add_method_handler( Args: method_app_call: an `ABIReturnSubroutine` that is registrable method_signature: a method signature string - on_completes: a list of OnCompletion args + on_complete: an OnCompletion args creation: a boolean variable indicating if this condition is triggered on creation """ oc_list: list[EnumInt] = [cast(EnumInt, on_complete)] From d95ae328d62dd791f45e06496bdc1e9d4095de55 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Mon, 16 May 2022 18:11:53 -0400 Subject: [PATCH 114/188] update testcases --- pyteal/ast/router_test.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/pyteal/ast/router_test.py b/pyteal/ast/router_test.py index 06a92da83..ad9caf372 100644 --- a/pyteal/ast/router_test.py +++ b/pyteal/ast/router_test.py @@ -1,8 +1,6 @@ # import pytest import pyteal as pt -from pyteal.compiler.compiler import compileTeal - options = pt.CompileOptions(version=5) @@ -109,6 +107,15 @@ def many_args( return output.set(_t.get()) +@pt.Subroutine(pt.TealType.none) +def safe_clear_state_delete(): + return ( + pt.If(pt.Txn.sender() == pt.Global.creator_address()) + .Then(pt.Approve()) + .Else(pt.Reject()) + ) + + def test_parse_conditions(): approval_conds, clear_state_conds = pt.Router.parse_conditions( concat_strings.method_signature(), @@ -135,7 +142,7 @@ def test(): ) ap, _, _ = router.build_program() print( - compileTeal( + pt.compileTeal( ap, version=6, mode=pt.Mode.Application, From e8a43a7d495bf9f0a84e4d5e14788578757ce7a8 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Mon, 16 May 2022 18:16:25 -0400 Subject: [PATCH 115/188] update testcases --- pyteal/ast/router.py | 6 +++--- pyteal/ast/router_test.py | 6 ++++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/pyteal/ast/router.py b/pyteal/ast/router.py index f84dfe921..e96fe5f07 100644 --- a/pyteal/ast/router.py +++ b/pyteal/ast/router.py @@ -186,7 +186,7 @@ def parse_conditions( return approval_conds, clear_state_conds @staticmethod - def __wrap_handler( + def wrap_handler( is_abi_method: bool, handler: ABIReturnSubroutine | SubroutineFnWrapper | Expr ) -> Expr: """This is a helper function that handles transaction arguments passing in bare-appcall/abi-method handlers. @@ -371,7 +371,7 @@ def add_bare_call( on_completes=ocList, creation=creation, ) - branch = Router.__wrap_handler(False, bare_app_call) + branch = Router.wrap_handler(False, bare_app_call) self.__append_to_ast(approval_conds, clear_state_conds, branch, None) def add_method_handler( @@ -402,7 +402,7 @@ def add_method_handler( on_completes=oc_list, creation=creation, ) - branch = Router.__wrap_handler(True, method_app_call) + branch = Router.wrap_handler(True, method_app_call) self.__append_to_ast( approval_conds, clear_state_conds, diff --git a/pyteal/ast/router_test.py b/pyteal/ast/router_test.py index ad9caf372..afb82a128 100644 --- a/pyteal/ast/router_test.py +++ b/pyteal/ast/router_test.py @@ -116,6 +116,12 @@ def safe_clear_state_delete(): ) +# TODO test parse_condition + +# TODO test wrap_handler + +# TODO test contract JSON object + def test_parse_conditions(): approval_conds, clear_state_conds = pt.Router.parse_conditions( concat_strings.method_signature(), From 19460c04f206cee2174bbf8a6d1a52cbb39722aa Mon Sep 17 00:00:00 2001 From: Hang Su Date: Mon, 16 May 2022 18:18:49 -0400 Subject: [PATCH 116/188] formatting --- pyteal/ast/router_test.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyteal/ast/router_test.py b/pyteal/ast/router_test.py index afb82a128..8956da76e 100644 --- a/pyteal/ast/router_test.py +++ b/pyteal/ast/router_test.py @@ -122,6 +122,7 @@ def safe_clear_state_delete(): # TODO test contract JSON object + def test_parse_conditions(): approval_conds, clear_state_conds = pt.Router.parse_conditions( concat_strings.method_signature(), From fa28de430b907fa260312767f7a86102d7c65e99 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Tue, 17 May 2022 12:55:22 -0400 Subject: [PATCH 117/188] branch cond testing --- pyteal/ast/router.py | 28 +++---- pyteal/ast/router_test.py | 160 +++++++++++++++++++++++++++++++++----- 2 files changed, 156 insertions(+), 32 deletions(-) diff --git a/pyteal/ast/router.py b/pyteal/ast/router.py index e96fe5f07..a1c77d9fe 100644 --- a/pyteal/ast/router.py +++ b/pyteal/ast/router.py @@ -85,8 +85,8 @@ def __init__(self, name: Optional[str] = None) -> None: @staticmethod def parse_conditions( - method_signature: str, - method_to_register: ABIReturnSubroutine | None, + method_signature: Optional[str], + method_to_register: Optional[ABIReturnSubroutine], on_completes: list[EnumInt], creation: bool, ) -> tuple[list[Expr], list[Expr]]: @@ -116,7 +116,7 @@ def parse_conditions( # check that the onComplete has no duplicates if len(on_completes) != len(set(on_completes)): - raise TealInputError(f"input {on_completes} has duplicates.") + raise TealInputError(f"input {on_completes} has duplicated on_complete(s)") # Check the existence of OC.CloseOut close_out_exist = any( @@ -131,6 +131,12 @@ def parse_conditions( raise TealInputError( "OnComplete ClearState/CloseOut may be ill-formed with app creation" ) + # check if there is ABI method but no method_signature is provided + # TODO API change to allow inferring method_signature from method_to_register? + if method_to_register is not None and not method_signature: + raise TealInputError( + "A method_signature must be provided if method_to_register is not None" + ) # Check: # - if current condition is for *ABI METHOD* @@ -138,7 +144,7 @@ def parse_conditions( # - or *BARE APP CALL* (numAppArg == 0) method_or_bare_condition = ( And( - Txn.application_args[0] == MethodSignature(method_signature), + Txn.application_args[0] == MethodSignature(cast(str, method_signature)), Txn.application_args.length() == Int( 1 @@ -158,11 +164,6 @@ def parse_conditions( ) clear_state_conds: list[Expr] = [] - if method_to_register is not None and not method_signature: - raise TealInputError( - "A method_signature must only be provided if method_to_register is not None" - ) - approval_conds.append(method_or_bare_condition) # if OC.ClearState exists, add method-or-bare-condition since it is only needed in ClearStateProgram @@ -278,9 +279,9 @@ def wrap_handler( tuple_abi_args: list[abi.BaseType] = [ t_arg_ts.new_instance() for t_arg_ts in tuple_arg_type_specs ] - tupled_arg: abi.Tuple = cast(abi.Tuple, arg_abi_vars[-1]) + last_tuple_arg: abi.Tuple = cast(abi.Tuple, arg_abi_vars[-1]) de_tuple_instructions: list[Expr] = [ - tupled_arg[i].store_into(tuple_abi_args[i]) + last_tuple_arg[i].store_into(tuple_abi_args[i]) for i in range(len(tuple_arg_type_specs)) ] decode_instructions += de_tuple_instructions @@ -366,7 +367,7 @@ def add_bare_call( else [cast(EnumInt, on_completes)] ) approval_conds, clear_state_conds = Router.parse_conditions( - method_signature="", + method_signature=None, method_to_register=None, on_completes=ocList, creation=creation, @@ -374,6 +375,7 @@ def add_bare_call( branch = Router.wrap_handler(False, bare_app_call) self.__append_to_ast(approval_conds, clear_state_conds, branch, None) + # TODO API should change to allow method signature not overriding? def add_method_handler( self, method_app_call: ABIReturnSubroutine, @@ -391,7 +393,7 @@ def add_method_handler( on_complete: an OnCompletion args creation: a boolean variable indicating if this condition is triggered on creation """ - oc_list: list[EnumInt] = [cast(EnumInt, on_complete)] + oc_list: list[EnumInt] = [on_complete] if method_signature is None: method_signature = method_app_call.method_signature() diff --git a/pyteal/ast/router_test.py b/pyteal/ast/router_test.py index 8956da76e..a834b1f74 100644 --- a/pyteal/ast/router_test.py +++ b/pyteal/ast/router_test.py @@ -1,5 +1,8 @@ -# import pytest import pyteal as pt +import itertools +import pytest +import random +import typing options = pt.CompileOptions(version=5) @@ -116,29 +119,143 @@ def safe_clear_state_delete(): ) -# TODO test parse_condition +GOOD_SUBROUTINE_CASES: list[pt.ABIReturnSubroutine | pt.SubroutineFnWrapper] = [ + add, + sub, + mul, + div, + mod, + qrem, + reverse, + concat_strings, + many_args, + safe_clear_state_delete, +] -# TODO test wrap_handler +ON_COMPLETE_CASES: list[pt.EnumInt] = [ + pt.OnComplete.NoOp, + pt.OnComplete.OptIn, + pt.OnComplete.ClearState, + pt.OnComplete.CloseOut, + pt.OnComplete.UpdateApplication, + pt.OnComplete.DeleteApplication, +] + + +def non_empty_power_set(no_dup_list: list): + masks = [1 << i for i in range(len(no_dup_list))] + for i in range(1, 1 << len(no_dup_list)): + yield [elem for mask, elem in zip(masks, no_dup_list) if i & mask] -# TODO test contract JSON object + +def is_sth_in_oc_list(sth: pt.EnumInt, oc_list: list[pt.EnumInt]): + return any(map(lambda x: str(x) == str(sth), oc_list)) def test_parse_conditions(): - approval_conds, clear_state_conds = pt.Router.parse_conditions( - concat_strings.method_signature(), - concat_strings, - [ - pt.OnComplete.ClearState, - pt.OnComplete.CloseOut, - pt.OnComplete.NoOp, - pt.OnComplete.OptIn, - ], - creation=False, - ) - print() - print([str(x) for x in approval_conds]) - print([str(x) for x in clear_state_conds]) - pass + ON_COMPLETE_COMBINED_CASES = non_empty_power_set(ON_COMPLETE_CASES) + + for subroutine, on_completes, is_creation in itertools.product( + GOOD_SUBROUTINE_CASES, ON_COMPLETE_COMBINED_CASES, [False, True] + ): + if not isinstance(subroutine, pt.ABIReturnSubroutine): + subroutine = None + + method_sig = subroutine.method_signature() if subroutine else None + + if is_creation and ( + is_sth_in_oc_list(pt.OnComplete.CloseOut, on_completes) + or is_sth_in_oc_list(pt.OnComplete.ClearState, on_completes) + ): + with pytest.raises(pt.TealInputError) as err_conflict_conditions: + pt.Router.parse_conditions( + method_sig, subroutine, on_completes, is_creation + ) + assert ( + "OnComplete ClearState/CloseOut may be ill-formed with app creation" + in str(err_conflict_conditions) + ) + continue + + mutated_on_completes = on_completes + [random.choice(on_completes)] + with pytest.raises(pt.TealInputError) as err_dup_oc: + pt.Router.parse_conditions( + method_sig, subroutine, mutated_on_completes, is_creation + ) + assert "has duplicated on_complete(s)" in str(err_dup_oc) + + if subroutine is not None: + with pytest.raises(pt.TealInputError) as err_wrong_override: + pt.Router.parse_conditions(None, subroutine, on_completes, is_creation) + assert ( + "A method_signature must be provided if method_to_register is not None" + in str(err_wrong_override) + ) + + ( + approval_condition_list, + clear_state_condition_list, + ) = pt.Router.parse_conditions( + method_sig, subroutine, on_completes, is_creation + ) + + if not is_sth_in_oc_list(pt.OnComplete.ClearState, on_completes): + assert len(clear_state_condition_list) == 0 + + def assemble_helper(what: pt.Expr) -> pt.TealBlock: + assembled, _ = what.__teal__(options) + assembled.addIncoming() + assembled = pt.TealBlock.NormalizeBlocks(assembled) + return assembled + + assembled_ap_condition_list: list[pt.TealBlock] = [ + assemble_helper(expr) for expr in approval_condition_list + ] + assembled_csp_condition_list: list[pt.TealBlock] = [ + assemble_helper(expr) for expr in clear_state_condition_list + ] + if is_creation: + creation_condition: pt.Expr = pt.Txn.application_id() == pt.Int(0) + assembled_condition = assemble_helper(creation_condition) + with pt.TealComponent.Context.ignoreExprEquality(): + assert assembled_condition in assembled_ap_condition_list + + subroutine_arg_cond: pt.Expr + if subroutine: + max_subroutine_arg_allowed = 1 + min( + pt.METHOD_ARG_NUM_LIMIT, subroutine.subroutine.argument_count() + ) + subroutine_arg_cond = pt.And( + pt.Txn.application_args[0] + == pt.MethodSignature(typing.cast(str, method_sig)), + pt.Txn.application_args.length() == pt.Int(max_subroutine_arg_allowed), + ) + else: + subroutine_arg_cond = pt.Txn.application_args.length() == pt.Int(0) + + assembled_condition = assemble_helper(subroutine_arg_cond) + with pt.TealComponent.Context.ignoreExprEquality(): + assert assembled_condition in assembled_ap_condition_list + + if is_sth_in_oc_list(pt.OnComplete.ClearState, on_completes): + with pt.TealComponent.Context.ignoreExprEquality(): + assert assembled_condition in assembled_csp_condition_list + + if len(on_completes) == 1 and is_sth_in_oc_list( + pt.OnComplete.ClearState, on_completes + ): + continue + + on_completes_cond: pt.Expr = pt.Or( + *[ + pt.Txn.on_completion() == oc + for oc in on_completes + if str(oc) != str(pt.OnComplete.ClearState) + ] + ) + on_completes_cond_assembled = assemble_helper(on_completes_cond) + with pt.TealComponent.Context.ignoreExprEquality(): + assert on_completes_cond_assembled in assembled_ap_condition_list def test(): @@ -156,3 +273,8 @@ def test(): # optimize=pt.OptimizeOptions(scratch_slots=True), ) ) + + +# TODO test wrap_handler + +# TODO test contract JSON object From 9961c1a6c99ff9e673045760129ccf8d4953b28b Mon Sep 17 00:00:00 2001 From: Hang Su Date: Tue, 17 May 2022 14:26:25 -0400 Subject: [PATCH 118/188] router contract test --- pyteal/ast/router.py | 10 +++++----- pyteal/ast/router_test.py | 33 ++++++++++++++++++++++++++++++--- 2 files changed, 35 insertions(+), 8 deletions(-) diff --git a/pyteal/ast/router.py b/pyteal/ast/router.py index a1c77d9fe..78ead95a4 100644 --- a/pyteal/ast/router.py +++ b/pyteal/ast/router.py @@ -188,7 +188,7 @@ def parse_conditions( @staticmethod def wrap_handler( - is_abi_method: bool, handler: ABIReturnSubroutine | SubroutineFnWrapper | Expr + is_method_call: bool, handler: ABIReturnSubroutine | SubroutineFnWrapper | Expr ) -> Expr: """This is a helper function that handles transaction arguments passing in bare-appcall/abi-method handlers. @@ -201,7 +201,7 @@ def wrap_handler( the last (16-th) Txn app-arg into a list of ABI method arguments, and pass in to the the ABI method. Args: - is_abi_method: a boolean value that specify if the handler is an ABI method. + is_method_call: a boolean value that specify if the handler is an ABI method. handler: an `ABIReturnSubroutine`, or `SubroutineFnWrapper` (for `Subroutine` case), or an `Expr`. Returns: Expr: @@ -209,7 +209,7 @@ def wrap_handler( - for abi-method it returns the txn args correctly decomposed into ABI variables, passed in ABIReturnSubroutine and logged, then approve. """ - if not is_abi_method: + if not is_method_call: match handler: case Expr(): if handler.type_of() != TealType.none: @@ -436,7 +436,7 @@ def __ast_construct( return program - def __contract_construct(self) -> dict[str, Any]: + def contract_construct(self) -> dict[str, Any]: """A helper function in constructing contract JSON object. It takes out the method signatures from approval program `ProgramNode`'s, @@ -464,7 +464,7 @@ def build_program(self) -> tuple[Expr, Expr, dict[str, Any]]: return ( Router.__ast_construct(self.approval_if_then), Router.__ast_construct(self.clear_state_if_then), - self.__contract_construct(), + self.contract_construct(), ) diff --git a/pyteal/ast/router_test.py b/pyteal/ast/router_test.py index a834b1f74..80c03efbc 100644 --- a/pyteal/ast/router_test.py +++ b/pyteal/ast/router_test.py @@ -3,6 +3,7 @@ import pytest import random import typing +import algosdk.abi as sdk_abi options = pt.CompileOptions(version=5) @@ -119,6 +120,11 @@ def safe_clear_state_delete(): ) +@pt.ABIReturnSubroutine +def dummy_doing_nothing(): + return pt.Seq(pt.Log(pt.Bytes("a message"))) + + GOOD_SUBROUTINE_CASES: list[pt.ABIReturnSubroutine | pt.SubroutineFnWrapper] = [ add, sub, @@ -130,6 +136,7 @@ def safe_clear_state_delete(): concat_strings, many_args, safe_clear_state_delete, + dummy_doing_nothing, ] ON_COMPLETE_CASES: list[pt.EnumInt] = [ @@ -258,7 +265,8 @@ def assemble_helper(what: pt.Expr) -> pt.TealBlock: assert on_completes_cond_assembled in assembled_ap_condition_list -def test(): +# TODO test wrap_handler +def test_wrap_handler_not_bare_call(): router = pt.Router() router.add_method_handler(many_args) router.add_bare_call( @@ -275,6 +283,25 @@ def test(): ) -# TODO test wrap_handler +def test_wrap_handler_method_call(): + pass + -# TODO test contract JSON object +def test_contract_json_obj(): + ONLY_ABI_SUBROUTINE_CASES = list( + filter(lambda x: isinstance(x, pt.ABIReturnSubroutine), GOOD_SUBROUTINE_CASES) + ) + ONLY_ABI_SUBROUTINE_CASES = random.choices(ONLY_ABI_SUBROUTINE_CASES, k=6) + for index, case in enumerate(non_empty_power_set(ONLY_ABI_SUBROUTINE_CASES)): + contract_name = f"contract_{index}" + router = pt.Router(contract_name) + method_list: list[sdk_abi.contract.Method] = [] + for subroutine in case: + router.add_method_handler(subroutine) + method_list.append( + sdk_abi.Method.from_signature(subroutine.method_signature()) + ) + router.add_bare_call(safe_clear_state_delete, pt.OnComplete.ClearState) + sdk_contract = sdk_abi.contract.Contract(contract_name, method_list) + contract = router.contract_construct() + assert sdk_contract.dictify() == contract From 95f90c7a3bb4934399f471191ca1896e6b8abafa Mon Sep 17 00:00:00 2001 From: Hang Su Date: Tue, 17 May 2022 14:54:47 -0400 Subject: [PATCH 119/188] subroutine branch wrapper bare call case tested --- pyteal/ast/router_test.py | 100 ++++++++++++++++++++++++++------------ 1 file changed, 70 insertions(+), 30 deletions(-) diff --git a/pyteal/ast/router_test.py b/pyteal/ast/router_test.py index 80c03efbc..696291ff2 100644 --- a/pyteal/ast/router_test.py +++ b/pyteal/ast/router_test.py @@ -125,6 +125,21 @@ def dummy_doing_nothing(): return pt.Seq(pt.Log(pt.Bytes("a message"))) +@pt.Subroutine(pt.TealType.uint64) +def returning_u64(): + return pt.Int(1) + + +@pt.Subroutine(pt.TealType.none) +def mult_over_u64_and_log(a: pt.Expr, b: pt.Expr): + return pt.Log(pt.Itob(a * b)) + + +@pt.ABIReturnSubroutine +def eine_constant(*, output: pt.abi.Uint64): + return output.set(1) + + GOOD_SUBROUTINE_CASES: list[pt.ABIReturnSubroutine | pt.SubroutineFnWrapper] = [ add, sub, @@ -265,24 +280,48 @@ def assemble_helper(what: pt.Expr) -> pt.TealBlock: assert on_completes_cond_assembled in assembled_ap_condition_list -# TODO test wrap_handler -def test_wrap_handler_not_bare_call(): - router = pt.Router() - router.add_method_handler(many_args) - router.add_bare_call( - pt.Approve(), [pt.OnComplete.ClearState, pt.OnComplete.DeleteApplication] - ) - ap, _, _ = router.build_program() - print( - pt.compileTeal( - ap, - version=6, - mode=pt.Mode.Application, - # optimize=pt.OptimizeOptions(scratch_slots=True), - ) - ) +def test_wrap_handler_bare_call(): + BARE_CALL_CASES = [ + dummy_doing_nothing, + safe_clear_state_delete, + pt.Approve(), + pt.Log(pt.Bytes("message")), + ] + for bare_call in BARE_CALL_CASES: + wrapped: pt.Expr = pt.Router.wrap_handler(False, bare_call) + match bare_call: + case pt.Expr(): + if bare_call.has_return(): + assert wrapped == bare_call + else: + assert wrapped == pt.Seq(bare_call, pt.Approve()) + case pt.SubroutineFnWrapper() | pt.ABIReturnSubroutine(): + assert wrapped == pt.Seq(bare_call(), pt.Approve()) + case _: + raise pt.TealInputError("how you got here?") + + ERROR_CASES = [ + (pt.Int(1), "bare appcall handler should have type to be none."), + ( + returning_u64, + f"subroutine call should be returning none not {pt.TealType.uint64}.", + ), + ( + mult_over_u64_and_log, + "subroutine call should take 0 arg for bare-app call. this subroutine takes 2.", + ), + ( + eine_constant, + f"abi-returning subroutine call should be returning void not {pt.abi.Uint64TypeSpec()}.", + ), + ] + for error_case, error_msg in ERROR_CASES: + with pytest.raises(pt.TealInputError) as bug: + pt.Router.wrap_handler(False, error_case) + assert error_msg in str(bug) +# TODO test wrap_handler def test_wrap_handler_method_call(): pass @@ -291,17 +330,18 @@ def test_contract_json_obj(): ONLY_ABI_SUBROUTINE_CASES = list( filter(lambda x: isinstance(x, pt.ABIReturnSubroutine), GOOD_SUBROUTINE_CASES) ) - ONLY_ABI_SUBROUTINE_CASES = random.choices(ONLY_ABI_SUBROUTINE_CASES, k=6) - for index, case in enumerate(non_empty_power_set(ONLY_ABI_SUBROUTINE_CASES)): - contract_name = f"contract_{index}" - router = pt.Router(contract_name) - method_list: list[sdk_abi.contract.Method] = [] - for subroutine in case: - router.add_method_handler(subroutine) - method_list.append( - sdk_abi.Method.from_signature(subroutine.method_signature()) - ) - router.add_bare_call(safe_clear_state_delete, pt.OnComplete.ClearState) - sdk_contract = sdk_abi.contract.Contract(contract_name, method_list) - contract = router.contract_construct() - assert sdk_contract.dictify() == contract + for _ in range(4): + ONLY_ABI_SUBROUTINE_CASES = random.choices(ONLY_ABI_SUBROUTINE_CASES, k=5) + for index, case in enumerate(non_empty_power_set(ONLY_ABI_SUBROUTINE_CASES)): + contract_name = f"contract_{index}" + router = pt.Router(contract_name) + method_list: list[sdk_abi.contract.Method] = [] + for subroutine in case: + router.add_method_handler(subroutine) + method_list.append( + sdk_abi.Method.from_signature(subroutine.method_signature()) + ) + router.add_bare_call(safe_clear_state_delete, pt.OnComplete.ClearState) + sdk_contract = sdk_abi.contract.Contract(contract_name, method_list) + contract = router.contract_construct() + assert sdk_contract.dictify() == contract From 8923ef5261bd37e753c001cc2948bfeceb8a3f45 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Tue, 17 May 2022 15:00:42 -0400 Subject: [PATCH 120/188] more testcases to barecall --- pyteal/ast/router.py | 2 +- pyteal/ast/router_test.py | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/pyteal/ast/router.py b/pyteal/ast/router.py index 78ead95a4..5fba65524 100644 --- a/pyteal/ast/router.py +++ b/pyteal/ast/router.py @@ -241,7 +241,7 @@ def wrap_handler( return Seq(cast(Expr, handler()), Approve()) case _: raise TealInputError( - "bare appcall can only accept: none type + Seq (no ret) or Subroutine (with ret)" + "bare appcall can only accept: none type Expr, or Subroutine/ABIReturnSubroutine with none return and no arg" ) else: if not isinstance(handler, ABIReturnSubroutine): diff --git a/pyteal/ast/router_test.py b/pyteal/ast/router_test.py index 696291ff2..45303228f 100644 --- a/pyteal/ast/router_test.py +++ b/pyteal/ast/router_test.py @@ -140,6 +140,11 @@ def eine_constant(*, output: pt.abi.Uint64): return output.set(1) +@pt.ABIReturnSubroutine +def take_abi_and_log(tb_logged: pt.abi.String): + return pt.Log(tb_logged.get()) + + GOOD_SUBROUTINE_CASES: list[pt.ABIReturnSubroutine | pt.SubroutineFnWrapper] = [ add, sub, @@ -314,6 +319,14 @@ def test_wrap_handler_bare_call(): eine_constant, f"abi-returning subroutine call should be returning void not {pt.abi.Uint64TypeSpec()}.", ), + ( + take_abi_and_log, + "abi-returning subroutine call should take 0 arg for bare-app call. this abi-returning subroutine takes 1.", + ), + ( + 1, + "bare appcall can only accept: none type Expr, or Subroutine/ABIReturnSubroutine with none return and no arg", + ), ] for error_case, error_msg in ERROR_CASES: with pytest.raises(pt.TealInputError) as bug: From 35762237efd70802cc1868d98efb11b4526deefb Mon Sep 17 00:00:00 2001 From: Hang Su Date: Tue, 17 May 2022 15:07:26 -0400 Subject: [PATCH 121/188] minor, need to work on method call wrapper --- pyteal/ast/router.py | 2 +- pyteal/ast/router_test.py | 13 ++++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/pyteal/ast/router.py b/pyteal/ast/router.py index 5fba65524..28ac50bbc 100644 --- a/pyteal/ast/router.py +++ b/pyteal/ast/router.py @@ -250,7 +250,7 @@ def wrap_handler( ) if not handler.is_registrable(): raise TealInputError( - f"method call ABIReturnSubroutine is not registable" + f"method call ABIReturnSubroutine is not registrable" f"got {handler.subroutine.argument_count()} args with {len(handler.subroutine.abi_args)} ABI args." ) diff --git a/pyteal/ast/router_test.py b/pyteal/ast/router_test.py index 45303228f..b5cb4290f 100644 --- a/pyteal/ast/router_test.py +++ b/pyteal/ast/router_test.py @@ -145,6 +145,11 @@ def take_abi_and_log(tb_logged: pt.abi.String): return pt.Log(tb_logged.get()) +@pt.ABIReturnSubroutine +def not_registrable(lhs: pt.abi.Uint64, rhs: pt.Expr, *, output: pt.abi.Uint64): + return output.set(lhs.get() * rhs) + + GOOD_SUBROUTINE_CASES: list[pt.ABIReturnSubroutine | pt.SubroutineFnWrapper] = [ add, sub, @@ -336,7 +341,13 @@ def test_wrap_handler_bare_call(): # TODO test wrap_handler def test_wrap_handler_method_call(): - pass + with pytest.raises(pt.TealInputError) as bug: + pt.Router.wrap_handler(True, not_registrable) + assert "method call ABIReturnSubroutine is not registrable" in str(bug) + + with pytest.raises(pt.TealInputError) as bug: + pt.Router.wrap_handler(True, safe_clear_state_delete) + assert "method call should be only registering ABIReturnSubroutine" in str(bug) def test_contract_json_obj(): From b8ea59a174aff66453a1397a1f47119338d51ad4 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Tue, 17 May 2022 15:45:12 -0400 Subject: [PATCH 122/188] half done on method call wrapper --- pyteal/ast/router.py | 6 +++--- pyteal/ast/router_test.py | 42 +++++++++++++++++++++++++++++++++------ 2 files changed, 39 insertions(+), 9 deletions(-) diff --git a/pyteal/ast/router.py b/pyteal/ast/router.py index 28ac50bbc..9ec5f10ab 100644 --- a/pyteal/ast/router.py +++ b/pyteal/ast/router.py @@ -258,10 +258,10 @@ def wrap_handler( list[abi.TypeSpec], handler.subroutine.expected_arg_types ) if handler.subroutine.argument_count() > METHOD_ARG_NUM_LIMIT: - to_be_tupled_specs = arg_type_specs[METHOD_ARG_NUM_LIMIT - 1 :] + last_arg_specs_grouped = arg_type_specs[METHOD_ARG_NUM_LIMIT - 1 :] arg_type_specs = arg_type_specs[: METHOD_ARG_NUM_LIMIT - 1] - tupled_spec = abi.TupleTypeSpec(*to_be_tupled_specs) - arg_type_specs.append(tupled_spec) + last_arg_spec = abi.TupleTypeSpec(*last_arg_specs_grouped) + arg_type_specs.append(last_arg_spec) arg_abi_vars: list[abi.BaseType] = [ type_spec.new_instance() for type_spec in arg_type_specs diff --git a/pyteal/ast/router_test.py b/pyteal/ast/router_test.py index b5cb4290f..23fc00f31 100644 --- a/pyteal/ast/router_test.py +++ b/pyteal/ast/router_test.py @@ -162,6 +162,8 @@ def not_registrable(lhs: pt.abi.Uint64, rhs: pt.Expr, *, output: pt.abi.Uint64): many_args, safe_clear_state_delete, dummy_doing_nothing, + eine_constant, + take_abi_and_log, ] ON_COMPLETE_CASES: list[pt.EnumInt] = [ @@ -184,6 +186,13 @@ def is_sth_in_oc_list(sth: pt.EnumInt, oc_list: list[pt.EnumInt]): return any(map(lambda x: str(x) == str(sth), oc_list)) +def assemble_helper(what: pt.Expr) -> pt.TealBlock: + assembled, _ = what.__teal__(options) + assembled.addIncoming() + assembled = pt.TealBlock.NormalizeBlocks(assembled) + return assembled + + def test_parse_conditions(): ON_COMPLETE_COMBINED_CASES = non_empty_power_set(ON_COMPLETE_CASES) @@ -234,12 +243,6 @@ def test_parse_conditions(): if not is_sth_in_oc_list(pt.OnComplete.ClearState, on_completes): assert len(clear_state_condition_list) == 0 - def assemble_helper(what: pt.Expr) -> pt.TealBlock: - assembled, _ = what.__teal__(options) - assembled.addIncoming() - assembled = pt.TealBlock.NormalizeBlocks(assembled) - return assembled - assembled_ap_condition_list: list[pt.TealBlock] = [ assemble_helper(expr) for expr in approval_condition_list ] @@ -349,6 +352,33 @@ def test_wrap_handler_method_call(): pt.Router.wrap_handler(True, safe_clear_state_delete) assert "method call should be only registering ABIReturnSubroutine" in str(bug) + ONLY_ABI_SUBROUTINE_CASES = list( + filter(lambda x: isinstance(x, pt.ABIReturnSubroutine), GOOD_SUBROUTINE_CASES) + ) + for abi_subroutine in ONLY_ABI_SUBROUTINE_CASES: + wrapped: pt.Expr = pt.Router.wrap_handler(True, abi_subroutine) + assembled_wrapped: pt.TealBlock = assemble_helper(wrapped) + print(assembled_wrapped) + if abi_subroutine.subroutine.argument_count() > pt.METHOD_ARG_NUM_LIMIT: + # TODO + continue + args: list[pt.abi.BaseType] = [ + spec.new_instance() + for spec in typing.cast( + list[pt.abi.TypeSpec], abi_subroutine.subroutine.expected_arg_types + ) + ] + loading: list[pt.Expr] = [ + arg.decode(pt.Txn.application_args[index + 1]) + for index, arg in enumerate(args) + ] + if abi_subroutine.type_of() != "void": + # TODO + continue + actual = assemble_helper(pt.Seq(*loading, abi_subroutine(*args), pt.Approve())) + with pt.TealComponent.Context.ignoreScratchSlotEquality(), pt.TealComponent.Context.ignoreExprEquality(): + assert actual == assembled_wrapped + def test_contract_json_obj(): ONLY_ABI_SUBROUTINE_CASES = list( From a940f5e936e34906d216eab3c2a0a04403cb4e05 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Tue, 17 May 2022 15:49:14 -0400 Subject: [PATCH 123/188] update on abi method wrapper with return --- pyteal/ast/router_test.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/pyteal/ast/router_test.py b/pyteal/ast/router_test.py index 23fc00f31..f98bd6e22 100644 --- a/pyteal/ast/router_test.py +++ b/pyteal/ast/router_test.py @@ -358,10 +358,10 @@ def test_wrap_handler_method_call(): for abi_subroutine in ONLY_ABI_SUBROUTINE_CASES: wrapped: pt.Expr = pt.Router.wrap_handler(True, abi_subroutine) assembled_wrapped: pt.TealBlock = assemble_helper(wrapped) - print(assembled_wrapped) if abi_subroutine.subroutine.argument_count() > pt.METHOD_ARG_NUM_LIMIT: # TODO continue + print(assembled_wrapped) args: list[pt.abi.BaseType] = [ spec.new_instance() for spec in typing.cast( @@ -372,10 +372,16 @@ def test_wrap_handler_method_call(): arg.decode(pt.Txn.application_args[index + 1]) for index, arg in enumerate(args) ] + evaluate: pt.Expr if abi_subroutine.type_of() != "void": - # TODO - continue - actual = assemble_helper(pt.Seq(*loading, abi_subroutine(*args), pt.Approve())) + output_temp = abi_subroutine.output_kwarg_info.abi_type.new_instance() + evaluate = pt.Seq( + abi_subroutine(*args).store_into(output_temp), + pt.abi.MethodReturn(output_temp), + ) + else: + evaluate = abi_subroutine(*args) + actual = assemble_helper(pt.Seq(*loading, evaluate, pt.Approve())) with pt.TealComponent.Context.ignoreScratchSlotEquality(), pt.TealComponent.Context.ignoreExprEquality(): assert actual == assembled_wrapped From 648d075a38d5e5ce61a76a3cc46aa6d3d86df24f Mon Sep 17 00:00:00 2001 From: Hang Su Date: Tue, 17 May 2022 16:35:27 -0400 Subject: [PATCH 124/188] detuple testcase --- pyteal/ast/router_test.py | 41 ++++++++++++++++++++++++++++++--------- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/pyteal/ast/router_test.py b/pyteal/ast/router_test.py index f98bd6e22..1a40f1257 100644 --- a/pyteal/ast/router_test.py +++ b/pyteal/ast/router_test.py @@ -342,7 +342,6 @@ def test_wrap_handler_bare_call(): assert error_msg in str(bug) -# TODO test wrap_handler def test_wrap_handler_method_call(): with pytest.raises(pt.TealInputError) as bug: pt.Router.wrap_handler(True, not_registrable) @@ -358,20 +357,43 @@ def test_wrap_handler_method_call(): for abi_subroutine in ONLY_ABI_SUBROUTINE_CASES: wrapped: pt.Expr = pt.Router.wrap_handler(True, abi_subroutine) assembled_wrapped: pt.TealBlock = assemble_helper(wrapped) - if abi_subroutine.subroutine.argument_count() > pt.METHOD_ARG_NUM_LIMIT: - # TODO - continue - print(assembled_wrapped) + args: list[pt.abi.BaseType] = [ spec.new_instance() for spec in typing.cast( list[pt.abi.TypeSpec], abi_subroutine.subroutine.expected_arg_types ) ] - loading: list[pt.Expr] = [ - arg.decode(pt.Txn.application_args[index + 1]) - for index, arg in enumerate(args) - ] + + loading: list[pt.Expr] + + if abi_subroutine.subroutine.argument_count() > pt.METHOD_ARG_NUM_LIMIT: + sdk_last_arg = pt.abi.TupleTypeSpec( + *[ + spec + for spec in typing.cast( + list[pt.abi.TypeSpec], + abi_subroutine.subroutine.expected_arg_types, + )[pt.METHOD_ARG_NUM_LIMIT - 1 :] + ] + ).new_instance() + loading = [ + arg.decode(pt.Txn.application_args[index + 1]) + for index, arg in enumerate(args[: pt.METHOD_ARG_NUM_LIMIT - 1]) + ] + loading.append( + sdk_last_arg.decode(pt.Txn.application_args[pt.METHOD_ARG_NUM_LIMIT]) + ) + for i in range(pt.METHOD_ARG_NUM_LIMIT - 1, len(args)): + loading.append( + sdk_last_arg[i - pt.METHOD_ARG_NUM_LIMIT + 1].store_into(args[i]) + ) + else: + loading = [ + arg.decode(pt.Txn.application_args[index + 1]) + for index, arg in enumerate(args) + ] + evaluate: pt.Expr if abi_subroutine.type_of() != "void": output_temp = abi_subroutine.output_kwarg_info.abi_type.new_instance() @@ -381,6 +403,7 @@ def test_wrap_handler_method_call(): ) else: evaluate = abi_subroutine(*args) + actual = assemble_helper(pt.Seq(*loading, evaluate, pt.Approve())) with pt.TealComponent.Context.ignoreScratchSlotEquality(), pt.TealComponent.Context.ignoreExprEquality(): assert actual == assembled_wrapped From 7a1c456b6faffb7df6a516d5cbf1089f22a60dda Mon Sep 17 00:00:00 2001 From: Hang Su Date: Tue, 17 May 2022 17:15:05 -0400 Subject: [PATCH 125/188] compiler test need to see csp --- pyteal/compiler/compiler_test.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/pyteal/compiler/compiler_test.py b/pyteal/compiler/compiler_test.py index 7c10ead1f..584de14fc 100644 --- a/pyteal/compiler/compiler_test.py +++ b/pyteal/compiler/compiler_test.py @@ -2266,11 +2266,13 @@ def all_laid_to_args( router.add_bare_call(pt.Approve(), pt.OnComplete.ClearState) - ap, csp, contract = router.build_program() + ap, csp, _ = router.build_program() actual_ap_compiled = pt.compileTeal( ap, pt.Mode.Application, version=6, assembleConstants=True ) - _ = pt.compileTeal(csp, pt.Mode.Application, version=6, assembleConstants=True) + actual_csp_compiled = pt.compileTeal( + csp, pt.Mode.Application, version=6, assembleConstants=True + ) expected_ap = """#pragma version 6 intcblock 0 1 3 @@ -2633,4 +2635,13 @@ def all_laid_to_args( retsub""".strip() assert expected_ap == actual_ap_compiled - # TODO in construction + expected_csp = """#pragma version 6 +txn NumAppArgs +pushint 0 // 0 +== +bnz main_l2 +err +main_l2: +pushint 1 // 1 +return""".strip() + assert expected_csp == actual_csp_compiled From 818e83944ebea201feedf12139a62034be236a74 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Tue, 17 May 2022 17:27:03 -0400 Subject: [PATCH 126/188] rm redundant type annotation --- pyteal/ast/router.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyteal/ast/router.py b/pyteal/ast/router.py index 9ec5f10ab..358804408 100644 --- a/pyteal/ast/router.py +++ b/pyteal/ast/router.py @@ -254,7 +254,7 @@ def wrap_handler( f"got {handler.subroutine.argument_count()} args with {len(handler.subroutine.abi_args)} ABI args." ) - arg_type_specs: list[abi.TypeSpec] = cast( + arg_type_specs = cast( list[abi.TypeSpec], handler.subroutine.expected_arg_types ) if handler.subroutine.argument_count() > METHOD_ARG_NUM_LIMIT: From 5fc8610f874f346f6e3ed0a62e697407566731b0 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Tue, 17 May 2022 17:48:56 -0400 Subject: [PATCH 127/188] more error msg --- pyteal/ast/router.py | 2 +- pyteal/ast/router_test.py | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/pyteal/ast/router.py b/pyteal/ast/router.py index 358804408..c255af8f9 100644 --- a/pyteal/ast/router.py +++ b/pyteal/ast/router.py @@ -214,7 +214,7 @@ def wrap_handler( case Expr(): if handler.type_of() != TealType.none: raise TealInputError( - "bare appcall handler should have type to be none." + f"bare appcall handler should be TealType.none not {handler.type_of()}." ) return handler if handler.has_return() else Seq(handler, Approve()) case SubroutineFnWrapper(): diff --git a/pyteal/ast/router_test.py b/pyteal/ast/router_test.py index 1a40f1257..dd696ca54 100644 --- a/pyteal/ast/router_test.py +++ b/pyteal/ast/router_test.py @@ -314,7 +314,10 @@ def test_wrap_handler_bare_call(): raise pt.TealInputError("how you got here?") ERROR_CASES = [ - (pt.Int(1), "bare appcall handler should have type to be none."), + ( + pt.Int(1), + f"bare appcall handler should be TealType.none not {pt.TealType.uint64}.", + ), ( returning_u64, f"subroutine call should be returning none not {pt.TealType.uint64}.", From 8f73760016f353e36ef18bf0467a8fbbc473cd49 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Wed, 18 May 2022 12:44:39 -0400 Subject: [PATCH 128/188] renaming --- pyteal/ast/router_test.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pyteal/ast/router_test.py b/pyteal/ast/router_test.py index dd696ca54..5f8d9f568 100644 --- a/pyteal/ast/router_test.py +++ b/pyteal/ast/router_test.py @@ -182,7 +182,7 @@ def non_empty_power_set(no_dup_list: list): yield [elem for mask, elem in zip(masks, no_dup_list) if i & mask] -def is_sth_in_oc_list(sth: pt.EnumInt, oc_list: list[pt.EnumInt]): +def oncomplete_is_in_oc_list(sth: pt.EnumInt, oc_list: list[pt.EnumInt]): return any(map(lambda x: str(x) == str(sth), oc_list)) @@ -205,8 +205,8 @@ def test_parse_conditions(): method_sig = subroutine.method_signature() if subroutine else None if is_creation and ( - is_sth_in_oc_list(pt.OnComplete.CloseOut, on_completes) - or is_sth_in_oc_list(pt.OnComplete.ClearState, on_completes) + oncomplete_is_in_oc_list(pt.OnComplete.CloseOut, on_completes) + or oncomplete_is_in_oc_list(pt.OnComplete.ClearState, on_completes) ): with pytest.raises(pt.TealInputError) as err_conflict_conditions: pt.Router.parse_conditions( @@ -240,7 +240,7 @@ def test_parse_conditions(): method_sig, subroutine, on_completes, is_creation ) - if not is_sth_in_oc_list(pt.OnComplete.ClearState, on_completes): + if not oncomplete_is_in_oc_list(pt.OnComplete.ClearState, on_completes): assert len(clear_state_condition_list) == 0 assembled_ap_condition_list: list[pt.TealBlock] = [ @@ -272,11 +272,11 @@ def test_parse_conditions(): with pt.TealComponent.Context.ignoreExprEquality(): assert assembled_condition in assembled_ap_condition_list - if is_sth_in_oc_list(pt.OnComplete.ClearState, on_completes): + if oncomplete_is_in_oc_list(pt.OnComplete.ClearState, on_completes): with pt.TealComponent.Context.ignoreExprEquality(): assert assembled_condition in assembled_csp_condition_list - if len(on_completes) == 1 and is_sth_in_oc_list( + if len(on_completes) == 1 and oncomplete_is_in_oc_list( pt.OnComplete.ClearState, on_completes ): continue From c5982176a597654250bf1a83ba9d235dd50d5870 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Wed, 18 May 2022 12:58:14 -0400 Subject: [PATCH 129/188] renaming is_registrable -> is_abi_routable --- pyteal/ast/router.py | 2 +- pyteal/ast/subroutine.py | 4 ++-- pyteal/ast/subroutine_test.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pyteal/ast/router.py b/pyteal/ast/router.py index c255af8f9..111efdbe1 100644 --- a/pyteal/ast/router.py +++ b/pyteal/ast/router.py @@ -248,7 +248,7 @@ def wrap_handler( raise TealInputError( f"method call should be only registering ABIReturnSubroutine, got {type(handler)}." ) - if not handler.is_registrable(): + if not handler.is_abi_routable(): raise TealInputError( f"method call ABIReturnSubroutine is not registrable" f"got {handler.subroutine.argument_count()} args with {len(handler.subroutine.abi_args)} ABI args." diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index bbae5825e..2517c6740 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -574,7 +574,7 @@ def name(self) -> str: return self.subroutine.name() def method_signature(self) -> str: - if not self.is_registrable(): + if not self.is_abi_routable(): raise TealInputError( "Only registrable methods may return a method signature" ) @@ -589,7 +589,7 @@ def type_of(self) -> str | abi.TypeSpec: else self.output_kwarg_info.abi_type ) - def is_registrable(self) -> bool: + def is_abi_routable(self) -> bool: return len(self.subroutine.abi_args) == self.subroutine.argument_count() diff --git a/pyteal/ast/subroutine_test.py b/pyteal/ast/subroutine_test.py index f0a31e25b..55be4e96b 100644 --- a/pyteal/ast/subroutine_test.py +++ b/pyteal/ast/subroutine_test.py @@ -207,11 +207,11 @@ def fn_2arg_1ret_with_expr( assert isinstance( invoked, (pt.Expr if case.ret_type == "void" else pt.abi.ReturnedValue) ) - assert case.definition.is_registrable() == all( + assert case.definition.is_abi_routable() == all( map(lambda x: isinstance(x, pt.abi.BaseType), case.arg_instances) ) - if case.definition.is_registrable(): + if case.definition.is_abi_routable(): assert case.definition.method_signature() == case.signature else: with pytest.raises(pt.TealInputError): From a1d2c2163a410217eec1f784df0f0ce4ca6fb80d Mon Sep 17 00:00:00 2001 From: Hang Su Date: Wed, 18 May 2022 13:14:12 -0400 Subject: [PATCH 130/188] reording imports, eliminate anti patterns --- pyteal/ast/router.py | 10 +++++----- pyteal/ast/router_test.py | 22 ++++++++++++++++------ 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/pyteal/ast/router.py b/pyteal/ast/router.py index 111efdbe1..ba070cbfb 100644 --- a/pyteal/ast/router.py +++ b/pyteal/ast/router.py @@ -1,6 +1,6 @@ -from algosdk.abi import Contract, Method from dataclasses import dataclass from typing import Any, cast, Optional +import algosdk.abi as sdk_abi from pyteal.config import METHOD_ARG_NUM_LIMIT from pyteal.errors import TealInputError @@ -60,7 +60,7 @@ class ProgramNode: condition: Expr branch: Expr - method_info: Optional[Method] + method_info: Optional[sdk_abi.Method] ProgramNode.__module__ = "pyteal" @@ -313,7 +313,7 @@ def __append_to_ast( approval_conditions: list[Expr], clear_state_conditions: list[Expr], branch: Expr, - method_obj: Optional[Method] = None, + method_obj: Optional[sdk_abi.Method] = None, ) -> None: """ A helper function that appends conditions and exeuction of branches into AST. @@ -409,7 +409,7 @@ def add_method_handler( approval_conds, clear_state_conds, branch, - Method.from_signature(method_signature), + sdk_abi.Method.from_signature(method_signature), ) @staticmethod @@ -449,7 +449,7 @@ def contract_construct(self) -> dict[str, Any]: method_collections = [ node.method_info for node in self.approval_if_then if node.method_info ] - return Contract(self.name, method_collections).dictify() + return sdk_abi.Contract(self.name, method_collections).dictify() def build_program(self) -> tuple[Expr, Expr, dict[str, Any]]: """ diff --git a/pyteal/ast/router_test.py b/pyteal/ast/router_test.py index 5f8d9f568..6a1a25993 100644 --- a/pyteal/ast/router_test.py +++ b/pyteal/ast/router_test.py @@ -202,7 +202,8 @@ def test_parse_conditions(): if not isinstance(subroutine, pt.ABIReturnSubroutine): subroutine = None - method_sig = subroutine.method_signature() if subroutine else None + is_abi_subroutine = isinstance(subroutine, pt.ABIReturnSubroutine) + method_sig = subroutine.method_signature() if is_abi_subroutine else None if is_creation and ( oncomplete_is_in_oc_list(pt.OnComplete.CloseOut, on_completes) @@ -210,7 +211,10 @@ def test_parse_conditions(): ): with pytest.raises(pt.TealInputError) as err_conflict_conditions: pt.Router.parse_conditions( - method_sig, subroutine, on_completes, is_creation + method_sig, + subroutine if is_abi_subroutine else None, + on_completes, + is_creation, ) assert ( "OnComplete ClearState/CloseOut may be ill-formed with app creation" @@ -221,11 +225,14 @@ def test_parse_conditions(): mutated_on_completes = on_completes + [random.choice(on_completes)] with pytest.raises(pt.TealInputError) as err_dup_oc: pt.Router.parse_conditions( - method_sig, subroutine, mutated_on_completes, is_creation + method_sig, + subroutine if is_abi_subroutine else None, + mutated_on_completes, + is_creation, ) assert "has duplicated on_complete(s)" in str(err_dup_oc) - if subroutine is not None: + if is_abi_subroutine: with pytest.raises(pt.TealInputError) as err_wrong_override: pt.Router.parse_conditions(None, subroutine, on_completes, is_creation) assert ( @@ -237,7 +244,10 @@ def test_parse_conditions(): approval_condition_list, clear_state_condition_list, ) = pt.Router.parse_conditions( - method_sig, subroutine, on_completes, is_creation + method_sig, + subroutine if is_abi_subroutine else None, + on_completes, + is_creation, ) if not oncomplete_is_in_oc_list(pt.OnComplete.ClearState, on_completes): @@ -256,7 +266,7 @@ def test_parse_conditions(): assert assembled_condition in assembled_ap_condition_list subroutine_arg_cond: pt.Expr - if subroutine: + if is_abi_subroutine: max_subroutine_arg_allowed = 1 + min( pt.METHOD_ARG_NUM_LIMIT, subroutine.subroutine.argument_count() ) From a0a764832c6398824e9e28bba1963a8bd8030551 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Wed, 18 May 2022 13:18:47 -0400 Subject: [PATCH 131/188] remove anti-pattern --- pyteal/ast/router_test.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/pyteal/ast/router_test.py b/pyteal/ast/router_test.py index 6a1a25993..3ea02ce30 100644 --- a/pyteal/ast/router_test.py +++ b/pyteal/ast/router_test.py @@ -199,9 +199,6 @@ def test_parse_conditions(): for subroutine, on_completes, is_creation in itertools.product( GOOD_SUBROUTINE_CASES, ON_COMPLETE_COMBINED_CASES, [False, True] ): - if not isinstance(subroutine, pt.ABIReturnSubroutine): - subroutine = None - is_abi_subroutine = isinstance(subroutine, pt.ABIReturnSubroutine) method_sig = subroutine.method_signature() if is_abi_subroutine else None From 4d2e57e07dc71df66b48ff2b5bee813579f2079b Mon Sep 17 00:00:00 2001 From: Hang Su Date: Wed, 18 May 2022 13:19:41 -0400 Subject: [PATCH 132/188] section it out --- pyteal/ast/router.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyteal/ast/router.py b/pyteal/ast/router.py index ba070cbfb..9d3a53cb2 100644 --- a/pyteal/ast/router.py +++ b/pyteal/ast/router.py @@ -1,5 +1,6 @@ from dataclasses import dataclass from typing import Any, cast, Optional + import algosdk.abi as sdk_abi from pyteal.config import METHOD_ARG_NUM_LIMIT From e162275ee59388497723e2ca6131b9241a4a046c Mon Sep 17 00:00:00 2001 From: Hang Su Date: Wed, 18 May 2022 13:30:19 -0400 Subject: [PATCH 133/188] non_empty_power_set -> power_set --- pyteal/ast/router.py | 2 ++ pyteal/ast/router_test.py | 19 +++++++++++++++---- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/pyteal/ast/router.py b/pyteal/ast/router.py index 9d3a53cb2..85a428b29 100644 --- a/pyteal/ast/router.py +++ b/pyteal/ast/router.py @@ -118,6 +118,8 @@ def parse_conditions( # check that the onComplete has no duplicates if len(on_completes) != len(set(on_completes)): raise TealInputError(f"input {on_completes} has duplicated on_complete(s)") + if len(on_completes) == 0: + raise TealInputError("on complete input should be non-empty list") # Check the existence of OC.CloseOut close_out_exist = any( diff --git a/pyteal/ast/router_test.py b/pyteal/ast/router_test.py index 3ea02ce30..a4d01cd3e 100644 --- a/pyteal/ast/router_test.py +++ b/pyteal/ast/router_test.py @@ -176,9 +176,9 @@ def not_registrable(lhs: pt.abi.Uint64, rhs: pt.Expr, *, output: pt.abi.Uint64): ] -def non_empty_power_set(no_dup_list: list): +def power_set(no_dup_list: list): masks = [1 << i for i in range(len(no_dup_list))] - for i in range(1, 1 << len(no_dup_list)): + for i in range(1 << len(no_dup_list)): yield [elem for mask, elem in zip(masks, no_dup_list) if i & mask] @@ -194,7 +194,7 @@ def assemble_helper(what: pt.Expr) -> pt.TealBlock: def test_parse_conditions(): - ON_COMPLETE_COMBINED_CASES = non_empty_power_set(ON_COMPLETE_CASES) + ON_COMPLETE_COMBINED_CASES = power_set(ON_COMPLETE_CASES) for subroutine, on_completes, is_creation in itertools.product( GOOD_SUBROUTINE_CASES, ON_COMPLETE_COMBINED_CASES, [False, True] @@ -202,6 +202,17 @@ def test_parse_conditions(): is_abi_subroutine = isinstance(subroutine, pt.ABIReturnSubroutine) method_sig = subroutine.method_signature() if is_abi_subroutine else None + if len(on_completes) == 0: + with pytest.raises(pt.TealInputError) as err_no_oc: + pt.Router.parse_conditions( + method_sig, + subroutine if is_abi_subroutine else None, + on_completes, + is_creation, + ) + assert "on complete input should be non-empty list" in str(err_no_oc) + continue + if is_creation and ( oncomplete_is_in_oc_list(pt.OnComplete.CloseOut, on_completes) or oncomplete_is_in_oc_list(pt.OnComplete.ClearState, on_completes) @@ -425,7 +436,7 @@ def test_contract_json_obj(): ) for _ in range(4): ONLY_ABI_SUBROUTINE_CASES = random.choices(ONLY_ABI_SUBROUTINE_CASES, k=5) - for index, case in enumerate(non_empty_power_set(ONLY_ABI_SUBROUTINE_CASES)): + for index, case in enumerate(power_set(ONLY_ABI_SUBROUTINE_CASES)): contract_name = f"contract_{index}" router = pt.Router(contract_name) method_list: list[sdk_abi.contract.Method] = [] From 2dafe23674fd9d7b92c1a4258979fa2075cd1ace Mon Sep 17 00:00:00 2001 From: Hang Su Date: Wed, 18 May 2022 16:27:59 -0400 Subject: [PATCH 134/188] add conflict detection to protect AST from overshadowing --- pyteal/ast/router.py | 113 +++++++++++++++++++++++++++++-- pyteal/ast/router_test.py | 6 +- pyteal/compiler/compiler_test.py | 35 ++++------ 3 files changed, 126 insertions(+), 28 deletions(-) diff --git a/pyteal/ast/router.py b/pyteal/ast/router.py index 85a428b29..711b47b02 100644 --- a/pyteal/ast/router.py +++ b/pyteal/ast/router.py @@ -62,11 +62,79 @@ class ProgramNode: condition: Expr branch: Expr method_info: Optional[sdk_abi.Method] + ast_order_indicator: "ConflictMapElem" ProgramNode.__module__ = "pyteal" +@dataclass +class ConflictMapElem: + is_method_call: bool + method_name: str + on_creation: bool + + def __lt__(self, other: "ConflictMapElem"): + # compare under same oc condition + # can be used to order AST + if not isinstance(other, ConflictMapElem): + raise TealInputError( + "ConflictMapElem can only check conflict with other ConflictMapElem" + ) + + if self.is_method_call: + if not other.is_method_call: + return False + else: + if other.is_method_call: + return True + + # either both is_method_call or not + # compare on_creation + if self.on_creation: + return not other.on_creation + else: + return other.on_creation + + def has_conflict_with(self, other: "ConflictMapElem"): + if not isinstance(other, ConflictMapElem): + raise TealInputError( + "ConflictMapElem can only check conflict with other ConflictMapElem" + ) + if not self.is_method_call and not other.is_method_call: + if self.method_name == other.method_name: + raise TealInputError(f"re-registering {self.method_name} under same OC") + else: + raise TealInputError( + f"re-registering {self.method_name} and {other.method_name} under same OC" + ) + + +ConflictMapElem.__module__ = "pyteal" + + +class ASTConflictResolver: + def __init__(self): + self.conflict_detect_map: dict[str, list[ConflictMapElem]] = { + name: list() for name in dir(OnComplete) if not name.startswith("__") + } + + def add_elem_to(self, oc: str, conflict_map_elem: ConflictMapElem): + if oc not in self.conflict_detect_map: + raise TealInputError( + f"{oc} is not one of the element in conflict map, should be one of the OnCompletes" + ) + elems_under_oc: list[ConflictMapElem] = self.conflict_detect_map[oc] + for elem in elems_under_oc: + if elem.has_conflict_with(conflict_map_elem): + raise TealInputError(f"{elem} has conflict with {conflict_map_elem}") + + self.conflict_detect_map[oc].append(conflict_map_elem) + + +ASTConflictResolver.__module__ = "pyteal" + + class Router: """ Class that help constructs: @@ -83,6 +151,7 @@ def __init__(self, name: Optional[str] = None) -> None: self.name: str = "Contract" if name is None else name self.approval_if_then: list[ProgramNode] = [] self.clear_state_if_then: list[ProgramNode] = [] + self.conflict_detect_map: ASTConflictResolver = ASTConflictResolver() @staticmethod def parse_conditions( @@ -134,6 +203,10 @@ def parse_conditions( raise TealInputError( "OnComplete ClearState/CloseOut may be ill-formed with app creation" ) + # Check if anything other than ClearState exists + oc_other_than_clear_state_exists = any( + map(lambda x: str(x) != str(OnComplete.ClearState), on_completes) + ) # check if there is ABI method but no method_signature is provided # TODO API change to allow inferring method_signature from method_to_register? if method_to_register is not None and not method_signature: @@ -167,7 +240,8 @@ def parse_conditions( ) clear_state_conds: list[Expr] = [] - approval_conds.append(method_or_bare_condition) + if oc_other_than_clear_state_exists: + approval_conds.append(method_or_bare_condition) # if OC.ClearState exists, add method-or-bare-condition since it is only needed in ClearStateProgram if clear_state_exist: @@ -316,16 +390,17 @@ def __append_to_ast( approval_conditions: list[Expr], clear_state_conditions: list[Expr], branch: Expr, + ast_order_indicator: ConflictMapElem, method_obj: Optional[sdk_abi.Method] = None, ) -> None: """ - A helper function that appends conditions and exeuction of branches into AST. + A helper function that appends conditions and execution of branches into AST. Args: approval_conditions: A list of exprs for approval program's condition on: creation?, method/bare, Or[OCs] clear_state_conditions: A list of exprs for clear-state program's condition on: method/bare branch: A branch of contract executing the registered method - method_obj: SDK's Method object to construct Contract JSON object + method_obj: SDK's Method objects to construct Contract JSON object """ if len(approval_conditions) > 0: self.approval_if_then.append( @@ -335,6 +410,7 @@ def __append_to_ast( else approval_conditions[0], branch, method_obj, + ast_order_indicator, ) ) if len(clear_state_conditions) > 0: @@ -345,6 +421,7 @@ def __append_to_ast( else clear_state_conditions[0], branch, method_obj, + ast_order_indicator, ) ) @@ -364,7 +441,7 @@ def add_bare_call( on_completes: a list of OnCompletion args creation: a boolean variable indicating if this condition is triggered on creation """ - ocList: list[EnumInt] = ( + oc_list: list[EnumInt] = ( cast(list[EnumInt], on_completes) if isinstance(on_completes, list) else [cast(EnumInt, on_completes)] @@ -372,11 +449,27 @@ def add_bare_call( approval_conds, clear_state_conds = Router.parse_conditions( method_signature=None, method_to_register=None, - on_completes=ocList, + on_completes=oc_list, creation=creation, ) branch = Router.wrap_handler(False, bare_app_call) - self.__append_to_ast(approval_conds, clear_state_conds, branch, None) + method_name: str + match bare_app_call: + case ABIReturnSubroutine(): + method_name = bare_app_call.method_signature() + case SubroutineFnWrapper(): + method_name = bare_app_call.name() + case Expr(): + method_name = str(bare_app_call) + case _: + raise TealInputError("...") + + ast_order_indicator = ConflictMapElem(False, method_name, creation) + for oc in oc_list: + self.conflict_detect_map.add_elem_to(oc.name, ast_order_indicator) + self.__append_to_ast( + approval_conds, clear_state_conds, branch, ast_order_indicator, None + ) # TODO API should change to allow method signature not overriding? def add_method_handler( @@ -408,10 +501,16 @@ def add_method_handler( creation=creation, ) branch = Router.wrap_handler(True, method_app_call) + ast_order_indicator = ConflictMapElem( + True, method_app_call.method_signature(), creation + ) + for oc in oc_list: + self.conflict_detect_map.add_elem_to(oc.name, ast_order_indicator) self.__append_to_ast( approval_conds, clear_state_conds, branch, + ast_order_indicator, sdk_abi.Method.from_signature(method_signature), ) @@ -435,6 +534,8 @@ def __ast_construct( if len(ast_list) == 0: raise TealInputError("ABIRouter: Cannot build program with an empty AST") + sorted(ast_list, key=lambda x: x.ast_order_indicator) + program: Cond = Cond(*[[node.condition, node.branch] for node in ast_list]) return program diff --git a/pyteal/ast/router_test.py b/pyteal/ast/router_test.py index a4d01cd3e..768db4518 100644 --- a/pyteal/ast/router_test.py +++ b/pyteal/ast/router_test.py @@ -288,7 +288,11 @@ def test_parse_conditions(): assembled_condition = assemble_helper(subroutine_arg_cond) with pt.TealComponent.Context.ignoreExprEquality(): - assert assembled_condition in assembled_ap_condition_list + if not ( + len(on_completes) == 1 + and oncomplete_is_in_oc_list(pt.OnComplete.ClearState, on_completes) + ): + assert assembled_condition in assembled_ap_condition_list if oncomplete_is_in_oc_list(pt.OnComplete.ClearState, on_completes): with pt.TealComponent.Context.ignoreExprEquality(): diff --git a/pyteal/compiler/compiler_test.py b/pyteal/compiler/compiler_test.py index 584de14fc..9a65c768b 100644 --- a/pyteal/compiler/compiler_test.py +++ b/pyteal/compiler/compiler_test.py @@ -2198,6 +2198,8 @@ def access_b4_store(magic_num: pt.abi.Uint64, *, output: pt.abi.Uint64): def test_router_app(): router = pt.Router() + router.add_bare_call(pt.Approve(), pt.OnComplete.ClearState) + @router.add_method_handler @pt.ABIReturnSubroutine def add(a: pt.abi.Uint64, b: pt.abi.Uint64, *, output: pt.abi.Uint64) -> pt.Expr: @@ -2264,8 +2266,6 @@ def all_laid_to_args( + _p.get() ) - router.add_bare_call(pt.Approve(), pt.OnComplete.ClearState) - ap, csp, _ = router.build_program() actual_ap_compiled = pt.compileTeal( ap, pt.Mode.Application, version=6, assembleConstants=True @@ -2288,7 +2288,7 @@ def all_laid_to_args( intc_0 // NoOp == && -bnz main_l14 +bnz main_l12 txna ApplicationArgs 0 pushbytes 0x78b488b7 // "sub(uint64,uint64)uint64" == @@ -2300,7 +2300,7 @@ def all_laid_to_args( intc_0 // NoOp == && -bnz main_l13 +bnz main_l11 txna ApplicationArgs 0 pushbytes 0xe2f188c5 // "mul(uint64,uint64)uint64" == @@ -2312,7 +2312,7 @@ def all_laid_to_args( intc_0 // NoOp == && -bnz main_l12 +bnz main_l10 txna ApplicationArgs 0 pushbytes 0x16e80f08 // "div(uint64,uint64)uint64" == @@ -2324,7 +2324,7 @@ def all_laid_to_args( intc_0 // NoOp == && -bnz main_l11 +bnz main_l9 txna ApplicationArgs 0 pushbytes 0x4dfc58ae // "mod(uint64,uint64)uint64" == @@ -2336,7 +2336,7 @@ def all_laid_to_args( intc_0 // NoOp == && -bnz main_l10 +bnz main_l8 txna ApplicationArgs 0 pushbytes 0x487ce2fd // "all_laid_to_args(uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64)uint64" == @@ -2348,16 +2348,9 @@ def all_laid_to_args( intc_0 // NoOp == && -bnz main_l9 -txn NumAppArgs -intc_0 // 0 -== -bnz main_l8 +bnz main_l7 err -main_l8: -intc_1 // 1 -return -main_l9: +main_l7: txna ApplicationArgs 1 btoi store 30 @@ -2435,7 +2428,7 @@ def all_laid_to_args( log intc_1 // 1 return -main_l10: +main_l8: txna ApplicationArgs 1 btoi store 24 @@ -2453,7 +2446,7 @@ def all_laid_to_args( log intc_1 // 1 return -main_l11: +main_l9: txna ApplicationArgs 1 btoi store 18 @@ -2471,7 +2464,7 @@ def all_laid_to_args( log intc_1 // 1 return -main_l12: +main_l10: txna ApplicationArgs 1 btoi store 12 @@ -2489,7 +2482,7 @@ def all_laid_to_args( log intc_1 // 1 return -main_l13: +main_l11: txna ApplicationArgs 1 btoi store 6 @@ -2507,7 +2500,7 @@ def all_laid_to_args( log intc_1 // 1 return -main_l14: +main_l12: txna ApplicationArgs 1 btoi store 0 From 11f4cff601c3b1e107bd2e4909e5467883367ef9 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Wed, 18 May 2022 16:38:49 -0400 Subject: [PATCH 135/188] error message --- pyteal/ast/router.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pyteal/ast/router.py b/pyteal/ast/router.py index 711b47b02..a701852a8 100644 --- a/pyteal/ast/router.py +++ b/pyteal/ast/router.py @@ -462,7 +462,10 @@ def add_bare_call( case Expr(): method_name = str(bare_app_call) case _: - raise TealInputError("...") + raise TealInputError( + f"bare app call can only be one of three following cases: " + f"{ABIReturnSubroutine, SubroutineFnWrapper, Expr}" + ) ast_order_indicator = ConflictMapElem(False, method_name, creation) for oc in oc_list: From 241e7ee7571c76a13bb33f94a810b1b633744efd Mon Sep 17 00:00:00 2001 From: Hang Su Date: Thu, 19 May 2022 10:28:42 -0400 Subject: [PATCH 136/188] shrinked commits on pr review --- pyteal/ast/router.py | 4 ++-- pyteal/ast/router_test.py | 27 +++++++++++---------------- pyteal/ast/subroutine_test.py | 2 ++ 3 files changed, 15 insertions(+), 18 deletions(-) diff --git a/pyteal/ast/router.py b/pyteal/ast/router.py index a701852a8..2844d8a41 100644 --- a/pyteal/ast/router.py +++ b/pyteal/ast/router.py @@ -274,8 +274,8 @@ def wrap_handler( - both `ABIReturnSubroutine` and `Subroutine` takes 0 argument on the stack. - all three cases have none (or void) type. - On ABI method case, if the ABI method has more than 15 args, this function manages to detuple - the last (16-th) Txn app-arg into a list of ABI method arguments, and pass in to the the ABI method. + On ABI method case, if the ABI method has more than 15 args, this function manages to de-tuple + the last (16-th) Txn app-arg into a list of ABI method arguments, and pass in to the ABI method. Args: is_method_call: a boolean value that specify if the handler is an ABI method. diff --git a/pyteal/ast/router_test.py b/pyteal/ast/router_test.py index 768db4518..9cf89397e 100644 --- a/pyteal/ast/router_test.py +++ b/pyteal/ast/router_test.py @@ -435,21 +435,16 @@ def test_wrap_handler_method_call(): def test_contract_json_obj(): - ONLY_ABI_SUBROUTINE_CASES = list( + abi_subroutines = list( filter(lambda x: isinstance(x, pt.ABIReturnSubroutine), GOOD_SUBROUTINE_CASES) ) - for _ in range(4): - ONLY_ABI_SUBROUTINE_CASES = random.choices(ONLY_ABI_SUBROUTINE_CASES, k=5) - for index, case in enumerate(power_set(ONLY_ABI_SUBROUTINE_CASES)): - contract_name = f"contract_{index}" - router = pt.Router(contract_name) - method_list: list[sdk_abi.contract.Method] = [] - for subroutine in case: - router.add_method_handler(subroutine) - method_list.append( - sdk_abi.Method.from_signature(subroutine.method_signature()) - ) - router.add_bare_call(safe_clear_state_delete, pt.OnComplete.ClearState) - sdk_contract = sdk_abi.contract.Contract(contract_name, method_list) - contract = router.contract_construct() - assert sdk_contract.dictify() == contract + contract_name = "contract_name" + router = pt.Router(contract_name) + method_list: list[sdk_abi.contract.Method] = [] + for subroutine in abi_subroutines: + router.add_method_handler(subroutine) + method_list.append(sdk_abi.Method.from_signature(subroutine.method_signature())) + router.add_bare_call(safe_clear_state_delete, pt.OnComplete.ClearState) + sdk_contract = sdk_abi.contract.Contract(contract_name, method_list) + contract = router.contract_construct() + assert sdk_contract.dictify() == contract diff --git a/pyteal/ast/subroutine_test.py b/pyteal/ast/subroutine_test.py index 55be4e96b..b07e41d07 100644 --- a/pyteal/ast/subroutine_test.py +++ b/pyteal/ast/subroutine_test.py @@ -9,6 +9,8 @@ options = pt.CompileOptions(version=5) +# something here + def test_subroutine_definition(): def fn0Args(): From 9b1205a329d28b3c10756a984c9175eee0a32167 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Thu, 19 May 2022 11:18:54 -0400 Subject: [PATCH 137/188] add compile_program --- pyteal/ast/router.py | 56 ++++++++++++++++++++++++++------ pyteal/ast/subroutine_test.py | 2 -- pyteal/compiler/compiler_test.py | 8 ++--- 3 files changed, 48 insertions(+), 18 deletions(-) diff --git a/pyteal/ast/router.py b/pyteal/ast/router.py index 2844d8a41..c2bd03690 100644 --- a/pyteal/ast/router.py +++ b/pyteal/ast/router.py @@ -23,6 +23,9 @@ from pyteal.ast.txn import Txn from pyteal.ast.return_ import Approve +from pyteal.compiler.compiler import compileTeal, DEFAULT_TEAL_VERSION, OptimizeOptions +from pyteal.ir.ops import Mode + """ Notes: - On a BareApp Call, check @@ -31,7 +34,7 @@ - [x] Must execute actions required to invoke the method - On Method Call, check - - [x] txna ApplicationArgs 0 == method "method-signature" + - [x] txn ApplicationArgs 0 == method "method-signature" - [x] On-Completion should match (only one On-Completion specified here?) - [x] non void method call should log with 0x151f7c75 return-method-specifier (kinda done in another PR to ABI-Type) @@ -39,12 +42,12 @@ (kinda done, but need to do with extraction and (en/de)-code) - [x] Must execute actions required to invoke the method - [x] extract arguments if needed - (decode txna ApplicationArgs 15 if there exists, and extract arguments to feed method) + (decode txn ApplicationArgs 15 if there exists, and extract arguments to feed method) Notes for OC: -- creation conflict with closeout and clearstate +- creation conflict with closeout and clear-state - must check: txn ApplicationId == 0 for creation -- clearstate AST build should be separated with other OC AST build +- clear-state AST build should be separated with other OC AST build """ @@ -162,7 +165,7 @@ def parse_conditions( ) -> tuple[list[Expr], list[Expr]]: """This is a helper function in inferring valid approval/clear-state program condition. - It starts with some initialization check to resolve some confliction: + It starts with some initialization check to resolve some conflict: - `creation` option is contradicting with OnCompletion.CloseOut and OnCompletion.ClearState - if there is `method_to_register` existing, then `method_signature` should appear @@ -267,7 +270,7 @@ def parse_conditions( def wrap_handler( is_method_call: bool, handler: ABIReturnSubroutine | SubroutineFnWrapper | Expr ) -> Expr: - """This is a helper function that handles transaction arguments passing in bare-appcall/abi-method handlers. + """This is a helper function that handles transaction arguments passing in bare-app-call/abi-method handlers. If `is_abi_method` is True, then it can only be `ABIReturnSubroutine`, otherwise: @@ -397,8 +400,8 @@ def __append_to_ast( A helper function that appends conditions and execution of branches into AST. Args: - approval_conditions: A list of exprs for approval program's condition on: creation?, method/bare, Or[OCs] - clear_state_conditions: A list of exprs for clear-state program's condition on: method/bare + approval_conditions: A list of expressions for approval program's condition on: creation?, method/bare, Or[OCs] + clear_state_conditions: A list of expressions for clear-state program's condition on: method/bare branch: A branch of contract executing the registered method method_obj: SDK's Method objects to construct Contract JSON object """ @@ -433,7 +436,7 @@ def add_bare_call( creation: bool = False, ) -> None: """ - Registering a bare-appcall to the router. + Registering a bare-app-call to the router. Args: bare_app_call: either an `ABIReturnSubroutine`, or `SubroutineFnWrapper`, or `Expr`. @@ -560,7 +563,7 @@ def contract_construct(self) -> dict[str, Any]: def build_program(self) -> tuple[Expr, Expr, dict[str, Any]]: """ - Connstructs ASTs for approval and clear-state programs from the registered methods in the router, + Constructs ASTs for approval and clear-state programs from the registered methods in the router, also generates a JSON object of contract to allow client read and call the methods easily. Returns: @@ -574,5 +577,38 @@ def build_program(self) -> tuple[Expr, Expr, dict[str, Any]]: self.contract_construct(), ) + def compile_program( + self, + *, + version: int = DEFAULT_TEAL_VERSION, + assembleConstants: bool = False, + optimize: OptimizeOptions = None, + ) -> tuple[str, str, dict[str, Any]]: + """ + Combining `build_program` and `compileTeal`, compiles built Approval and ClearState programs + and returns Contract JSON object for off-chain calling. + + Returns: + approval_program: compiled approval program + clear_state_program: compiled clear-state program + contract: JSON object of contract to allow client start off-chain call + """ + ap, csp, contract = self.build_program() + ap_compiled = compileTeal( + ap, + Mode.Application, + version=version, + assembleConstants=assembleConstants, + optimize=optimize, + ) + csp_compiled = compileTeal( + csp, + Mode.Application, + version=version, + assembleConstants=assembleConstants, + optimize=optimize, + ) + return ap_compiled, csp_compiled, contract + Router.__module__ = "pyteal" diff --git a/pyteal/ast/subroutine_test.py b/pyteal/ast/subroutine_test.py index b07e41d07..55be4e96b 100644 --- a/pyteal/ast/subroutine_test.py +++ b/pyteal/ast/subroutine_test.py @@ -9,8 +9,6 @@ options = pt.CompileOptions(version=5) -# something here - def test_subroutine_definition(): def fn0Args(): diff --git a/pyteal/compiler/compiler_test.py b/pyteal/compiler/compiler_test.py index 9a65c768b..d2320b35f 100644 --- a/pyteal/compiler/compiler_test.py +++ b/pyteal/compiler/compiler_test.py @@ -2266,12 +2266,8 @@ def all_laid_to_args( + _p.get() ) - ap, csp, _ = router.build_program() - actual_ap_compiled = pt.compileTeal( - ap, pt.Mode.Application, version=6, assembleConstants=True - ) - actual_csp_compiled = pt.compileTeal( - csp, pt.Mode.Application, version=6, assembleConstants=True + actual_ap_compiled, actual_csp_compiled, _ = router.compile_program( + version=6, assembleConstants=True ) expected_ap = """#pragma version 6 From 912b16b384356c8144b3eefee21baca46f000947 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Thu, 19 May 2022 11:50:52 -0400 Subject: [PATCH 138/188] fixup subroutine testcase --- pyteal/ast/subroutine_test.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pyteal/ast/subroutine_test.py b/pyteal/ast/subroutine_test.py index 775f3d99f..3d1934cdf 100644 --- a/pyteal/ast/subroutine_test.py +++ b/pyteal/ast/subroutine_test.py @@ -1,5 +1,5 @@ from itertools import product -from typing import List, Literal +from typing import List, Literal, Optional, cast import pytest from dataclasses import dataclass @@ -86,7 +86,7 @@ class ABISubroutineTC: arg_instances: list[pt.Expr | pt.abi.BaseType] name: str ret_type: str | pt.abi.TypeSpec - signature: str + signature: Optional[str] def test_abi_subroutine_definition(): @@ -206,6 +206,7 @@ def fn_w_tuple1arg( ], "fn_w_tuple1arg", pt.abi.ByteTypeSpec(), + None, ), ) @@ -230,7 +231,7 @@ def fn_w_tuple1arg( ) if case.definition.is_abi_routable(): - assert case.definition.method_signature() == case.signature + assert case.definition.method_signature() == cast(str, case.signature) else: with pytest.raises(pt.TealInputError): case.definition.method_signature() From d28fdc6bc18b0a0c06a900cc5ca2171e8b7c0100 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Thu, 19 May 2022 13:02:23 -0400 Subject: [PATCH 139/188] remove method signature specifying, directly infer from ABIReturnSubroutine --- pyteal/ast/router.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/pyteal/ast/router.py b/pyteal/ast/router.py index c2bd03690..48d0819ea 100644 --- a/pyteal/ast/router.py +++ b/pyteal/ast/router.py @@ -477,12 +477,10 @@ def add_bare_call( approval_conds, clear_state_conds, branch, ast_order_indicator, None ) - # TODO API should change to allow method signature not overriding? def add_method_handler( self, method_app_call: ABIReturnSubroutine, *, - method_signature: str = None, on_complete: EnumInt = OnComplete.NoOp, creation: bool = False, ) -> None: @@ -491,14 +489,11 @@ def add_method_handler( Args: method_app_call: an `ABIReturnSubroutine` that is registrable - method_signature: a method signature string on_complete: an OnCompletion args creation: a boolean variable indicating if this condition is triggered on creation """ oc_list: list[EnumInt] = [on_complete] - - if method_signature is None: - method_signature = method_app_call.method_signature() + method_signature = method_app_call.method_signature() approval_conds, clear_state_conds = Router.parse_conditions( method_signature=method_signature, From e8b47655f0ef9089f5837faf0b1ae7a73970fe52 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Thu, 19 May 2022 13:13:13 -0400 Subject: [PATCH 140/188] remove method sign field, directly infer from ABIReturnSubroutine --- pyteal/ast/router.py | 14 +++----------- pyteal/ast/router_test.py | 12 ------------ 2 files changed, 3 insertions(+), 23 deletions(-) diff --git a/pyteal/ast/router.py b/pyteal/ast/router.py index 48d0819ea..6b08907e2 100644 --- a/pyteal/ast/router.py +++ b/pyteal/ast/router.py @@ -158,7 +158,6 @@ def __init__(self, name: Optional[str] = None) -> None: @staticmethod def parse_conditions( - method_signature: Optional[str], method_to_register: Optional[ABIReturnSubroutine], on_completes: list[EnumInt], creation: bool, @@ -178,7 +177,6 @@ def parse_conditions( - if it is handling conditions for other cases, then `Txn.application_arg_num == 0` Args: - method_signature: a string representing method signature for ABI method method_to_register: an ABIReturnSubroutine if exists, or None on_completes: a list of OnCompletion args creation: a boolean variable indicating if this condition is triggered on creation @@ -210,12 +208,6 @@ def parse_conditions( oc_other_than_clear_state_exists = any( map(lambda x: str(x) != str(OnComplete.ClearState), on_completes) ) - # check if there is ABI method but no method_signature is provided - # TODO API change to allow inferring method_signature from method_to_register? - if method_to_register is not None and not method_signature: - raise TealInputError( - "A method_signature must be provided if method_to_register is not None" - ) # Check: # - if current condition is for *ABI METHOD* @@ -223,7 +215,8 @@ def parse_conditions( # - or *BARE APP CALL* (numAppArg == 0) method_or_bare_condition = ( And( - Txn.application_args[0] == MethodSignature(cast(str, method_signature)), + Txn.application_args[0] + == MethodSignature(method_to_register.method_signature()), Txn.application_args.length() == Int( 1 @@ -234,6 +227,7 @@ def parse_conditions( ), ) if method_to_register is not None + # TODO the default condition for bare call need to be revised else Txn.application_args.length() == Int(0) ) @@ -450,7 +444,6 @@ def add_bare_call( else [cast(EnumInt, on_completes)] ) approval_conds, clear_state_conds = Router.parse_conditions( - method_signature=None, method_to_register=None, on_completes=oc_list, creation=creation, @@ -496,7 +489,6 @@ def add_method_handler( method_signature = method_app_call.method_signature() approval_conds, clear_state_conds = Router.parse_conditions( - method_signature=method_signature, method_to_register=method_app_call, on_completes=oc_list, creation=creation, diff --git a/pyteal/ast/router_test.py b/pyteal/ast/router_test.py index 9cf89397e..9a428b5c2 100644 --- a/pyteal/ast/router_test.py +++ b/pyteal/ast/router_test.py @@ -205,7 +205,6 @@ def test_parse_conditions(): if len(on_completes) == 0: with pytest.raises(pt.TealInputError) as err_no_oc: pt.Router.parse_conditions( - method_sig, subroutine if is_abi_subroutine else None, on_completes, is_creation, @@ -219,7 +218,6 @@ def test_parse_conditions(): ): with pytest.raises(pt.TealInputError) as err_conflict_conditions: pt.Router.parse_conditions( - method_sig, subroutine if is_abi_subroutine else None, on_completes, is_creation, @@ -233,26 +231,16 @@ def test_parse_conditions(): mutated_on_completes = on_completes + [random.choice(on_completes)] with pytest.raises(pt.TealInputError) as err_dup_oc: pt.Router.parse_conditions( - method_sig, subroutine if is_abi_subroutine else None, mutated_on_completes, is_creation, ) assert "has duplicated on_complete(s)" in str(err_dup_oc) - if is_abi_subroutine: - with pytest.raises(pt.TealInputError) as err_wrong_override: - pt.Router.parse_conditions(None, subroutine, on_completes, is_creation) - assert ( - "A method_signature must be provided if method_to_register is not None" - in str(err_wrong_override) - ) - ( approval_condition_list, clear_state_condition_list, ) = pt.Router.parse_conditions( - method_sig, subroutine if is_abi_subroutine else None, on_completes, is_creation, From e35022044f8330920d382775badf21f683f7f26b Mon Sep 17 00:00:00 2001 From: Hang Su Date: Thu, 19 May 2022 13:57:31 -0400 Subject: [PATCH 141/188] relaxation of bare app call arg num restriction --- pyteal/ast/router.py | 2 +- pyteal/ast/router_test.py | 2 +- pyteal/compiler/compiler_test.py | 7 +++---- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/pyteal/ast/router.py b/pyteal/ast/router.py index 6b08907e2..5009a9cb9 100644 --- a/pyteal/ast/router.py +++ b/pyteal/ast/router.py @@ -228,7 +228,7 @@ def parse_conditions( ) if method_to_register is not None # TODO the default condition for bare call need to be revised - else Txn.application_args.length() == Int(0) + else Int(1) ) # Check if it is a *CREATION* diff --git a/pyteal/ast/router_test.py b/pyteal/ast/router_test.py index 9a428b5c2..5b6d7a76a 100644 --- a/pyteal/ast/router_test.py +++ b/pyteal/ast/router_test.py @@ -272,7 +272,7 @@ def test_parse_conditions(): pt.Txn.application_args.length() == pt.Int(max_subroutine_arg_allowed), ) else: - subroutine_arg_cond = pt.Txn.application_args.length() == pt.Int(0) + subroutine_arg_cond = pt.Int(1) assembled_condition = assemble_helper(subroutine_arg_cond) with pt.TealComponent.Context.ignoreExprEquality(): diff --git a/pyteal/compiler/compiler_test.py b/pyteal/compiler/compiler_test.py index d2320b35f..8016ae467 100644 --- a/pyteal/compiler/compiler_test.py +++ b/pyteal/compiler/compiler_test.py @@ -2625,12 +2625,11 @@ def all_laid_to_args( assert expected_ap == actual_ap_compiled expected_csp = """#pragma version 6 -txn NumAppArgs -pushint 0 // 0 -== +intcblock 1 +intc_0 // 1 bnz main_l2 err main_l2: -pushint 1 // 1 +intc_0 // 1 return""".strip() assert expected_csp == actual_csp_compiled From 0b63f7896f9a871acbf076e8d143de8639d3c81c Mon Sep 17 00:00:00 2001 From: Hang Su Date: Thu, 19 May 2022 15:12:59 -0400 Subject: [PATCH 142/188] add barecall class, api change following --- pyteal/ast/router.py | 48 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/pyteal/ast/router.py b/pyteal/ast/router.py index 5009a9cb9..ab0085930 100644 --- a/pyteal/ast/router.py +++ b/pyteal/ast/router.py @@ -21,7 +21,8 @@ from pyteal.ast.methodsig import MethodSignature from pyteal.ast.naryexpr import And, Or from pyteal.ast.txn import Txn -from pyteal.ast.return_ import Approve +from pyteal.ast.return_ import Approve, Reject, Return +from pyteal.ast.global_ import Global from pyteal.compiler.compiler import compileTeal, DEFAULT_TEAL_VERSION, OptimizeOptions from pyteal.ir.ops import Mode @@ -51,6 +52,43 @@ """ +@dataclass(init=False) +class BareCall: + action: ABIReturnSubroutine | SubroutineFnWrapper | Expr + on_complete: list[EnumInt] + if_creation: bool = False + + def __init__( + self, + bare_app_call: ABIReturnSubroutine | SubroutineFnWrapper | Expr, + on_completes: EnumInt | list[EnumInt], + *, + creation: bool = False, + ): + oc_list: list[EnumInt] = ( + cast(list[EnumInt], on_completes) + if isinstance(on_completes, list) + else [cast(EnumInt, on_completes)] + ) + + self.action = bare_app_call + self.on_complete = oc_list + self.if_creation = creation + + +BareCall.__module__ = "pyteal" + + +DEFAULT_BARE_CALLS: list[BareCall] = [ + BareCall(Approve(), OnComplete.NoOp, creation=True), + BareCall( + Return(Txn.sender() == Global.creator_address()), + [OnComplete.DeleteApplication, OnComplete.UpdateApplication], + ), + BareCall(Reject(), [OnComplete.OptIn, OnComplete.CloseOut, OnComplete.ClearState]), +] + + @dataclass class ProgramNode: """ @@ -145,12 +183,18 @@ class Router: - and a contract JSON object allowing for easily read and call methods in the contract """ - def __init__(self, name: Optional[str] = None) -> None: + def __init__( + self, name: Optional[str] = None, bare_calls: list[BareCall] = None + ) -> None: """ Args: name (optional): the name of the smart contract, used in the JSON object. Default name is `contract` """ + if bare_calls is None: + bare_calls = DEFAULT_BARE_CALLS + # TODO need to change API to allow defining bare_call on init + self.name: str = "Contract" if name is None else name self.approval_if_then: list[ProgramNode] = [] self.clear_state_if_then: list[ProgramNode] = [] From f1988573cb0e87f697395a26f95a3b2b2fe63857 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Fri, 20 May 2022 12:27:32 -0400 Subject: [PATCH 143/188] rename barecall to OnCompleteAction --- pyteal/ast/router.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/pyteal/ast/router.py b/pyteal/ast/router.py index ab0085930..3cfc69503 100644 --- a/pyteal/ast/router.py +++ b/pyteal/ast/router.py @@ -53,7 +53,7 @@ @dataclass(init=False) -class BareCall: +class OnCompleteAction: action: ABIReturnSubroutine | SubroutineFnWrapper | Expr on_complete: list[EnumInt] if_creation: bool = False @@ -76,16 +76,18 @@ def __init__( self.if_creation = creation -BareCall.__module__ = "pyteal" +OnCompleteAction.__module__ = "pyteal" -DEFAULT_BARE_CALLS: list[BareCall] = [ - BareCall(Approve(), OnComplete.NoOp, creation=True), - BareCall( +DEFAULT_BARE_CALLS: list[OnCompleteAction] = [ + OnCompleteAction(Approve(), OnComplete.NoOp, creation=True), + OnCompleteAction( Return(Txn.sender() == Global.creator_address()), [OnComplete.DeleteApplication, OnComplete.UpdateApplication], ), - BareCall(Reject(), [OnComplete.OptIn, OnComplete.CloseOut, OnComplete.ClearState]), + OnCompleteAction( + Reject(), [OnComplete.OptIn, OnComplete.CloseOut, OnComplete.ClearState] + ), ] @@ -184,7 +186,7 @@ class Router: """ def __init__( - self, name: Optional[str] = None, bare_calls: list[BareCall] = None + self, name: Optional[str] = None, bare_calls: list[OnCompleteAction] = None ) -> None: """ Args: From d5f75d320c31d593561c649e0c0c1bb11bc64a52 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Fri, 20 May 2022 12:48:51 -0400 Subject: [PATCH 144/188] remove outdated comments --- pyteal/ast/router.py | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/pyteal/ast/router.py b/pyteal/ast/router.py index 3cfc69503..35392d3a6 100644 --- a/pyteal/ast/router.py +++ b/pyteal/ast/router.py @@ -28,23 +28,6 @@ from pyteal.ir.ops import Mode """ -Notes: -- On a BareApp Call, check - - [x] txn NumAppArgs == 0 - - [x] On-Completion should match (can be a list of On-Completion here) - - [x] Must execute actions required to invoke the method - -- On Method Call, check - - [x] txn ApplicationArgs 0 == method "method-signature" - - [x] On-Completion should match (only one On-Completion specified here?) - - [x] non void method call should log with 0x151f7c75 return-method-specifier - (kinda done in another PR to ABI-Type) - - [x] redirect the method arguments and pass them to handler function - (kinda done, but need to do with extraction and (en/de)-code) - - [x] Must execute actions required to invoke the method - - [x] extract arguments if needed - (decode txn ApplicationArgs 15 if there exists, and extract arguments to feed method) - Notes for OC: - creation conflict with closeout and clear-state - must check: txn ApplicationId == 0 for creation From 0dd7208d84a4e28b8c2552b2561488d9c5210f6e Mon Sep 17 00:00:00 2001 From: Hang Su Date: Fri, 20 May 2022 17:11:04 -0400 Subject: [PATCH 145/188] start changing router api --- pyteal/__init__.pyi | 1 + pyteal/ast/__init__.py | 4 +- pyteal/ast/router.py | 128 +++++++++++++++++++++---------- pyteal/ast/router_test.py | 12 +-- pyteal/compiler/compiler_test.py | 24 +++--- 5 files changed, 109 insertions(+), 60 deletions(-) diff --git a/pyteal/__init__.pyi b/pyteal/__init__.pyi index 0e5d441fb..1f6341c85 100644 --- a/pyteal/__init__.pyi +++ b/pyteal/__init__.pyi @@ -138,6 +138,7 @@ __all__ = [ "Nonce", "Not", "OnComplete", + "OnCompleteActions", "Op", "OpUp", "OpUpMode", diff --git a/pyteal/ast/__init__.py b/pyteal/ast/__init__.py index bed28af74..1fedcc2a9 100644 --- a/pyteal/ast/__init__.py +++ b/pyteal/ast/__init__.py @@ -138,8 +138,7 @@ from pyteal.ast.multi import MultiValue from pyteal.ast.opup import OpUp, OpUpMode from pyteal.ast.ecdsa import EcdsaCurve, EcdsaVerify, EcdsaDecompress, EcdsaRecover - -from pyteal.ast.router import Router +from pyteal.ast.router import Router, OnCompleteActions # abi import pyteal.ast.abi as abi # noqa: I250 @@ -282,6 +281,7 @@ "Break", "Continue", "Router", + "OnCompleteActions", "abi", "EcdsaCurve", "EcdsaVerify", diff --git a/pyteal/ast/router.py b/pyteal/ast/router.py index efa301ba4..ff83b145b 100644 --- a/pyteal/ast/router.py +++ b/pyteal/ast/router.py @@ -1,11 +1,13 @@ from dataclasses import dataclass -from typing import Any, cast, Optional +from typing import Any, Callable, cast, Optional import algosdk.abi as sdk_abi from pyteal.config import METHOD_ARG_NUM_LIMIT from pyteal.errors import TealInputError from pyteal.types import TealType +from pyteal.compiler.compiler import compileTeal, DEFAULT_TEAL_VERSION, OptimizeOptions +from pyteal.ir.ops import Mode from pyteal.ast import abi from pyteal.ast.subroutine import ( @@ -24,8 +26,6 @@ from pyteal.ast.return_ import Approve, Reject, Return from pyteal.ast.global_ import Global -from pyteal.compiler.compiler import compileTeal, DEFAULT_TEAL_VERSION, OptimizeOptions -from pyteal.ir.ops import Mode """ Notes for OC: @@ -35,43 +35,61 @@ """ -@dataclass(init=False) -class OnCompleteAction: - action: ABIReturnSubroutine | SubroutineFnWrapper | Expr - on_complete: list[EnumInt] - if_creation: bool = False +class OnCompleteActions: + def __init__(self): + self.oc_to_action: dict[ + EnumInt, dict[bool, Expr | SubroutineFnWrapper | ABIReturnSubroutine] + ] = dict() - def __init__( + def set_action( self, - bare_app_call: ABIReturnSubroutine | SubroutineFnWrapper | Expr, - on_completes: EnumInt | list[EnumInt], + action: Expr | SubroutineFnWrapper | ABIReturnSubroutine, + on_complete: EnumInt, + create: bool = False, + ) -> "OnCompleteActions": + if on_complete.name not in self.oc_to_action: + self.oc_to_action[on_complete] = dict() + self.oc_to_action[on_complete][create] = action + return self + + @classmethod + def template( + cls, *, - creation: bool = False, - ): - oc_list: list[EnumInt] = ( - cast(list[EnumInt], on_completes) - if isinstance(on_completes, list) - else [cast(EnumInt, on_completes)] + approve_on_noop_create: Optional[bool] = True, + creator_can_update: Optional[bool] = True, + creator_can_delete: Optional[bool] = True, + can_opt_in: Optional[bool] = False, + can_close_out: Optional[bool] = True, + can_clear_state: Optional[bool] = True, + ) -> "OnCompleteActions": + instance = cls() + + if approve_on_noop_create: + instance.set_action(Approve(), OnComplete.NoOp, True) + + allow_creator_expr = Return(Txn.sender() == Global.creator_address()) + instance.set_action( + allow_creator_expr if creator_can_update else Reject(), + OnComplete.UpdateApplication, + ) + instance.set_action( + allow_creator_expr if creator_can_delete else Reject(), + OnComplete.DeleteApplication, ) - self.action = bare_app_call - self.on_complete = oc_list - self.if_creation = creation - + instance.set_action(Approve() if can_opt_in else Reject(), OnComplete.OptIn) + instance.set_action( + Approve() if can_close_out else Reject(), OnComplete.CloseOut + ) + instance.set_action( + Approve() if can_clear_state else Reject(), OnComplete.ClearState + ) -OnCompleteAction.__module__ = "pyteal" + return instance -DEFAULT_BARE_CALLS: list[OnCompleteAction] = [ - OnCompleteAction(Approve(), OnComplete.NoOp, creation=True), - OnCompleteAction( - Return(Txn.sender() == Global.creator_address()), - [OnComplete.DeleteApplication, OnComplete.UpdateApplication], - ), - OnCompleteAction( - Reject(), [OnComplete.OptIn, OnComplete.CloseOut, OnComplete.ClearState] - ), -] +OnCompleteActions.__module__ = "pyteal" @dataclass @@ -169,21 +187,23 @@ class Router: """ def __init__( - self, name: Optional[str] = None, bare_calls: list[OnCompleteAction] = None + self, + name: str, + bare_calls: OnCompleteActions, ) -> None: """ Args: name (optional): the name of the smart contract, used in the JSON object. Default name is `contract` """ - if bare_calls is None: - bare_calls = DEFAULT_BARE_CALLS - # TODO need to change API to allow defining bare_call on init - self.name: str = "Contract" if name is None else name + self.name: str = name self.approval_if_then: list[ProgramNode] = [] self.clear_state_if_then: list[ProgramNode] = [] self.conflict_detect_map: ASTConflictResolver = ASTConflictResolver() + for oc, action_on_creation in bare_calls.oc_to_action.items(): + for on_create, action in action_on_creation.items(): + self.__add_bare_call(action, on_completes=[oc], creation=on_create) @staticmethod def parse_conditions( @@ -453,7 +473,7 @@ def __append_to_ast( ) ) - def add_bare_call( + def __add_bare_call( self, bare_app_call: ABIReturnSubroutine | SubroutineFnWrapper | Expr, on_completes: EnumInt | list[EnumInt], @@ -501,7 +521,7 @@ def add_bare_call( approval_conds, clear_state_conds, branch, ast_order_indicator, None ) - def add_method_handler( + def __add_method_handler( self, method_app_call: ABIReturnSubroutine, *, @@ -538,6 +558,36 @@ def add_method_handler( sdk_abi.Method.from_signature(method_signature), ) + def abi_method( + self, *, on_complete: EnumInt = OnComplete.NoOp, creation: bool = False + ) -> Callable[[Callable | ABIReturnSubroutine], ABIReturnSubroutine]: + """Decorator to register an ABI method call to router. + + Allowing following syntax: + + @router.abi_method(on_complete=OnComplete.OptIn, create=False) + @router.abi_method(on_complete=OnComplete.NoOp, create=False) + def echo(a: pt.abi.Uint64, *, output: pt.abi.Uint64) -> pt.Expr: + ... + + Args: + on_complete: an OnCompletion args + creation: a boolean variable indicating if this condition is triggered on creation + """ + + def __method(impl: Callable | ABIReturnSubroutine) -> ABIReturnSubroutine: + subroutine: ABIReturnSubroutine = ( + impl + if isinstance(impl, ABIReturnSubroutine) + else ABIReturnSubroutine(impl) + ) + self.__add_method_handler( + subroutine, on_complete=on_complete, creation=creation + ) + return subroutine + + return __method + @staticmethod def __ast_construct( ast_list: list[ProgramNode], diff --git a/pyteal/ast/router_test.py b/pyteal/ast/router_test.py index 5b6d7a76a..600bfb8fc 100644 --- a/pyteal/ast/router_test.py +++ b/pyteal/ast/router_test.py @@ -427,12 +427,14 @@ def test_contract_json_obj(): filter(lambda x: isinstance(x, pt.ABIReturnSubroutine), GOOD_SUBROUTINE_CASES) ) contract_name = "contract_name" - router = pt.Router(contract_name) - method_list: list[sdk_abi.contract.Method] = [] + on_complete_actions = pt.OnCompleteActions().set_action( + safe_clear_state_delete, pt.OnComplete.ClearState + ) + router = pt.Router(contract_name, on_complete_actions) + method_list: list[sdk_abi.Method] = [] for subroutine in abi_subroutines: - router.add_method_handler(subroutine) + router.abi_method()(subroutine) method_list.append(sdk_abi.Method.from_signature(subroutine.method_signature())) - router.add_bare_call(safe_clear_state_delete, pt.OnComplete.ClearState) - sdk_contract = sdk_abi.contract.Contract(contract_name, method_list) + sdk_contract = sdk_abi.Contract(contract_name, method_list) contract = router.contract_construct() assert sdk_contract.dictify() == contract diff --git a/pyteal/compiler/compiler_test.py b/pyteal/compiler/compiler_test.py index 8016ae467..35b37650e 100644 --- a/pyteal/compiler/compiler_test.py +++ b/pyteal/compiler/compiler_test.py @@ -2196,37 +2196,33 @@ def access_b4_store(magic_num: pt.abi.Uint64, *, output: pt.abi.Uint64): def test_router_app(): - router = pt.Router() + on_completion_actions = pt.OnCompleteActions().set_action( + pt.Approve(), pt.OnComplete.ClearState + ) - router.add_bare_call(pt.Approve(), pt.OnComplete.ClearState) + router = pt.Router("Contract", on_completion_actions) - @router.add_method_handler - @pt.ABIReturnSubroutine + @router.abi_method() def add(a: pt.abi.Uint64, b: pt.abi.Uint64, *, output: pt.abi.Uint64) -> pt.Expr: return output.set(a.get() + b.get()) - @router.add_method_handler - @pt.ABIReturnSubroutine + @router.abi_method() def sub(a: pt.abi.Uint64, b: pt.abi.Uint64, *, output: pt.abi.Uint64) -> pt.Expr: return output.set(a.get() - b.get()) - @router.add_method_handler - @pt.ABIReturnSubroutine + @router.abi_method() def mul(a: pt.abi.Uint64, b: pt.abi.Uint64, *, output: pt.abi.Uint64) -> pt.Expr: return output.set(a.get() * b.get()) - @router.add_method_handler - @pt.ABIReturnSubroutine + @router.abi_method() def div(a: pt.abi.Uint64, b: pt.abi.Uint64, *, output: pt.abi.Uint64) -> pt.Expr: return output.set(a.get() / b.get()) - @router.add_method_handler - @pt.ABIReturnSubroutine + @router.abi_method() def mod(a: pt.abi.Uint64, b: pt.abi.Uint64, *, output: pt.abi.Uint64) -> pt.Expr: return output.set(a.get() % b.get()) - @router.add_method_handler - @pt.ABIReturnSubroutine + @router.abi_method() def all_laid_to_args( _a: pt.abi.Uint64, _b: pt.abi.Uint64, From 89f137b28534226b42b3fb19d21ff834224270d8 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Mon, 23 May 2022 14:10:41 -0400 Subject: [PATCH 146/188] eliminate contradiction for oc and creation --- pyteal/ast/router.py | 14 +++++++------- pyteal/ast/router_test.py | 30 +++++++++++++++--------------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/pyteal/ast/router.py b/pyteal/ast/router.py index ff83b145b..b70e7a9f7 100644 --- a/pyteal/ast/router.py +++ b/pyteal/ast/router.py @@ -243,18 +243,18 @@ def parse_conditions( raise TealInputError("on complete input should be non-empty list") # Check the existence of OC.CloseOut - close_out_exist = any( - map(lambda x: str(x) == str(OnComplete.CloseOut), on_completes) - ) + # close_out_exist = any( + # map(lambda x: str(x) == str(OnComplete.CloseOut), on_completes) + # ) # Check the existence of OC.ClearState (needed later) clear_state_exist = any( map(lambda x: str(x) == str(OnComplete.ClearState), on_completes) ) # Ill formed report if app create with existence of OC.CloseOut or OC.ClearState - if creation and (close_out_exist or clear_state_exist): - raise TealInputError( - "OnComplete ClearState/CloseOut may be ill-formed with app creation" - ) + # if creation and (close_out_exist or clear_state_exist): + # raise TealInputError( + # "OnComplete ClearState/CloseOut may be ill-formed with app creation" + # ) # Check if anything other than ClearState exists oc_other_than_clear_state_exists = any( map(lambda x: str(x) != str(OnComplete.ClearState), on_completes) diff --git a/pyteal/ast/router_test.py b/pyteal/ast/router_test.py index 600bfb8fc..b12f5fabf 100644 --- a/pyteal/ast/router_test.py +++ b/pyteal/ast/router_test.py @@ -212,21 +212,21 @@ def test_parse_conditions(): assert "on complete input should be non-empty list" in str(err_no_oc) continue - if is_creation and ( - oncomplete_is_in_oc_list(pt.OnComplete.CloseOut, on_completes) - or oncomplete_is_in_oc_list(pt.OnComplete.ClearState, on_completes) - ): - with pytest.raises(pt.TealInputError) as err_conflict_conditions: - pt.Router.parse_conditions( - subroutine if is_abi_subroutine else None, - on_completes, - is_creation, - ) - assert ( - "OnComplete ClearState/CloseOut may be ill-formed with app creation" - in str(err_conflict_conditions) - ) - continue + # if is_creation and ( + # oncomplete_is_in_oc_list(pt.OnComplete.CloseOut, on_completes) + # or oncomplete_is_in_oc_list(pt.OnComplete.ClearState, on_completes) + # ): + # with pytest.raises(pt.TealInputError) as err_conflict_conditions: + # pt.Router.parse_conditions( + # subroutine if is_abi_subroutine else None, + # on_completes, + # is_creation, + # ) + # assert ( + # "OnComplete ClearState/CloseOut may be ill-formed with app creation" + # in str(err_conflict_conditions) + # ) + # continue mutated_on_completes = on_completes + [random.choice(on_completes)] with pytest.raises(pt.TealInputError) as err_dup_oc: From ecd27672bd8de7458359deac39f2145c592b26a0 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Mon, 23 May 2022 14:57:43 -0400 Subject: [PATCH 147/188] remove restriction on cond --- pyteal/ast/router.py | 36 +++++++----------------------- pyteal/ast/router_test.py | 26 +++------------------- pyteal/compiler/compiler_test.py | 38 ++++++-------------------------- 3 files changed, 18 insertions(+), 82 deletions(-) diff --git a/pyteal/ast/router.py b/pyteal/ast/router.py index b70e7a9f7..aaace6531 100644 --- a/pyteal/ast/router.py +++ b/pyteal/ast/router.py @@ -242,45 +242,25 @@ def parse_conditions( if len(on_completes) == 0: raise TealInputError("on complete input should be non-empty list") - # Check the existence of OC.CloseOut - # close_out_exist = any( - # map(lambda x: str(x) == str(OnComplete.CloseOut), on_completes) - # ) # Check the existence of OC.ClearState (needed later) clear_state_exist = any( map(lambda x: str(x) == str(OnComplete.ClearState), on_completes) ) - # Ill formed report if app create with existence of OC.CloseOut or OC.ClearState - # if creation and (close_out_exist or clear_state_exist): - # raise TealInputError( - # "OnComplete ClearState/CloseOut may be ill-formed with app creation" - # ) - # Check if anything other than ClearState exists oc_other_than_clear_state_exists = any( map(lambda x: str(x) != str(OnComplete.ClearState), on_completes) ) # Check: # - if current condition is for *ABI METHOD* - # (method selector && numAppArg == 1 + min(METHOD_APP_ARG_NUM_LIMIT, subroutineSyntaxArgNum)) - # - or *BARE APP CALL* (numAppArg == 0) - method_or_bare_condition = ( - And( - Txn.application_args[0] - == MethodSignature(method_to_register.method_signature()), - Txn.application_args.length() - == Int( - 1 - + min( - method_to_register.subroutine.argument_count(), - METHOD_ARG_NUM_LIMIT, - ) - ), + # - *method selector matches* or *BARE APP CALL* (Int(1)) + method_or_bare_condition: Expr + if method_to_register is not None: + method_or_bare_condition = ( + MethodSignature(method_to_register.method_signature()) + == Txn.application_args[0] ) - if method_to_register is not None - # TODO the default condition for bare call need to be revised - else Int(1) - ) + else: + method_or_bare_condition = Int(1) # Check if it is a *CREATION* approval_conds: list[Expr] = ( diff --git a/pyteal/ast/router_test.py b/pyteal/ast/router_test.py index b12f5fabf..2a99c0675 100644 --- a/pyteal/ast/router_test.py +++ b/pyteal/ast/router_test.py @@ -212,22 +212,6 @@ def test_parse_conditions(): assert "on complete input should be non-empty list" in str(err_no_oc) continue - # if is_creation and ( - # oncomplete_is_in_oc_list(pt.OnComplete.CloseOut, on_completes) - # or oncomplete_is_in_oc_list(pt.OnComplete.ClearState, on_completes) - # ): - # with pytest.raises(pt.TealInputError) as err_conflict_conditions: - # pt.Router.parse_conditions( - # subroutine if is_abi_subroutine else None, - # on_completes, - # is_creation, - # ) - # assert ( - # "OnComplete ClearState/CloseOut may be ill-formed with app creation" - # in str(err_conflict_conditions) - # ) - # continue - mutated_on_completes = on_completes + [random.choice(on_completes)] with pytest.raises(pt.TealInputError) as err_dup_oc: pt.Router.parse_conditions( @@ -263,13 +247,9 @@ def test_parse_conditions(): subroutine_arg_cond: pt.Expr if is_abi_subroutine: - max_subroutine_arg_allowed = 1 + min( - pt.METHOD_ARG_NUM_LIMIT, subroutine.subroutine.argument_count() - ) - subroutine_arg_cond = pt.And( - pt.Txn.application_args[0] - == pt.MethodSignature(typing.cast(str, method_sig)), - pt.Txn.application_args.length() == pt.Int(max_subroutine_arg_allowed), + subroutine_arg_cond = ( + pt.MethodSignature(typing.cast(str, method_sig)) + == pt.Txn.application_args[0] ) else: subroutine_arg_cond = pt.Int(1) diff --git a/pyteal/compiler/compiler_test.py b/pyteal/compiler/compiler_test.py index 35b37650e..dfaea0a39 100644 --- a/pyteal/compiler/compiler_test.py +++ b/pyteal/compiler/compiler_test.py @@ -2267,75 +2267,51 @@ def all_laid_to_args( ) expected_ap = """#pragma version 6 -intcblock 0 1 3 +intcblock 0 1 bytecblock 0x151f7c75 -txna ApplicationArgs 0 pushbytes 0xfe6bdf69 // "add(uint64,uint64)uint64" +txna ApplicationArgs 0 == -txn NumAppArgs -intc_2 // 3 -== -&& txn OnCompletion intc_0 // NoOp == && bnz main_l12 -txna ApplicationArgs 0 pushbytes 0x78b488b7 // "sub(uint64,uint64)uint64" +txna ApplicationArgs 0 == -txn NumAppArgs -intc_2 // 3 -== -&& txn OnCompletion intc_0 // NoOp == && bnz main_l11 -txna ApplicationArgs 0 pushbytes 0xe2f188c5 // "mul(uint64,uint64)uint64" +txna ApplicationArgs 0 == -txn NumAppArgs -intc_2 // 3 -== -&& txn OnCompletion intc_0 // NoOp == && bnz main_l10 -txna ApplicationArgs 0 pushbytes 0x16e80f08 // "div(uint64,uint64)uint64" +txna ApplicationArgs 0 == -txn NumAppArgs -intc_2 // 3 -== -&& txn OnCompletion intc_0 // NoOp == && bnz main_l9 -txna ApplicationArgs 0 pushbytes 0x4dfc58ae // "mod(uint64,uint64)uint64" +txna ApplicationArgs 0 == -txn NumAppArgs -intc_2 // 3 -== -&& txn OnCompletion intc_0 // NoOp == && bnz main_l8 -txna ApplicationArgs 0 pushbytes 0x487ce2fd // "all_laid_to_args(uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64)uint64" +txna ApplicationArgs 0 == -txn NumAppArgs -pushint 16 // 16 -== -&& txn OnCompletion intc_0 // NoOp == From 4e8efe2bd641c77d025f53d89a03c799d8d11197 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Tue, 24 May 2022 12:06:16 -0400 Subject: [PATCH 148/188] new interface: add bare-calls on init, register method-call more fine-grained --- pyteal/__init__.pyi | 5 +- pyteal/ast/__init__.py | 13 +- pyteal/ast/router.py | 649 ++++++++++++------------------- pyteal/ast/router_test.py | 257 +++++++----- pyteal/ast/subroutine.py | 6 +- pyteal/compiler/compiler_test.py | 42 +- 6 files changed, 468 insertions(+), 504 deletions(-) diff --git a/pyteal/__init__.pyi b/pyteal/__init__.pyi index 1f6341c85..da7d57c5c 100644 --- a/pyteal/__init__.pyi +++ b/pyteal/__init__.pyi @@ -69,6 +69,8 @@ __all__ = [ "BytesSqrt", "BytesXor", "BytesZero", + "CallConfig", + "CallConfigs", "CompileOptions", "Concat", "Cond", @@ -137,8 +139,9 @@ __all__ = [ "Neq", "Nonce", "Not", + "OCAction", + "OCActions", "OnComplete", - "OnCompleteActions", "Op", "OpUp", "OpUpMode", diff --git a/pyteal/ast/__init__.py b/pyteal/ast/__init__.py index 1fedcc2a9..56e31103b 100644 --- a/pyteal/ast/__init__.py +++ b/pyteal/ast/__init__.py @@ -138,7 +138,13 @@ from pyteal.ast.multi import MultiValue from pyteal.ast.opup import OpUp, OpUpMode from pyteal.ast.ecdsa import EcdsaCurve, EcdsaVerify, EcdsaDecompress, EcdsaRecover -from pyteal.ast.router import Router, OnCompleteActions +from pyteal.ast.router import ( + Router, + CallConfig, + CallConfigs, + OCAction, + OCActions, +) # abi import pyteal.ast.abi as abi # noqa: I250 @@ -281,7 +287,10 @@ "Break", "Continue", "Router", - "OnCompleteActions", + "CallConfig", + "CallConfigs", + "OCAction", + "OCActions", "abi", "EcdsaCurve", "EcdsaVerify", diff --git a/pyteal/ast/router.py b/pyteal/ast/router.py index aaace6531..d9c080611 100644 --- a/pyteal/ast/router.py +++ b/pyteal/ast/router.py @@ -1,5 +1,6 @@ -from dataclasses import dataclass -from typing import Any, Callable, cast, Optional +from dataclasses import dataclass, field +from typing import Any, cast, Optional +from enum import Enum import algosdk.abi as sdk_abi @@ -23,160 +24,167 @@ from pyteal.ast.methodsig import MethodSignature from pyteal.ast.naryexpr import And, Or from pyteal.ast.txn import Txn -from pyteal.ast.return_ import Approve, Reject, Return -from pyteal.ast.global_ import Global +from pyteal.ast.return_ import Approve -""" -Notes for OC: -- creation conflict with closeout and clear-state -- must check: txn ApplicationId == 0 for creation -- clear-state AST build should be separated with other OC AST build -""" +class CallConfig(Enum): + NEVER = 0 + CALL = 1 + CREATE = 2 + ALL = 3 + def __or__(self, other: object) -> "CallConfig": + if not isinstance(other, CallConfig): + raise TealInputError("OCMethodConfig must be compared with same class") + return CallConfig(self.value | other.value) -class OnCompleteActions: - def __init__(self): - self.oc_to_action: dict[ - EnumInt, dict[bool, Expr | SubroutineFnWrapper | ABIReturnSubroutine] - ] = dict() + def __and__(self, other: object) -> "CallConfig": + if not isinstance(other, CallConfig): + raise TealInputError("OCMethodConfig must be compared with same class") + return CallConfig(self.value & other.value) - def set_action( - self, - action: Expr | SubroutineFnWrapper | ABIReturnSubroutine, - on_complete: EnumInt, - create: bool = False, - ) -> "OnCompleteActions": - if on_complete.name not in self.oc_to_action: - self.oc_to_action[on_complete] = dict() - self.oc_to_action[on_complete][create] = action - return self - - @classmethod - def template( - cls, - *, - approve_on_noop_create: Optional[bool] = True, - creator_can_update: Optional[bool] = True, - creator_can_delete: Optional[bool] = True, - can_opt_in: Optional[bool] = False, - can_close_out: Optional[bool] = True, - can_clear_state: Optional[bool] = True, - ) -> "OnCompleteActions": - instance = cls() - - if approve_on_noop_create: - instance.set_action(Approve(), OnComplete.NoOp, True) - - allow_creator_expr = Return(Txn.sender() == Global.creator_address()) - instance.set_action( - allow_creator_expr if creator_can_update else Reject(), - OnComplete.UpdateApplication, - ) - instance.set_action( - allow_creator_expr if creator_can_delete else Reject(), - OnComplete.DeleteApplication, - ) + def __eq__(self, other: object) -> bool: + if not isinstance(other, CallConfig): + raise TealInputError("OCMethodConfig must be compared with same class") + return self.value == other.value - instance.set_action(Approve() if can_opt_in else Reject(), OnComplete.OptIn) - instance.set_action( - Approve() if can_close_out else Reject(), OnComplete.CloseOut - ) - instance.set_action( - Approve() if can_clear_state else Reject(), OnComplete.ClearState - ) - return instance +CallConfig.__module__ = "pyteal" -OnCompleteActions.__module__ = "pyteal" +@dataclass(frozen=True) +class CallConfigs: + no_op: CallConfig = field(kw_only=True, default=CallConfig.CALL) + opt_in: CallConfig = field(kw_only=True, default=CallConfig.NEVER) + close_out: CallConfig = field(kw_only=True, default=CallConfig.NEVER) + clear_state: CallConfig = field(kw_only=True, default=CallConfig.NEVER) + update_application: CallConfig = field(kw_only=True, default=CallConfig.NEVER) + delete_application: CallConfig = field(kw_only=True, default=CallConfig.NEVER) + def is_never(self) -> bool: + return ( + self.no_op == CallConfig.NEVER + and self.opt_in == CallConfig.NEVER + and self.close_out == CallConfig.NEVER + and self.clear_state == CallConfig.NEVER + and self.update_application == CallConfig.NEVER + and self.delete_application == CallConfig.NEVER + ) -@dataclass -class ProgramNode: - """ - This class contains a condition branch in program AST, with - - `condition`: logical condition of entering such AST branch - - `branch`: steps to execute the branch after entering - - `method_info` (optional): only needed in approval program node, constructed from - - SDK's method - - ABIReturnSubroutine's method signature - """ + def oc_under_call_config(self, call_config: CallConfig) -> list[EnumInt]: + if not isinstance(call_config, CallConfig): + raise TealInputError( + "generate condition based on OCMethodCallConfigs should be based on OCMethodConfig" + ) + config_oc_pairs: list[tuple[CallConfig, EnumInt]] = [ + (self.no_op, OnComplete.NoOp), + (self.opt_in, OnComplete.OptIn), + (self.close_out, OnComplete.CloseOut), + (self.clear_state, OnComplete.ClearState), + (self.update_application, OnComplete.UpdateApplication), + (self.delete_application, OnComplete.DeleteApplication), + ] + return [ + oc + for oc_config, oc in config_oc_pairs + if (oc_config & call_config) != CallConfig.NEVER + ] - condition: Expr - branch: Expr - method_info: Optional[sdk_abi.Method] - ast_order_indicator: "ConflictMapElem" +@dataclass(frozen=True) +class OCAction: + on_create: Optional[Expr | SubroutineFnWrapper | ABIReturnSubroutine] = field( + kw_only=True, default=None + ) + on_call: Optional[Expr | SubroutineFnWrapper | ABIReturnSubroutine] = field( + kw_only=True, default=None + ) -ProgramNode.__module__ = "pyteal" + @staticmethod + def never() -> "OCAction": + return OCAction() + @staticmethod + def create_only( + f: Expr | SubroutineFnWrapper | ABIReturnSubroutine, + ) -> "OCAction": + return OCAction(on_create=f) -@dataclass -class ConflictMapElem: - is_method_call: bool - method_name: str - on_creation: bool - - def __lt__(self, other: "ConflictMapElem"): - # compare under same oc condition - # can be used to order AST - if not isinstance(other, ConflictMapElem): - raise TealInputError( - "ConflictMapElem can only check conflict with other ConflictMapElem" - ) + @staticmethod + def call_only( + f: Expr | SubroutineFnWrapper | ABIReturnSubroutine, + ) -> "OCAction": + return OCAction(on_call=f) + + @staticmethod + def always( + f: Expr | SubroutineFnWrapper | ABIReturnSubroutine, + ) -> "OCAction": + return OCAction(on_create=f, on_call=f) + + +OCAction.__module__ = "pyteal" + + +@dataclass(frozen=True) +class OCActions: + close_out: OCAction = field(kw_only=True, default=OCAction.never()) + clear_state: OCAction = field(kw_only=True, default=OCAction.never()) + delete_application: OCAction = field(kw_only=True, default=OCAction.never()) + no_op: OCAction = field(kw_only=True, default=OCAction.never()) + opt_in: OCAction = field(kw_only=True, default=OCAction.never()) + update_application: OCAction = field(kw_only=True, default=OCAction.never()) + + def dictify(self) -> dict[EnumInt, OCAction]: + return { + OnComplete.CloseOut: self.close_out, + OnComplete.ClearState: self.clear_state, + OnComplete.DeleteApplication: self.delete_application, + OnComplete.NoOp: self.no_op, + OnComplete.OptIn: self.opt_in, + OnComplete.UpdateApplication: self.update_application, + } - if self.is_method_call: - if not other.is_method_call: + def is_empty(self) -> bool: + for oc_action in self.dictify().values(): + if oc_action.on_call is not None or oc_action.on_create is not None: return False - else: - if other.is_method_call: - return True + return True - # either both is_method_call or not - # compare on_creation - if self.on_creation: - return not other.on_creation - else: - return other.on_creation - def has_conflict_with(self, other: "ConflictMapElem"): - if not isinstance(other, ConflictMapElem): - raise TealInputError( - "ConflictMapElem can only check conflict with other ConflictMapElem" - ) - if not self.is_method_call and not other.is_method_call: - if self.method_name == other.method_name: - raise TealInputError(f"re-registering {self.method_name} under same OC") - else: - raise TealInputError( - f"re-registering {self.method_name} and {other.method_name} under same OC" - ) +OCActions.__module__ = "pyteal" -ConflictMapElem.__module__ = "pyteal" +@dataclass(frozen=True) +class CondNode: + condition: Expr + branch: Expr -class ASTConflictResolver: - def __init__(self): - self.conflict_detect_map: dict[str, list[ConflictMapElem]] = { - name: list() for name in dir(OnComplete) if not name.startswith("__") - } +CondNode.__module__ = "pyteal" - def add_elem_to(self, oc: str, conflict_map_elem: ConflictMapElem): - if oc not in self.conflict_detect_map: - raise TealInputError( - f"{oc} is not one of the element in conflict map, should be one of the OnCompletes" - ) - elems_under_oc: list[ConflictMapElem] = self.conflict_detect_map[oc] - for elem in elems_under_oc: - if elem.has_conflict_with(conflict_map_elem): - raise TealInputError(f"{elem} has conflict with {conflict_map_elem}") - self.conflict_detect_map[oc].append(conflict_map_elem) +@dataclass +class CategorizedCondNodes: + method_calls_create: list[CondNode] = field(default_factory=list) + bare_calls_create: list[CondNode] = field(default_factory=list) + method_calls: list[CondNode] = field(default_factory=list) + bare_calls: list[CondNode] = field(default_factory=list) + + def program_construction(self) -> Expr: + concatenated_ast = ( + self.method_calls_create + + self.bare_calls_create + + self.method_calls + + self.bare_calls + ) + if len(concatenated_ast) == 0: + raise TealInputError("ABIRouter: Cannot build program with an empty AST") + program: Cond = Cond(*[[n.condition, n.branch] for n in concatenated_ast]) + return program -ASTConflictResolver.__module__ = "pyteal" +CategorizedCondNodes.__module__ = "pyteal" class Router: @@ -189,107 +197,20 @@ class Router: def __init__( self, name: str, - bare_calls: OnCompleteActions, + bare_calls: OCActions, ) -> None: """ Args: - name (optional): the name of the smart contract, used in the JSON object. - Default name is `contract` + name: the name of the smart contract, used in the JSON object. + bare_calls: the bare app call registered for each on_completion. """ self.name: str = name - self.approval_if_then: list[ProgramNode] = [] - self.clear_state_if_then: list[ProgramNode] = [] - self.conflict_detect_map: ASTConflictResolver = ASTConflictResolver() - for oc, action_on_creation in bare_calls.oc_to_action.items(): - for on_create, action in action_on_creation.items(): - self.__add_bare_call(action, on_completes=[oc], creation=on_create) + self.categorized_approval_ast = CategorizedCondNodes() + self.categorized_clear_state_ast = CategorizedCondNodes() + self.added_method_sig: set[str] = set() - @staticmethod - def parse_conditions( - method_to_register: Optional[ABIReturnSubroutine], - on_completes: list[EnumInt], - creation: bool, - ) -> tuple[list[Expr], list[Expr]]: - """This is a helper function in inferring valid approval/clear-state program condition. - - It starts with some initialization check to resolve some conflict: - - `creation` option is contradicting with OnCompletion.CloseOut and OnCompletion.ClearState - - if there is `method_to_register` existing, then `method_signature` should appear - - Then this function appends conditions to approval/clear-state program condition: - - if `creation` is true, then append `Txn.application_id() == 0` to approval conditions - - if it is handling abi-method, then - `Txn.application_arg[0] == hash(method_signature) && - Txn.application_arg_num == 1 + min(METHOD_ARG_NUM_LIMIT, method's arg num)` - where `METHOD_ARG_NUM_LIMIT == 15`. - # TODO wonder if we need to care about arg number, if the arg number is not enough/valid - # we just directly fail - - if it is handling conditions for other cases, then `Int(1)` automatically approve - - Args: - method_to_register: an ABIReturnSubroutine if exists, or None - on_completes: a list of OnCompletion args - creation: a boolean variable indicating if this condition is triggered on creation - Returns: - approval_conds: A list of exprs for approval program's condition on: creation?, method/bare, Or[OCs] - clear_state_conds: A list of exprs for clear-state program's condition on: method/bare - """ - - # check that the onComplete has no duplicates - if len(on_completes) != len(set(on_completes)): - raise TealInputError(f"input {on_completes} has duplicated on_complete(s)") - if len(on_completes) == 0: - raise TealInputError("on complete input should be non-empty list") - - # Check the existence of OC.ClearState (needed later) - clear_state_exist = any( - map(lambda x: str(x) == str(OnComplete.ClearState), on_completes) - ) - oc_other_than_clear_state_exists = any( - map(lambda x: str(x) != str(OnComplete.ClearState), on_completes) - ) - - # Check: - # - if current condition is for *ABI METHOD* - # - *method selector matches* or *BARE APP CALL* (Int(1)) - method_or_bare_condition: Expr - if method_to_register is not None: - method_or_bare_condition = ( - MethodSignature(method_to_register.method_signature()) - == Txn.application_args[0] - ) - else: - method_or_bare_condition = Int(1) - - # Check if it is a *CREATION* - approval_conds: list[Expr] = ( - [Txn.application_id() == Int(0)] if creation else [] - ) - clear_state_conds: list[Expr] = [] - - if oc_other_than_clear_state_exists: - approval_conds.append(method_or_bare_condition) - - # if OC.ClearState exists, add method-or-bare-condition since it is only needed in ClearStateProgram - if clear_state_exist: - clear_state_conds.append(method_or_bare_condition) - - # Check onComplete conditions for approval_conds, filter out *ClearState* - approval_oc_conds: list[Expr] = [ - Txn.on_completion() == oc - for oc in on_completes - if str(oc) != str(OnComplete.ClearState) - ] - - # if approval OC condition is not empty, append Or to approval_conds - if len(approval_oc_conds) > 0: - approval_conds.append(Or(*approval_oc_conds)) - - # what we have here is: - # list of conds for approval program on one branch: creation?, method/bare, Or[OCs] - # list of conds for clearState program on one branch: method/bare - return approval_conds, clear_state_conds + self.__add_bare_call(bare_calls) @staticmethod def wrap_handler( @@ -413,186 +334,126 @@ def wrap_handler( Approve(), ) - def __append_to_ast( - self, - approval_conditions: list[Expr], - clear_state_conditions: list[Expr], - branch: Expr, - ast_order_indicator: ConflictMapElem, - method_obj: Optional[sdk_abi.Method] = None, - ) -> None: - """ - A helper function that appends conditions and execution of branches into AST. + def __add_bare_call(self, oc_actions: OCActions) -> None: + if oc_actions.is_empty(): + raise TealInputError("the OnCompleteActions is empty.") + bare_app_calls: dict[EnumInt, OCAction] = oc_actions.dictify() - Args: - approval_conditions: A list of expressions for approval program's condition on: creation?, method/bare, Or[OCs] - clear_state_conditions: A list of expressions for clear-state program's condition on: method/bare - branch: A branch of contract executing the registered method - method_obj: SDK's Method objects to construct Contract JSON object - """ - if len(approval_conditions) > 0: - self.approval_if_then.append( - ProgramNode( - And(*approval_conditions) - if len(approval_conditions) > 1 - else approval_conditions[0], - branch, - method_obj, - ast_order_indicator, - ) + cs_calls = bare_app_calls[OnComplete.ClearState] + if cs_calls.on_call is not None: + on_call = cast( + Expr | SubroutineFnWrapper | ABIReturnSubroutine, + cs_calls.on_call, ) - if len(clear_state_conditions) > 0: - self.clear_state_if_then.append( - ProgramNode( - And(*clear_state_conditions) - if len(clear_state_conditions) > 1 - else clear_state_conditions[0], - branch, - method_obj, - ast_order_indicator, - ) + wrapped = Router.wrap_handler(False, on_call) + self.categorized_clear_state_ast.bare_calls.append( + CondNode(Int(1), wrapped) + ) + if cs_calls.on_create is not None: + on_create = cast( + Expr | SubroutineFnWrapper | ABIReturnSubroutine, + cs_calls.on_create, + ) + wrapped = Router.wrap_handler(False, on_create) + self.categorized_clear_state_ast.bare_calls_create.append( + CondNode(Txn.application_id() == Int(0), wrapped) ) - def __add_bare_call( - self, - bare_app_call: ABIReturnSubroutine | SubroutineFnWrapper | Expr, - on_completes: EnumInt | list[EnumInt], - *, - creation: bool = False, - ) -> None: - """ - Registering a bare-app-call to the router. + approval_calls = { + oc: oc_action + for oc, oc_action in bare_app_calls.items() + if str(oc) != str(OnComplete.ClearState) + } - Args: - bare_app_call: either an `ABIReturnSubroutine`, or `SubroutineFnWrapper`, or `Expr`. - must take no arguments and evaluate to none (void). - on_completes: a list of OnCompletion args - creation: a boolean variable indicating if this condition is triggered on creation - """ - oc_list: list[EnumInt] = ( - cast(list[EnumInt], on_completes) - if isinstance(on_completes, list) - else [cast(EnumInt, on_completes)] - ) - approval_conds, clear_state_conds = Router.parse_conditions( - method_to_register=None, - on_completes=oc_list, - creation=creation, - ) - branch = Router.wrap_handler(False, bare_app_call) - method_name: str - match bare_app_call: - case ABIReturnSubroutine(): - method_name = bare_app_call.method_signature() - case SubroutineFnWrapper(): - method_name = bare_app_call.name() - case Expr(): - method_name = str(bare_app_call) - case _: - raise TealInputError( - f"bare app call can only be one of three following cases: " - f"{ABIReturnSubroutine, SubroutineFnWrapper, Expr}" + for oc, approval_bac in approval_calls.items(): + if approval_bac.on_call: + on_call = cast( + Expr | SubroutineFnWrapper | ABIReturnSubroutine, + approval_bac.on_call, + ) + wrapped = Router.wrap_handler(False, on_call) + self.categorized_approval_ast.bare_calls.append( + CondNode(Txn.on_completion() == oc, wrapped) + ) + if approval_bac.on_create: + on_create = cast( + Expr | SubroutineFnWrapper | ABIReturnSubroutine, + approval_bac.on_create, + ) + wrapped = Router.wrap_handler(False, on_create) + self.categorized_approval_ast.bare_calls_create.append( + CondNode( + And(Txn.application_id() == Int(0), Txn.on_completion() == oc), + wrapped, + ) ) - ast_order_indicator = ConflictMapElem(False, method_name, creation) - for oc in oc_list: - self.conflict_detect_map.add_elem_to(oc.name, ast_order_indicator) - self.__append_to_ast( - approval_conds, clear_state_conds, branch, ast_order_indicator, None - ) - - def __add_method_handler( + def add_method_handler( self, - method_app_call: ABIReturnSubroutine, - *, - on_complete: EnumInt = OnComplete.NoOp, - creation: bool = False, + method_call: ABIReturnSubroutine, + method_overload_name: str = None, + call_configs: CallConfigs = CallConfigs(), ) -> None: - """ - Registering an ABI method call to the router. - - Args: - method_app_call: an `ABIReturnSubroutine` that is registrable - on_complete: an OnCompletion args - creation: a boolean variable indicating if this condition is triggered on creation - """ - oc_list: list[EnumInt] = [on_complete] - method_signature = method_app_call.method_signature() - - approval_conds, clear_state_conds = Router.parse_conditions( - method_to_register=method_app_call, - on_completes=oc_list, - creation=creation, - ) - branch = Router.wrap_handler(True, method_app_call) - ast_order_indicator = ConflictMapElem( - True, method_app_call.method_signature(), creation - ) - for oc in oc_list: - self.conflict_detect_map.add_elem_to(oc.name, ast_order_indicator) - self.__append_to_ast( - approval_conds, - clear_state_conds, - branch, - ast_order_indicator, - sdk_abi.Method.from_signature(method_signature), - ) - - def abi_method( - self, *, on_complete: EnumInt = OnComplete.NoOp, creation: bool = False - ) -> Callable[[Callable | ABIReturnSubroutine], ABIReturnSubroutine]: - """Decorator to register an ABI method call to router. - - Allowing following syntax: - - @router.abi_method(on_complete=OnComplete.OptIn, create=False) - @router.abi_method(on_complete=OnComplete.NoOp, create=False) - def echo(a: pt.abi.Uint64, *, output: pt.abi.Uint64) -> pt.Expr: - ... - - Args: - on_complete: an OnCompletion args - creation: a boolean variable indicating if this condition is triggered on creation - """ - - def __method(impl: Callable | ABIReturnSubroutine) -> ABIReturnSubroutine: - subroutine: ABIReturnSubroutine = ( - impl - if isinstance(impl, ABIReturnSubroutine) - else ABIReturnSubroutine(impl) + if not isinstance(method_call, ABIReturnSubroutine): + raise TealInputError( + "for adding method handler, must be ABIReturnSubroutine" ) - self.__add_method_handler( - subroutine, on_complete=on_complete, creation=creation + method_signature = method_call.method_signature(method_overload_name) + if call_configs.is_never(): + raise TealInputError( + f"registered method {method_signature} is never executed" + ) + oc_create: list[EnumInt] = call_configs.oc_under_call_config(CallConfig.CREATE) + oc_call: list[EnumInt] = call_configs.oc_under_call_config(CallConfig.CALL) + if method_signature in self.added_method_sig: + raise TealInputError(f"re-registering method {method_signature} detected") + self.added_method_sig.add(method_signature) + + wrapped = Router.wrap_handler(True, method_call) + + if any(str(OnComplete.ClearState) == str(x) for x in oc_create): + self.categorized_clear_state_ast.method_calls_create.append( + CondNode( + And( + Txn.application_id() == Int(0), + Txn.application_args[0] == MethodSignature(method_signature), + ), + wrapped, + ) + ) + oc_create = [ + oc for oc in oc_create if str(oc) != str(OnComplete.ClearState) + ] + if any(str(OnComplete.ClearState) == str(x) for x in oc_call): + self.categorized_clear_state_ast.method_calls.append( + CondNode( + Txn.application_args[0] == MethodSignature(method_signature), + wrapped, + ) + ) + oc_call = [oc for oc in oc_call if str(oc) != str(OnComplete.ClearState)] + + if oc_create: + self.categorized_approval_ast.method_calls_create.append( + CondNode( + And( + Txn.application_id() == Int(0), + Txn.application_args[0] == MethodSignature(method_signature), + Or(*[Txn.on_completion() == oc for oc in oc_create]), + ), + wrapped, + ) + ) + if oc_call: + self.categorized_approval_ast.method_calls.append( + CondNode( + And( + Txn.application_args[0] == MethodSignature(method_signature), + Or(*[Txn.on_completion() == oc for oc in oc_call]), + ), + wrapped, + ) ) - return subroutine - - return __method - - @staticmethod - def __ast_construct( - ast_list: list[ProgramNode], - ) -> Expr: - """A helper function in constructing approval/clear-state programs. - - It takes a list of `ProgramNode`s, which contains conditions of entering a condition branch - and the execution of the branch. - - It constructs the program's AST from the list of `ProgramNode`. - - Args: - ast_list: a non-empty list of `ProgramNode`'s containing conditions of entering such branch - and execution of the branch. - Returns: - program: the Cond AST of (approval/clear-state) program from the list of `ProgramNode`. - """ - if len(ast_list) == 0: - raise TealInputError("ABIRouter: Cannot build program with an empty AST") - - sorted(ast_list, key=lambda x: x.ast_order_indicator) - - program: Cond = Cond(*[[node.condition, node.branch] for node in ast_list]) - - return program def contract_construct(self) -> dict[str, Any]: """A helper function in constructing contract JSON object. @@ -605,7 +466,7 @@ def contract_construct(self) -> dict[str, Any]: approval program's method signatures and `self.name`. """ method_collections = [ - node.method_info for node in self.approval_if_then if node.method_info + sdk_abi.Method.from_signature(sig) for sig in self.added_method_sig ] return sdk_abi.Contract(self.name, method_collections).dictify() @@ -620,8 +481,8 @@ def build_program(self) -> tuple[Expr, Expr, dict[str, Any]]: contract: JSON object of contract to allow client start off-chain call """ return ( - Router.__ast_construct(self.approval_if_then), - Router.__ast_construct(self.clear_state_if_then), + self.categorized_approval_ast.program_construction(), + self.categorized_clear_state_ast.program_construction(), self.contract_construct(), ) diff --git a/pyteal/ast/router_test.py b/pyteal/ast/router_test.py index 2a99c0675..a3b348712 100644 --- a/pyteal/ast/router_test.py +++ b/pyteal/ast/router_test.py @@ -1,7 +1,8 @@ import pyteal as pt import itertools import pytest -import random + +# import random import typing import algosdk.abi as sdk_abi @@ -176,12 +177,38 @@ def not_registrable(lhs: pt.abi.Uint64, rhs: pt.Expr, *, output: pt.abi.Uint64): ] -def power_set(no_dup_list: list): - masks = [1 << i for i in range(len(no_dup_list))] +CALL_CONFIGS = [ + pt.CallConfig.NEVER, + pt.CallConfig.CALL, + pt.CallConfig.CREATE, + pt.CallConfig.ALL, +] + + +def power_set(no_dup_list: list, length_override: int = None): + if length_override is None: + length_override = len(no_dup_list) + assert 1 <= length_override <= len(no_dup_list) + masks = [1 << i for i in range(length_override)] for i in range(1 << len(no_dup_list)): yield [elem for mask, elem in zip(masks, no_dup_list) if i & mask] +def full_perm_gen(non_dup_list: list, perm_length: int): + if perm_length < 0: + raise + elif perm_length == 0: + yield [] + return + for index in range(len(non_dup_list) ** perm_length): + index_list_basis = [] + temp = index + for i in range(perm_length): + index_list_basis.append(non_dup_list[temp % len(non_dup_list)]) + temp //= len(non_dup_list) + yield index_list_basis + + def oncomplete_is_in_oc_list(sth: pt.EnumInt, oc_list: list[pt.EnumInt]): return any(map(lambda x: str(x) == str(sth), oc_list)) @@ -193,94 +220,140 @@ def assemble_helper(what: pt.Expr) -> pt.TealBlock: return assembled -def test_parse_conditions(): - ON_COMPLETE_COMBINED_CASES = power_set(ON_COMPLETE_CASES) +def camel_to_snake(name: str) -> str: + return "".join(["_" + c.lower() if c.isupper() else c for c in name]).lstrip("_") - for subroutine, on_completes, is_creation in itertools.product( - GOOD_SUBROUTINE_CASES, ON_COMPLETE_COMBINED_CASES, [False, True] - ): - is_abi_subroutine = isinstance(subroutine, pt.ABIReturnSubroutine) - method_sig = subroutine.method_signature() if is_abi_subroutine else None - - if len(on_completes) == 0: - with pytest.raises(pt.TealInputError) as err_no_oc: - pt.Router.parse_conditions( - subroutine if is_abi_subroutine else None, - on_completes, - is_creation, - ) - assert "on complete input should be non-empty list" in str(err_no_oc) - continue - - mutated_on_completes = on_completes + [random.choice(on_completes)] - with pytest.raises(pt.TealInputError) as err_dup_oc: - pt.Router.parse_conditions( - subroutine if is_abi_subroutine else None, - mutated_on_completes, - is_creation, - ) - assert "has duplicated on_complete(s)" in str(err_dup_oc) - ( - approval_condition_list, - clear_state_condition_list, - ) = pt.Router.parse_conditions( - subroutine if is_abi_subroutine else None, - on_completes, - is_creation, - ) +def test_add_bare_call(): + pass - if not oncomplete_is_in_oc_list(pt.OnComplete.ClearState, on_completes): - assert len(clear_state_condition_list) == 0 - assembled_ap_condition_list: list[pt.TealBlock] = [ - assemble_helper(expr) for expr in approval_condition_list - ] - assembled_csp_condition_list: list[pt.TealBlock] = [ - assemble_helper(expr) for expr in clear_state_condition_list - ] - if is_creation: - creation_condition: pt.Expr = pt.Txn.application_id() == pt.Int(0) - assembled_condition = assemble_helper(creation_condition) - with pt.TealComponent.Context.ignoreExprEquality(): - assert assembled_condition in assembled_ap_condition_list - - subroutine_arg_cond: pt.Expr - if is_abi_subroutine: - subroutine_arg_cond = ( - pt.MethodSignature(typing.cast(str, method_sig)) - == pt.Txn.application_args[0] - ) - else: - subroutine_arg_cond = pt.Int(1) - - assembled_condition = assemble_helper(subroutine_arg_cond) - with pt.TealComponent.Context.ignoreExprEquality(): - if not ( - len(on_completes) == 1 - and oncomplete_is_in_oc_list(pt.OnComplete.ClearState, on_completes) - ): - assert assembled_condition in assembled_ap_condition_list - - if oncomplete_is_in_oc_list(pt.OnComplete.ClearState, on_completes): - with pt.TealComponent.Context.ignoreExprEquality(): - assert assembled_condition in assembled_csp_condition_list - - if len(on_completes) == 1 and oncomplete_is_in_oc_list( - pt.OnComplete.ClearState, on_completes - ): - continue - - on_completes_cond: pt.Expr = pt.Or( - *[ - pt.Txn.on_completion() == oc - for oc in on_completes - if str(oc) != str(pt.OnComplete.ClearState) - ] +def test_add_method(): + abi_subroutine_cases = [ + abi_ret + for abi_ret in GOOD_SUBROUTINE_CASES + if isinstance(abi_ret, pt.ABIReturnSubroutine) + ] + normal_subroutine = [ + subroutine + for subroutine in GOOD_SUBROUTINE_CASES + if not isinstance(subroutine, pt.ABIReturnSubroutine) + ] + router = pt.Router( + "routerForMethodTest", + pt.OCActions(clear_state=pt.OCAction.call_only(pt.Approve())), + ) + for subroutine in normal_subroutine: + with pytest.raises(pt.TealInputError) as must_be_abi: + router.add_method_handler(subroutine) + assert "for adding method handler, must be ABIReturnSubroutine" in str( + must_be_abi ) - on_completes_cond_assembled = assemble_helper(on_completes_cond) - with pt.TealComponent.Context.ignoreExprEquality(): - assert on_completes_cond_assembled in assembled_ap_condition_list + on_complete_pow_set = power_set(ON_COMPLETE_CASES) + for handler, on_complete_set in itertools.product( + abi_subroutine_cases, on_complete_pow_set + ): + full_perm_call_configs_for_ocs = full_perm_gen( + CALL_CONFIGS, len(on_complete_set) + ) + oc_names = [camel_to_snake(oc.name) for oc in on_complete_set] + for call_config in full_perm_call_configs_for_ocs: + method_call_configs: pt.CallConfigs = pt.CallConfigs( + **dict(zip(oc_names, call_config)) + ) + if method_call_configs.is_never(): + with pytest.raises(pt.TealInputError) as call_config_never: + router.add_method_handler(handler, None, method_call_configs) + assert "is never executed" in str(call_config_never) + continue + + # router.add_method_handler(handler, None, method_call_configs) + # on_create = method_call_configs.oc_under_call_config(pt.CallConfig.CREATE) + # on_call = method_call_configs.oc_under_call_config(pt.CallConfig.CALL) + + +# for subroutine, on_completes, is_creation in itertools.product( +# GOOD_SUBROUTINE_CASES, ON_COMPLETE_COMBINED_CASES, [False, True] +# ): +# if len(on_completes) == 0: +# with pytest.raises(pt.TealInputError) as err_no_oc: +# pt.Router.parse_conditions( +# subroutine if is_abi_subroutine else None, +# on_completes, +# is_creation, +# ) +# assert "on complete input should be non-empty list" in str(err_no_oc) +# continue +# +# mutated_on_completes = on_completes + [random.choice(on_completes)] +# with pytest.raises(pt.TealInputError) as err_dup_oc: +# pt.Router.parse_conditions( +# subroutine if is_abi_subroutine else None, +# mutated_on_completes, +# is_creation, +# ) +# assert "has duplicated on_complete(s)" in str(err_dup_oc) +# +# ( +# approval_condition_list, +# clear_state_condition_list, +# ) = pt.Router.parse_conditions( +# subroutine if is_abi_subroutine else None, +# on_completes, +# is_creation, +# ) +# +# if not oncomplete_is_in_oc_list(pt.OnComplete.ClearState, on_completes): +# assert len(clear_state_condition_list) == 0 +# +# assembled_ap_condition_list: list[pt.TealBlock] = [ +# assemble_helper(expr) for expr in approval_condition_list +# ] +# assembled_csp_condition_list: list[pt.TealBlock] = [ +# assemble_helper(expr) for expr in clear_state_condition_list +# ] +# if is_creation: +# creation_condition: pt.Expr = pt.Txn.application_id() == pt.Int(0) +# assembled_condition = assemble_helper(creation_condition) +# with pt.TealComponent.Context.ignoreExprEquality(): +# assert assembled_condition in assembled_ap_condition_list +# +# subroutine_arg_cond: pt.Expr +# if is_abi_subroutine: +# subroutine_arg_cond = ( +# pt.MethodSignature(typing.cast(str, method_sig)) +# == pt.Txn.application_args[0] +# ) +# else: +# subroutine_arg_cond = pt.Int(1) +# +# assembled_condition = assemble_helper(subroutine_arg_cond) +# with pt.TealComponent.Context.ignoreExprEquality(): +# if not ( +# len(on_completes) == 1 +# and oncomplete_is_in_oc_list(pt.OnComplete.ClearState, on_completes) +# ): +# assert assembled_condition in assembled_ap_condition_list +# +# if oncomplete_is_in_oc_list(pt.OnComplete.ClearState, on_completes): +# with pt.TealComponent.Context.ignoreExprEquality(): +# assert assembled_condition in assembled_csp_condition_list +# +# if len(on_completes) == 1 and oncomplete_is_in_oc_list( +# pt.OnComplete.ClearState, on_completes +# ): +# continue +# +# on_completes_cond: pt.Expr = pt.Or( +# *[ +# pt.Txn.on_completion() == oc +# for oc in on_completes +# if str(oc) != str(pt.OnComplete.ClearState) +# ] +# ) +# on_completes_cond_assembled = assemble_helper(on_completes_cond) +# with pt.TealComponent.Context.ignoreExprEquality(): +# assert on_completes_cond_assembled in assembled_ap_condition_list def test_wrap_handler_bare_call(): @@ -407,14 +480,18 @@ def test_contract_json_obj(): filter(lambda x: isinstance(x, pt.ABIReturnSubroutine), GOOD_SUBROUTINE_CASES) ) contract_name = "contract_name" - on_complete_actions = pt.OnCompleteActions().set_action( - safe_clear_state_delete, pt.OnComplete.ClearState + on_complete_actions = pt.OCActions( + clear_state=pt.OCAction.call_only(safe_clear_state_delete) ) router = pt.Router(contract_name, on_complete_actions) method_list: list[sdk_abi.Method] = [] for subroutine in abi_subroutines: - router.abi_method()(subroutine) + router.add_method_handler(subroutine) method_list.append(sdk_abi.Method.from_signature(subroutine.method_signature())) sdk_contract = sdk_abi.Contract(contract_name, method_list) contract = router.contract_construct() - assert sdk_contract.dictify() == contract + assert sdk_contract.desc == contract["desc"] + assert sdk_contract.name == contract["name"] + assert sdk_contract.networks == contract["networks"] + for method in sdk_contract.methods: + assert method.dictify() in contract["methods"] diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index c85528f9a..c934c8735 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -589,14 +589,16 @@ def __call__( def name(self) -> str: return self.subroutine.name() - def method_signature(self) -> str: + def method_signature(self, name_overloading: str = None) -> str: if not self.is_abi_routable(): raise TealInputError( "Only registrable methods may return a method signature" ) args = [str(v) for v in self.subroutine.abi_args.values()] - return f"{self.name()}({','.join(args)}){self.type_of()}" + if name_overloading is None: + name_overloading = self.name() + return f"{name_overloading}({','.join(args)}){self.type_of()}" def type_of(self) -> str | abi.TypeSpec: return ( diff --git a/pyteal/compiler/compiler_test.py b/pyteal/compiler/compiler_test.py index dfaea0a39..2ad435e22 100644 --- a/pyteal/compiler/compiler_test.py +++ b/pyteal/compiler/compiler_test.py @@ -2196,33 +2196,43 @@ def access_b4_store(magic_num: pt.abi.Uint64, *, output: pt.abi.Uint64): def test_router_app(): - on_completion_actions = pt.OnCompleteActions().set_action( - pt.Approve(), pt.OnComplete.ClearState + on_completion_actions = pt.OCActions( + clear_state=pt.OCAction.call_only(pt.Approve()) ) - router = pt.Router("Contract", on_completion_actions) + router = pt.Router("ASimpleQuestionablyRobustContract", on_completion_actions) - @router.abi_method() + @pt.ABIReturnSubroutine def add(a: pt.abi.Uint64, b: pt.abi.Uint64, *, output: pt.abi.Uint64) -> pt.Expr: return output.set(a.get() + b.get()) - @router.abi_method() + router.add_method_handler(add) + + @pt.ABIReturnSubroutine def sub(a: pt.abi.Uint64, b: pt.abi.Uint64, *, output: pt.abi.Uint64) -> pt.Expr: return output.set(a.get() - b.get()) - @router.abi_method() + router.add_method_handler(sub) + + @pt.ABIReturnSubroutine def mul(a: pt.abi.Uint64, b: pt.abi.Uint64, *, output: pt.abi.Uint64) -> pt.Expr: return output.set(a.get() * b.get()) - @router.abi_method() + router.add_method_handler(mul) + + @pt.ABIReturnSubroutine def div(a: pt.abi.Uint64, b: pt.abi.Uint64, *, output: pt.abi.Uint64) -> pt.Expr: return output.set(a.get() / b.get()) - @router.abi_method() + router.add_method_handler(div) + + @pt.ABIReturnSubroutine def mod(a: pt.abi.Uint64, b: pt.abi.Uint64, *, output: pt.abi.Uint64) -> pt.Expr: return output.set(a.get() % b.get()) - @router.abi_method() + router.add_method_handler(mod) + + @pt.ABIReturnSubroutine def all_laid_to_args( _a: pt.abi.Uint64, _b: pt.abi.Uint64, @@ -2262,6 +2272,8 @@ def all_laid_to_args( + _p.get() ) + router.add_method_handler(all_laid_to_args) + actual_ap_compiled, actual_csp_compiled, _ = router.compile_program( version=6, assembleConstants=True ) @@ -2269,48 +2281,48 @@ def all_laid_to_args( expected_ap = """#pragma version 6 intcblock 0 1 bytecblock 0x151f7c75 -pushbytes 0xfe6bdf69 // "add(uint64,uint64)uint64" txna ApplicationArgs 0 +pushbytes 0xfe6bdf69 // "add(uint64,uint64)uint64" == txn OnCompletion intc_0 // NoOp == && bnz main_l12 -pushbytes 0x78b488b7 // "sub(uint64,uint64)uint64" txna ApplicationArgs 0 +pushbytes 0x78b488b7 // "sub(uint64,uint64)uint64" == txn OnCompletion intc_0 // NoOp == && bnz main_l11 -pushbytes 0xe2f188c5 // "mul(uint64,uint64)uint64" txna ApplicationArgs 0 +pushbytes 0xe2f188c5 // "mul(uint64,uint64)uint64" == txn OnCompletion intc_0 // NoOp == && bnz main_l10 -pushbytes 0x16e80f08 // "div(uint64,uint64)uint64" txna ApplicationArgs 0 +pushbytes 0x16e80f08 // "div(uint64,uint64)uint64" == txn OnCompletion intc_0 // NoOp == && bnz main_l9 -pushbytes 0x4dfc58ae // "mod(uint64,uint64)uint64" txna ApplicationArgs 0 +pushbytes 0x4dfc58ae // "mod(uint64,uint64)uint64" == txn OnCompletion intc_0 // NoOp == && bnz main_l8 -pushbytes 0x487ce2fd // "all_laid_to_args(uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64)uint64" txna ApplicationArgs 0 +pushbytes 0x487ce2fd // "all_laid_to_args(uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64)uint64" == txn OnCompletion intc_0 // NoOp From 89ab1671d78b075b62d4c0fee981190905603527 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Tue, 24 May 2022 15:24:02 -0400 Subject: [PATCH 149/188] documentation for new interface from last discussion --- pyteal/ast/router.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/pyteal/ast/router.py b/pyteal/ast/router.py index d9c080611..2083aaf70 100644 --- a/pyteal/ast/router.py +++ b/pyteal/ast/router.py @@ -28,6 +28,18 @@ class CallConfig(Enum): + """ + CallConfigs: a "bitset" alike class for more fine-grained control over + `call or create` for a method about an OnComplete case. + + This enumeration class allows for specifying one of the four following cases: + - CALL + - CREATE + - ALL + - NEVER + for a method call on one on_complete case. + """ + NEVER = 0 CALL = 1 CREATE = 2 @@ -54,6 +66,10 @@ def __eq__(self, other: object) -> bool: @dataclass(frozen=True) class CallConfigs: + """ + CallConfigs keep track of one method registration's CallConfigs for all OnComplete cases. + """ + no_op: CallConfig = field(kw_only=True, default=CallConfig.CALL) opt_in: CallConfig = field(kw_only=True, default=CallConfig.NEVER) close_out: CallConfig = field(kw_only=True, default=CallConfig.NEVER) @@ -93,6 +109,10 @@ def oc_under_call_config(self, call_config: CallConfig) -> list[EnumInt]: @dataclass(frozen=True) class OCAction: + """ + OnComplete Action, registers bare calls to one single OnCompletion case. + """ + on_create: Optional[Expr | SubroutineFnWrapper | ABIReturnSubroutine] = field( kw_only=True, default=None ) @@ -128,6 +148,10 @@ def always( @dataclass(frozen=True) class OCActions: + """ + OnCompletion Actions keep track of bare-call registrations to all OnCompletion cases. + """ + close_out: OCAction = field(kw_only=True, default=OCAction.never()) clear_state: OCAction = field(kw_only=True, default=OCAction.never()) delete_application: OCAction = field(kw_only=True, default=OCAction.never()) From 59c86d15011256bab6f080cc40c44a3af6476891 Mon Sep 17 00:00:00 2001 From: Michael Diamant Date: Wed, 25 May 2022 10:59:18 -0400 Subject: [PATCH 150/188] Refactor #170 to reduce visibility and mutation (#362) * Refactor to use partition * Designate _oc_under_call_config as private --- pyteal/ast/router.py | 33 +++++++++++++++++++-------------- requirements.txt | 1 + 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/pyteal/ast/router.py b/pyteal/ast/router.py index 2083aaf70..5457f5305 100644 --- a/pyteal/ast/router.py +++ b/pyteal/ast/router.py @@ -1,6 +1,7 @@ from dataclasses import dataclass, field -from typing import Any, cast, Optional +from typing import Any, cast, Optional, Tuple from enum import Enum +import more_itertools import algosdk.abi as sdk_abi @@ -87,7 +88,7 @@ def is_never(self) -> bool: and self.delete_application == CallConfig.NEVER ) - def oc_under_call_config(self, call_config: CallConfig) -> list[EnumInt]: + def _oc_under_call_config(self, call_config: CallConfig) -> list[EnumInt]: if not isinstance(call_config, CallConfig): raise TealInputError( "generate condition based on OCMethodCallConfigs should be based on OCMethodConfig" @@ -427,15 +428,21 @@ def add_method_handler( raise TealInputError( f"registered method {method_signature} is never executed" ) - oc_create: list[EnumInt] = call_configs.oc_under_call_config(CallConfig.CREATE) - oc_call: list[EnumInt] = call_configs.oc_under_call_config(CallConfig.CALL) if method_signature in self.added_method_sig: raise TealInputError(f"re-registering method {method_signature} detected") self.added_method_sig.add(method_signature) wrapped = Router.wrap_handler(True, method_call) - if any(str(OnComplete.ClearState) == str(x) for x in oc_create): + def partition(cc: CallConfig) -> Tuple[bool, list[EnumInt]]: + (not_clear_states, clear_states) = more_itertools.partition( + lambda x: str(x) == str(OnComplete.ClearState), + call_configs._oc_under_call_config(cc), + ) + return (len(list(clear_states)) > 0, list(not_clear_states)) + + (create_has_clear_state, create_others) = partition(CallConfig.CREATE) + if create_has_clear_state: self.categorized_clear_state_ast.method_calls_create.append( CondNode( And( @@ -445,35 +452,33 @@ def add_method_handler( wrapped, ) ) - oc_create = [ - oc for oc in oc_create if str(oc) != str(OnComplete.ClearState) - ] - if any(str(OnComplete.ClearState) == str(x) for x in oc_call): + + (call_has_clear_state, call_others) = partition(CallConfig.CALL) + if call_has_clear_state: self.categorized_clear_state_ast.method_calls.append( CondNode( Txn.application_args[0] == MethodSignature(method_signature), wrapped, ) ) - oc_call = [oc for oc in oc_call if str(oc) != str(OnComplete.ClearState)] - if oc_create: + if create_others: self.categorized_approval_ast.method_calls_create.append( CondNode( And( Txn.application_id() == Int(0), Txn.application_args[0] == MethodSignature(method_signature), - Or(*[Txn.on_completion() == oc for oc in oc_create]), + Or(*[Txn.on_completion() == oc for oc in create_others]), ), wrapped, ) ) - if oc_call: + if call_others: self.categorized_approval_ast.method_calls.append( CondNode( And( Txn.application_args[0] == MethodSignature(method_signature), - Or(*[Txn.on_completion() == oc for oc in oc_call]), + Or(*[Txn.on_completion() == oc for oc in call_others]), ), wrapped, ) diff --git a/requirements.txt b/requirements.txt index 5238ceca9..79087ceb6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,6 +2,7 @@ black==22.3.0 flake8==4.0.1 flake8-tidy-imports==4.6.0 graviton@git+https://github.com/algorand/graviton@v0.3.0 +more-itertools==8.13.0 mypy==0.950 pytest==7.1.1 pytest-cov==3.0.0 From 2d203ffff9101d8d3cb2494e767bac284084f2df Mon Sep 17 00:00:00 2001 From: Hang Su Date: Wed, 25 May 2022 11:03:26 -0400 Subject: [PATCH 151/188] hide wrap handler method --- pyteal/ast/router.py | 12 ++++++------ pyteal/ast/router_test.py | 10 +++++----- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pyteal/ast/router.py b/pyteal/ast/router.py index 2083aaf70..595514bba 100644 --- a/pyteal/ast/router.py +++ b/pyteal/ast/router.py @@ -237,7 +237,7 @@ def __init__( self.__add_bare_call(bare_calls) @staticmethod - def wrap_handler( + def _wrap_handler( is_method_call: bool, handler: ABIReturnSubroutine | SubroutineFnWrapper | Expr ) -> Expr: """This is a helper function that handles transaction arguments passing in bare-app-call/abi-method handlers. @@ -369,7 +369,7 @@ def __add_bare_call(self, oc_actions: OCActions) -> None: Expr | SubroutineFnWrapper | ABIReturnSubroutine, cs_calls.on_call, ) - wrapped = Router.wrap_handler(False, on_call) + wrapped = Router._wrap_handler(False, on_call) self.categorized_clear_state_ast.bare_calls.append( CondNode(Int(1), wrapped) ) @@ -378,7 +378,7 @@ def __add_bare_call(self, oc_actions: OCActions) -> None: Expr | SubroutineFnWrapper | ABIReturnSubroutine, cs_calls.on_create, ) - wrapped = Router.wrap_handler(False, on_create) + wrapped = Router._wrap_handler(False, on_create) self.categorized_clear_state_ast.bare_calls_create.append( CondNode(Txn.application_id() == Int(0), wrapped) ) @@ -395,7 +395,7 @@ def __add_bare_call(self, oc_actions: OCActions) -> None: Expr | SubroutineFnWrapper | ABIReturnSubroutine, approval_bac.on_call, ) - wrapped = Router.wrap_handler(False, on_call) + wrapped = Router._wrap_handler(False, on_call) self.categorized_approval_ast.bare_calls.append( CondNode(Txn.on_completion() == oc, wrapped) ) @@ -404,7 +404,7 @@ def __add_bare_call(self, oc_actions: OCActions) -> None: Expr | SubroutineFnWrapper | ABIReturnSubroutine, approval_bac.on_create, ) - wrapped = Router.wrap_handler(False, on_create) + wrapped = Router._wrap_handler(False, on_create) self.categorized_approval_ast.bare_calls_create.append( CondNode( And(Txn.application_id() == Int(0), Txn.on_completion() == oc), @@ -433,7 +433,7 @@ def add_method_handler( raise TealInputError(f"re-registering method {method_signature} detected") self.added_method_sig.add(method_signature) - wrapped = Router.wrap_handler(True, method_call) + wrapped = Router._wrap_handler(True, method_call) if any(str(OnComplete.ClearState) == str(x) for x in oc_create): self.categorized_clear_state_ast.method_calls_create.append( diff --git a/pyteal/ast/router_test.py b/pyteal/ast/router_test.py index a3b348712..584b9742e 100644 --- a/pyteal/ast/router_test.py +++ b/pyteal/ast/router_test.py @@ -364,7 +364,7 @@ def test_wrap_handler_bare_call(): pt.Log(pt.Bytes("message")), ] for bare_call in BARE_CALL_CASES: - wrapped: pt.Expr = pt.Router.wrap_handler(False, bare_call) + wrapped: pt.Expr = pt.Router._wrap_handler(False, bare_call) match bare_call: case pt.Expr(): if bare_call.has_return(): @@ -404,24 +404,24 @@ def test_wrap_handler_bare_call(): ] for error_case, error_msg in ERROR_CASES: with pytest.raises(pt.TealInputError) as bug: - pt.Router.wrap_handler(False, error_case) + pt.Router._wrap_handler(False, error_case) assert error_msg in str(bug) def test_wrap_handler_method_call(): with pytest.raises(pt.TealInputError) as bug: - pt.Router.wrap_handler(True, not_registrable) + pt.Router._wrap_handler(True, not_registrable) assert "method call ABIReturnSubroutine is not registrable" in str(bug) with pytest.raises(pt.TealInputError) as bug: - pt.Router.wrap_handler(True, safe_clear_state_delete) + pt.Router._wrap_handler(True, safe_clear_state_delete) assert "method call should be only registering ABIReturnSubroutine" in str(bug) ONLY_ABI_SUBROUTINE_CASES = list( filter(lambda x: isinstance(x, pt.ABIReturnSubroutine), GOOD_SUBROUTINE_CASES) ) for abi_subroutine in ONLY_ABI_SUBROUTINE_CASES: - wrapped: pt.Expr = pt.Router.wrap_handler(True, abi_subroutine) + wrapped: pt.Expr = pt.Router._wrap_handler(True, abi_subroutine) assembled_wrapped: pt.TealBlock = assemble_helper(wrapped) args: list[pt.abi.BaseType] = [ From 2557efc2b14b34f1ab69429ad952cdc3cb084e27 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Wed, 25 May 2022 11:18:27 -0400 Subject: [PATCH 152/188] adding comment changes --- pyteal/ast/router.py | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/pyteal/ast/router.py b/pyteal/ast/router.py index eeb1b3bf0..0aba5fed5 100644 --- a/pyteal/ast/router.py +++ b/pyteal/ast/router.py @@ -1,8 +1,8 @@ from dataclasses import dataclass, field -from typing import Any, cast, Optional, Tuple +from typing import Any, cast, Optional from enum import Enum -import more_itertools +import more_itertools import algosdk.abi as sdk_abi from pyteal.config import METHOD_ARG_NUM_LIMIT @@ -107,6 +107,15 @@ def _oc_under_call_config(self, call_config: CallConfig) -> list[EnumInt]: if (oc_config & call_config) != CallConfig.NEVER ] + def _partition_oc_by_clear_state( + self, cc: CallConfig + ) -> tuple[bool, list[EnumInt]]: + not_clear_states, clear_states = more_itertools.partition( + lambda x: str(x) == str(OnComplete.ClearState), + self._oc_under_call_config(cc), + ) + return len(list(clear_states)) > 0, list(not_clear_states) + @dataclass(frozen=True) class OCAction: @@ -434,14 +443,10 @@ def add_method_handler( wrapped = Router._wrap_handler(True, method_call) - def partition(cc: CallConfig) -> Tuple[bool, list[EnumInt]]: - (not_clear_states, clear_states) = more_itertools.partition( - lambda x: str(x) == str(OnComplete.ClearState), - call_configs._oc_under_call_config(cc), - ) - return (len(list(clear_states)) > 0, list(not_clear_states)) - - (create_has_clear_state, create_others) = partition(CallConfig.CREATE) + ( + create_has_clear_state, + create_others, + ) = call_configs._partition_oc_by_clear_state(CallConfig.CREATE) if create_has_clear_state: self.categorized_clear_state_ast.method_calls_create.append( CondNode( @@ -453,7 +458,9 @@ def partition(cc: CallConfig) -> Tuple[bool, list[EnumInt]]: ) ) - (call_has_clear_state, call_others) = partition(CallConfig.CALL) + call_has_clear_state, call_others = call_configs._partition_oc_by_clear_state( + CallConfig.CALL + ) if call_has_clear_state: self.categorized_clear_state_ast.method_calls.append( CondNode( From a65d66ee8a71c17115e08f0a5f13250e410fa4ba Mon Sep 17 00:00:00 2001 From: Hang Su Date: Wed, 25 May 2022 13:32:05 -0400 Subject: [PATCH 153/188] pr review partly --- pyteal/__init__.pyi | 4 +- pyteal/ast/__init__.py | 8 +- pyteal/ast/router.py | 140 ++++++++++++++----------------- pyteal/ast/router_test.py | 10 +-- pyteal/ast/subroutine.py | 8 +- pyteal/compiler/compiler_test.py | 4 +- 6 files changed, 78 insertions(+), 96 deletions(-) diff --git a/pyteal/__init__.pyi b/pyteal/__init__.pyi index da7d57c5c..a85e8278b 100644 --- a/pyteal/__init__.pyi +++ b/pyteal/__init__.pyi @@ -139,9 +139,9 @@ __all__ = [ "Neq", "Nonce", "Not", - "OCAction", - "OCActions", "OnComplete", + "OnCompleteAction", + "OnCompleteActions", "Op", "OpUp", "OpUpMode", diff --git a/pyteal/ast/__init__.py b/pyteal/ast/__init__.py index 56e31103b..e02be6312 100644 --- a/pyteal/ast/__init__.py +++ b/pyteal/ast/__init__.py @@ -142,8 +142,8 @@ Router, CallConfig, CallConfigs, - OCAction, - OCActions, + OnCompleteAction, + OnCompleteActions, ) # abi @@ -289,8 +289,8 @@ "Router", "CallConfig", "CallConfigs", - "OCAction", - "OCActions", + "OnCompleteAction", + "OnCompleteActions", "abi", "EcdsaCurve", "EcdsaVerify", diff --git a/pyteal/ast/router.py b/pyteal/ast/router.py index 0aba5fed5..4316e87aa 100644 --- a/pyteal/ast/router.py +++ b/pyteal/ast/router.py @@ -1,4 +1,4 @@ -from dataclasses import dataclass, field +from dataclasses import dataclass, field, fields, astuple from typing import Any, cast, Optional from enum import Enum @@ -6,7 +6,7 @@ import algosdk.abi as sdk_abi from pyteal.config import METHOD_ARG_NUM_LIMIT -from pyteal.errors import TealInputError +from pyteal.errors import TealInputError, TealInternalError from pyteal.types import TealType from pyteal.compiler.compiler import compileTeal, DEFAULT_TEAL_VERSION, OptimizeOptions from pyteal.ir.ops import Mode @@ -30,7 +30,7 @@ class CallConfig(Enum): """ - CallConfigs: a "bitset" alike class for more fine-grained control over + CallConfigs: a "bitset"-like class for more fine-grained control over `call or create` for a method about an OnComplete case. This enumeration class allows for specifying one of the four following cases: @@ -48,17 +48,17 @@ class CallConfig(Enum): def __or__(self, other: object) -> "CallConfig": if not isinstance(other, CallConfig): - raise TealInputError("OCMethodConfig must be compared with same class") + raise TealInputError("CallConfig must be compared with same class") return CallConfig(self.value | other.value) def __and__(self, other: object) -> "CallConfig": if not isinstance(other, CallConfig): - raise TealInputError("OCMethodConfig must be compared with same class") + raise TealInputError("CallConfig must be compared with same class") return CallConfig(self.value & other.value) def __eq__(self, other: object) -> bool: if not isinstance(other, CallConfig): - raise TealInputError("OCMethodConfig must be compared with same class") + raise TealInputError("CallConfig must be compared with same class") return self.value == other.value @@ -79,19 +79,12 @@ class CallConfigs: delete_application: CallConfig = field(kw_only=True, default=CallConfig.NEVER) def is_never(self) -> bool: - return ( - self.no_op == CallConfig.NEVER - and self.opt_in == CallConfig.NEVER - and self.close_out == CallConfig.NEVER - and self.clear_state == CallConfig.NEVER - and self.update_application == CallConfig.NEVER - and self.delete_application == CallConfig.NEVER - ) + return all(map(lambda cc: cc == CallConfig.NEVER, astuple(self))) def _oc_under_call_config(self, call_config: CallConfig) -> list[EnumInt]: if not isinstance(call_config, CallConfig): raise TealInputError( - "generate condition based on OCMethodCallConfigs should be based on OCMethodConfig" + "generate condition based on CallConfigs should be based on CallConfig" ) config_oc_pairs: list[tuple[CallConfig, EnumInt]] = [ (self.no_op, OnComplete.NoOp), @@ -107,9 +100,7 @@ def _oc_under_call_config(self, call_config: CallConfig) -> list[EnumInt]: if (oc_config & call_config) != CallConfig.NEVER ] - def _partition_oc_by_clear_state( - self, cc: CallConfig - ) -> tuple[bool, list[EnumInt]]: + def partition_oc_by_clear_state(self, cc: CallConfig) -> tuple[bool, list[EnumInt]]: not_clear_states, clear_states = more_itertools.partition( lambda x: str(x) == str(OnComplete.ClearState), self._oc_under_call_config(cc), @@ -118,7 +109,7 @@ def _partition_oc_by_clear_state( @dataclass(frozen=True) -class OCAction: +class OnCompleteAction: """ OnComplete Action, registers bare calls to one single OnCompletion case. """ @@ -131,48 +122,58 @@ class OCAction: ) @staticmethod - def never() -> "OCAction": - return OCAction() + def never() -> "OnCompleteAction": + return OnCompleteAction() @staticmethod def create_only( f: Expr | SubroutineFnWrapper | ABIReturnSubroutine, - ) -> "OCAction": - return OCAction(on_create=f) + ) -> "OnCompleteAction": + return OnCompleteAction(on_create=f) @staticmethod def call_only( f: Expr | SubroutineFnWrapper | ABIReturnSubroutine, - ) -> "OCAction": - return OCAction(on_call=f) + ) -> "OnCompleteAction": + return OnCompleteAction(on_call=f) @staticmethod def always( f: Expr | SubroutineFnWrapper | ABIReturnSubroutine, - ) -> "OCAction": - return OCAction(on_create=f, on_call=f) + ) -> "OnCompleteAction": + return OnCompleteAction(on_create=f, on_call=f) + + def is_empty(self) -> bool: + return not (self.on_call or self.on_create) -OCAction.__module__ = "pyteal" +OnCompleteAction.__module__ = "pyteal" @dataclass(frozen=True) -class OCActions: +class OnCompleteActions: """ OnCompletion Actions keep track of bare-call registrations to all OnCompletion cases. """ - close_out: OCAction = field(kw_only=True, default=OCAction.never()) - clear_state: OCAction = field(kw_only=True, default=OCAction.never()) - delete_application: OCAction = field(kw_only=True, default=OCAction.never()) - no_op: OCAction = field(kw_only=True, default=OCAction.never()) - opt_in: OCAction = field(kw_only=True, default=OCAction.never()) - update_application: OCAction = field(kw_only=True, default=OCAction.never()) + close_out: OnCompleteAction = field(kw_only=True, default=OnCompleteAction.never()) + clear_state: OnCompleteAction = field( + kw_only=True, default=OnCompleteAction.never() + ) + delete_application: OnCompleteAction = field( + kw_only=True, default=OnCompleteAction.never() + ) + no_op: OnCompleteAction = field(kw_only=True, default=OnCompleteAction.never()) + opt_in: OnCompleteAction = field(kw_only=True, default=OnCompleteAction.never()) + update_application: OnCompleteAction = field( + kw_only=True, default=OnCompleteAction.never() + ) - def dictify(self) -> dict[EnumInt, OCAction]: - return { + def partition_oc_by_clear_state( + self, + ) -> tuple[OnCompleteAction, dict[EnumInt, OnCompleteAction]]: + return self.clear_state, { OnComplete.CloseOut: self.close_out, - OnComplete.ClearState: self.clear_state, OnComplete.DeleteApplication: self.delete_application, OnComplete.NoOp: self.no_op, OnComplete.OptIn: self.opt_in, @@ -180,13 +181,14 @@ def dictify(self) -> dict[EnumInt, OCAction]: } def is_empty(self) -> bool: - for oc_action in self.dictify().values(): - if oc_action.on_call is not None or oc_action.on_create is not None: + for action_field in fields(self): + action: OnCompleteAction = getattr(self, action_field.name) + if not action.is_empty(): return False return True -OCActions.__module__ = "pyteal" +OnCompleteActions.__module__ = "pyteal" @dataclass(frozen=True) @@ -212,7 +214,7 @@ def program_construction(self) -> Expr: + self.method_calls + self.bare_calls ) - if len(concatenated_ast) == 0: + if not concatenated_ast: raise TealInputError("ABIRouter: Cannot build program with an empty AST") program: Cond = Cond(*[[n.condition, n.branch] for n in concatenated_ast]) return program @@ -231,7 +233,7 @@ class Router: def __init__( self, name: str, - bare_calls: OCActions, + bare_calls: OnCompleteActions, ) -> None: """ Args: @@ -280,7 +282,7 @@ def _wrap_handler( case SubroutineFnWrapper(): if handler.type_of() != TealType.none: raise TealInputError( - f"subroutine call should be returning none not {handler.type_of()}." + f"subroutine call should be returning TealType.none not {handler.type_of()}." ) if handler.subroutine.argument_count() != 0: raise TealInputError( @@ -310,7 +312,7 @@ def _wrap_handler( ) if not handler.is_abi_routable(): raise TealInputError( - f"method call ABIReturnSubroutine is not registrable" + f"method call ABIReturnSubroutine is not routable " f"got {handler.subroutine.argument_count()} args with {len(handler.subroutine.abi_args)} ABI args." ) @@ -368,52 +370,32 @@ def _wrap_handler( Approve(), ) - def __add_bare_call(self, oc_actions: OCActions) -> None: + def __add_bare_call(self, oc_actions: OnCompleteActions) -> None: + action_type = Expr | SubroutineFnWrapper | ABIReturnSubroutine if oc_actions.is_empty(): - raise TealInputError("the OnCompleteActions is empty.") - bare_app_calls: dict[EnumInt, OCAction] = oc_actions.dictify() - - cs_calls = bare_app_calls[OnComplete.ClearState] - if cs_calls.on_call is not None: - on_call = cast( - Expr | SubroutineFnWrapper | ABIReturnSubroutine, - cs_calls.on_call, - ) + raise TealInternalError("the OnCompleteActions is empty.") + cs_calls, approval_calls = oc_actions.partition_oc_by_clear_state() + if cs_calls.on_call: + on_call = cast(action_type, cs_calls.on_call) wrapped = Router._wrap_handler(False, on_call) self.categorized_clear_state_ast.bare_calls.append( CondNode(Int(1), wrapped) ) - if cs_calls.on_create is not None: - on_create = cast( - Expr | SubroutineFnWrapper | ABIReturnSubroutine, - cs_calls.on_create, - ) + if cs_calls.on_create: + on_create = cast(action_type, cs_calls.on_create) wrapped = Router._wrap_handler(False, on_create) self.categorized_clear_state_ast.bare_calls_create.append( CondNode(Txn.application_id() == Int(0), wrapped) ) - - approval_calls = { - oc: oc_action - for oc, oc_action in bare_app_calls.items() - if str(oc) != str(OnComplete.ClearState) - } - for oc, approval_bac in approval_calls.items(): if approval_bac.on_call: - on_call = cast( - Expr | SubroutineFnWrapper | ABIReturnSubroutine, - approval_bac.on_call, - ) + on_call = cast(action_type, approval_bac.on_call) wrapped = Router._wrap_handler(False, on_call) self.categorized_approval_ast.bare_calls.append( CondNode(Txn.on_completion() == oc, wrapped) ) if approval_bac.on_create: - on_create = cast( - Expr | SubroutineFnWrapper | ABIReturnSubroutine, - approval_bac.on_create, - ) + on_create = cast(action_type, approval_bac.on_create) wrapped = Router._wrap_handler(False, on_create) self.categorized_approval_ast.bare_calls_create.append( CondNode( @@ -425,14 +407,14 @@ def __add_bare_call(self, oc_actions: OCActions) -> None: def add_method_handler( self, method_call: ABIReturnSubroutine, - method_overload_name: str = None, + overriding_name: str = None, call_configs: CallConfigs = CallConfigs(), ) -> None: if not isinstance(method_call, ABIReturnSubroutine): raise TealInputError( "for adding method handler, must be ABIReturnSubroutine" ) - method_signature = method_call.method_signature(method_overload_name) + method_signature = method_call.method_signature(overriding_name) if call_configs.is_never(): raise TealInputError( f"registered method {method_signature} is never executed" @@ -446,7 +428,7 @@ def add_method_handler( ( create_has_clear_state, create_others, - ) = call_configs._partition_oc_by_clear_state(CallConfig.CREATE) + ) = call_configs.partition_oc_by_clear_state(CallConfig.CREATE) if create_has_clear_state: self.categorized_clear_state_ast.method_calls_create.append( CondNode( @@ -458,7 +440,7 @@ def add_method_handler( ) ) - call_has_clear_state, call_others = call_configs._partition_oc_by_clear_state( + call_has_clear_state, call_others = call_configs.partition_oc_by_clear_state( CallConfig.CALL ) if call_has_clear_state: diff --git a/pyteal/ast/router_test.py b/pyteal/ast/router_test.py index 584b9742e..1ba8a72fc 100644 --- a/pyteal/ast/router_test.py +++ b/pyteal/ast/router_test.py @@ -241,7 +241,7 @@ def test_add_method(): ] router = pt.Router( "routerForMethodTest", - pt.OCActions(clear_state=pt.OCAction.call_only(pt.Approve())), + pt.OnCompleteActions(clear_state=pt.OnCompleteAction.call_only(pt.Approve())), ) for subroutine in normal_subroutine: with pytest.raises(pt.TealInputError) as must_be_abi: @@ -383,7 +383,7 @@ def test_wrap_handler_bare_call(): ), ( returning_u64, - f"subroutine call should be returning none not {pt.TealType.uint64}.", + f"subroutine call should be returning TealType.none not {pt.TealType.uint64}.", ), ( mult_over_u64_and_log, @@ -411,7 +411,7 @@ def test_wrap_handler_bare_call(): def test_wrap_handler_method_call(): with pytest.raises(pt.TealInputError) as bug: pt.Router._wrap_handler(True, not_registrable) - assert "method call ABIReturnSubroutine is not registrable" in str(bug) + assert "method call ABIReturnSubroutine is not routable" in str(bug) with pytest.raises(pt.TealInputError) as bug: pt.Router._wrap_handler(True, safe_clear_state_delete) @@ -480,8 +480,8 @@ def test_contract_json_obj(): filter(lambda x: isinstance(x, pt.ABIReturnSubroutine), GOOD_SUBROUTINE_CASES) ) contract_name = "contract_name" - on_complete_actions = pt.OCActions( - clear_state=pt.OCAction.call_only(safe_clear_state_delete) + on_complete_actions = pt.OnCompleteActions( + clear_state=pt.OnCompleteAction.call_only(safe_clear_state_delete) ) router = pt.Router(contract_name, on_complete_actions) method_list: list[sdk_abi.Method] = [] diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index c934c8735..75c98ae76 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -589,16 +589,16 @@ def __call__( def name(self) -> str: return self.subroutine.name() - def method_signature(self, name_overloading: str = None) -> str: + def method_signature(self, overriding_name: str = None) -> str: if not self.is_abi_routable(): raise TealInputError( "Only registrable methods may return a method signature" ) args = [str(v) for v in self.subroutine.abi_args.values()] - if name_overloading is None: - name_overloading = self.name() - return f"{name_overloading}({','.join(args)}){self.type_of()}" + if overriding_name is None: + overriding_name = self.name() + return f"{overriding_name}({','.join(args)}){self.type_of()}" def type_of(self) -> str | abi.TypeSpec: return ( diff --git a/pyteal/compiler/compiler_test.py b/pyteal/compiler/compiler_test.py index 2ad435e22..9966a0503 100644 --- a/pyteal/compiler/compiler_test.py +++ b/pyteal/compiler/compiler_test.py @@ -2196,8 +2196,8 @@ def access_b4_store(magic_num: pt.abi.Uint64, *, output: pt.abi.Uint64): def test_router_app(): - on_completion_actions = pt.OCActions( - clear_state=pt.OCAction.call_only(pt.Approve()) + on_completion_actions = pt.OnCompleteActions( + clear_state=pt.OnCompleteAction.call_only(pt.Approve()) ) router = pt.Router("ASimpleQuestionablyRobustContract", on_completion_actions) From f38caed25423659d71dcd66e7569c4692964f72c Mon Sep 17 00:00:00 2001 From: Hang Su Date: Wed, 25 May 2022 16:19:08 -0400 Subject: [PATCH 154/188] partition into two methods --- pyteal/ast/router.py | 28 ++++++++++++++-------------- requirements.txt | 1 - 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/pyteal/ast/router.py b/pyteal/ast/router.py index 4316e87aa..871598d32 100644 --- a/pyteal/ast/router.py +++ b/pyteal/ast/router.py @@ -2,7 +2,6 @@ from typing import Any, cast, Optional from enum import Enum -import more_itertools import algosdk.abi as sdk_abi from pyteal.config import METHOD_ARG_NUM_LIMIT @@ -100,12 +99,16 @@ def _oc_under_call_config(self, call_config: CallConfig) -> list[EnumInt]: if (oc_config & call_config) != CallConfig.NEVER ] - def partition_oc_by_clear_state(self, cc: CallConfig) -> tuple[bool, list[EnumInt]]: - not_clear_states, clear_states = more_itertools.partition( - lambda x: str(x) == str(OnComplete.ClearState), - self._oc_under_call_config(cc), + def has_clear_state(self, cc: CallConfig) -> bool: + return (self.clear_state & cc) != CallConfig.NEVER + + def approval_program_oc(self, cc: CallConfig) -> list[EnumInt]: + return list( + filter( + lambda x: str(x) != str(OnComplete.ClearState), + self._oc_under_call_config(cc), + ) ) - return len(list(clear_states)) > 0, list(not_clear_states) @dataclass(frozen=True) @@ -425,10 +428,11 @@ def add_method_handler( wrapped = Router._wrap_handler(True, method_call) - ( - create_has_clear_state, - create_others, - ) = call_configs.partition_oc_by_clear_state(CallConfig.CREATE) + create_has_clear_state = call_configs.has_clear_state(CallConfig.CREATE) + create_others = call_configs.approval_program_oc(CallConfig.CREATE) + call_has_clear_state = call_configs.has_clear_state(CallConfig.CALL) + call_others = call_configs.approval_program_oc(CallConfig.CALL) + if create_has_clear_state: self.categorized_clear_state_ast.method_calls_create.append( CondNode( @@ -439,10 +443,6 @@ def add_method_handler( wrapped, ) ) - - call_has_clear_state, call_others = call_configs.partition_oc_by_clear_state( - CallConfig.CALL - ) if call_has_clear_state: self.categorized_clear_state_ast.method_calls.append( CondNode( diff --git a/requirements.txt b/requirements.txt index 79087ceb6..5238ceca9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,6 @@ black==22.3.0 flake8==4.0.1 flake8-tidy-imports==4.6.0 graviton@git+https://github.com/algorand/graviton@v0.3.0 -more-itertools==8.13.0 mypy==0.950 pytest==7.1.1 pytest-cov==3.0.0 From 0f10534ef2b2924fb165d921c4f9b406002477a7 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Wed, 25 May 2022 16:25:43 -0400 Subject: [PATCH 155/188] hide all these methods --- pyteal/ast/router.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pyteal/ast/router.py b/pyteal/ast/router.py index 871598d32..4e4c1ee9a 100644 --- a/pyteal/ast/router.py +++ b/pyteal/ast/router.py @@ -99,10 +99,10 @@ def _oc_under_call_config(self, call_config: CallConfig) -> list[EnumInt]: if (oc_config & call_config) != CallConfig.NEVER ] - def has_clear_state(self, cc: CallConfig) -> bool: + def _has_clear_state(self, cc: CallConfig) -> bool: return (self.clear_state & cc) != CallConfig.NEVER - def approval_program_oc(self, cc: CallConfig) -> list[EnumInt]: + def _approval_program_oc(self, cc: CallConfig) -> list[EnumInt]: return list( filter( lambda x: str(x) != str(OnComplete.ClearState), @@ -172,7 +172,7 @@ class OnCompleteActions: kw_only=True, default=OnCompleteAction.never() ) - def partition_oc_by_clear_state( + def _clear_state_n_other_oc( self, ) -> tuple[OnCompleteAction, dict[EnumInt, OnCompleteAction]]: return self.clear_state, { @@ -377,7 +377,7 @@ def __add_bare_call(self, oc_actions: OnCompleteActions) -> None: action_type = Expr | SubroutineFnWrapper | ABIReturnSubroutine if oc_actions.is_empty(): raise TealInternalError("the OnCompleteActions is empty.") - cs_calls, approval_calls = oc_actions.partition_oc_by_clear_state() + cs_calls, approval_calls = oc_actions._clear_state_n_other_oc() if cs_calls.on_call: on_call = cast(action_type, cs_calls.on_call) wrapped = Router._wrap_handler(False, on_call) @@ -428,10 +428,10 @@ def add_method_handler( wrapped = Router._wrap_handler(True, method_call) - create_has_clear_state = call_configs.has_clear_state(CallConfig.CREATE) - create_others = call_configs.approval_program_oc(CallConfig.CREATE) - call_has_clear_state = call_configs.has_clear_state(CallConfig.CALL) - call_others = call_configs.approval_program_oc(CallConfig.CALL) + create_has_clear_state = call_configs._has_clear_state(CallConfig.CREATE) + create_others = call_configs._approval_program_oc(CallConfig.CREATE) + call_has_clear_state = call_configs._has_clear_state(CallConfig.CALL) + call_others = call_configs._approval_program_oc(CallConfig.CALL) if create_has_clear_state: self.categorized_clear_state_ast.method_calls_create.append( From 2323920cf2f947b10fbb635a644dd4f6b25146a9 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Wed, 25 May 2022 16:34:30 -0400 Subject: [PATCH 156/188] arc4 compliant CallConfigs classmethod --- pyteal/ast/router.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/pyteal/ast/router.py b/pyteal/ast/router.py index 4e4c1ee9a..edacf8b18 100644 --- a/pyteal/ast/router.py +++ b/pyteal/ast/router.py @@ -80,6 +80,17 @@ class CallConfigs: def is_never(self) -> bool: return all(map(lambda cc: cc == CallConfig.NEVER, astuple(self))) + @classmethod + def arc4_compliant(cls): + return cls( + no_op=CallConfig.ALL, + opt_in=CallConfig.ALL, + close_out=CallConfig.ALL, + clear_state=CallConfig.ALL, + update_application=CallConfig.ALL, + delete_application=CallConfig.ALL, + ) + def _oc_under_call_config(self, call_config: CallConfig) -> list[EnumInt]: if not isinstance(call_config, CallConfig): raise TealInputError( @@ -229,7 +240,7 @@ def program_construction(self) -> Expr: class Router: """ Class that help constructs: - - an ARC-4 app's approval/clear-state programs + - a *Generalized* ARC-4 app's approval/clear-state programs - and a contract JSON object allowing for easily read and call methods in the contract """ From 9cea65d2ca57abf9486746d02860abc1394ced64 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Wed, 25 May 2022 17:05:43 -0400 Subject: [PATCH 157/188] arc4 compliant CallConfigs documentation --- pyteal/ast/router.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/pyteal/ast/router.py b/pyteal/ast/router.py index edacf8b18..66418d844 100644 --- a/pyteal/ast/router.py +++ b/pyteal/ast/router.py @@ -68,6 +68,18 @@ def __eq__(self, other: object) -> bool: class CallConfigs: """ CallConfigs keep track of one method registration's CallConfigs for all OnComplete cases. + + By ARC-0004 spec: + If an Application is called with greater than zero Application call arguments (NOT a bare Application call), + the Application MUST always treat the first argument as a method selector and invoke the specified method, + regardless of the OnCompletion action of the Application call. + This applies to Application creation transactions as well, where the supplied Application ID is 0. + + The `CallConfigs` implementation generalized contract method call such that method call is allowed + for certain OnCompletions. + + The `arc4_compliant` method constructs a `CallConfigs` that allows a method call to be executed + under any OnCompletion, which is "arc4-compliant". """ no_op: CallConfig = field(kw_only=True, default=CallConfig.CALL) @@ -91,6 +103,9 @@ def arc4_compliant(cls): delete_application=CallConfig.ALL, ) + def is_arc4_compliant(self) -> bool: + return self == self.arc4_compliant() + def _oc_under_call_config(self, call_config: CallConfig) -> list[EnumInt]: if not isinstance(call_config, CallConfig): raise TealInputError( From 065a797f1eb419d42d83ec046f5918e21bc98edb Mon Sep 17 00:00:00 2001 From: Hang Su Date: Wed, 25 May 2022 17:22:20 -0400 Subject: [PATCH 158/188] disclaimer comments --- pyteal/ast/router.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/pyteal/ast/router.py b/pyteal/ast/router.py index 66418d844..bc4e093f4 100644 --- a/pyteal/ast/router.py +++ b/pyteal/ast/router.py @@ -27,6 +27,22 @@ from pyteal.ast.return_ import Approve +################################################################################ +# DISCLAIMER # +################################################################################ +# ABI-Router is still taking shape and is subject to backwards incompatible # +# changes. # +# # +# * For ARC-4 Application definition, feel encouraged to use ABI-Router. # +# Expect a best-effort attempt to minimize backwards incompatible changes # +# along with a migration path. # +# # +# * USE AT YOUR OWN RISK. # +# Based on feedback, the API and usage patterns will change more freely and # +# with less effort to provide migration paths. # +################################################################################ + + class CallConfig(Enum): """ CallConfigs: a "bitset"-like class for more fine-grained control over From e0dc4bd58bc1ac491fe131d406ec7ecaa5057cd6 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Wed, 25 May 2022 20:33:25 -0400 Subject: [PATCH 159/188] update new abi return hash prefix --- pyteal/ast/abi/method_return.py | 6 ++-- pyteal/ast/router.py | 31 +++++-------------- pyteal/ast/router_test.py | 8 ++--- pyteal/compiler/compiler_test.py | 16 +++++----- pyteal/compiler/constants.py | 2 +- pyteal/config.py | 5 ++- .../teal/roundtrip/app_roundtrip_().teal | 2 +- .../teal/roundtrip/app_roundtrip_(bool).teal | 2 +- .../roundtrip/app_roundtrip_(bool)[10].teal | 2 +- .../roundtrip/app_roundtrip_(bool,byte).teal | 2 +- ..._roundtrip_(bool,byte,address,string).teal | 2 +- ...byte),uint8)[2],string,bool[]))[]_<2>.teal | 2 +- ...rip_(bool,byte,address,string,uint64).teal | 2 +- .../app_roundtrip_(bool,uint64,uint32).teal | 2 +- .../teal/roundtrip/app_roundtrip_(byte).teal | 2 +- .../app_roundtrip_(byte,bool,uint64).teal | 2 +- ...[4],(bool,bool),uint64,address)[]_<7>.teal | 2 +- .../roundtrip/app_roundtrip_(uint16).teal | 2 +- .../app_roundtrip_(uint16,uint8,byte).teal | 2 +- .../roundtrip/app_roundtrip_(uint32).teal | 2 +- .../app_roundtrip_(uint32,uint16,uint8).teal | 2 +- .../roundtrip/app_roundtrip_(uint64).teal | 2 +- .../app_roundtrip_(uint64,uint32,uint16).teal | 2 +- .../teal/roundtrip/app_roundtrip_(uint8).teal | 2 +- .../app_roundtrip_(uint8,byte,bool).teal | 8 ++--- .../teal/roundtrip/app_roundtrip_address.teal | 2 +- .../app_roundtrip_address[]_<10>.teal | 2 +- .../teal/roundtrip/app_roundtrip_bool.teal | 2 +- .../teal/roundtrip/app_roundtrip_bool[1].teal | 2 +- .../app_roundtrip_bool[3][]_<11>.teal | 2 +- .../roundtrip/app_roundtrip_bool[42].teal | 2 +- .../roundtrip/app_roundtrip_bool[]_<0>.teal | 2 +- .../roundtrip/app_roundtrip_bool[]_<1>.teal | 2 +- .../roundtrip/app_roundtrip_bool[]_<42>.teal | 2 +- .../teal/roundtrip/app_roundtrip_byte.teal | 2 +- .../roundtrip/app_roundtrip_string_<0>.teal | 2 +- .../roundtrip/app_roundtrip_string_<13>.teal | 2 +- .../roundtrip/app_roundtrip_string_<1>.teal | 2 +- .../teal/roundtrip/app_roundtrip_uint16.teal | 2 +- .../teal/roundtrip/app_roundtrip_uint32.teal | 2 +- .../teal/roundtrip/app_roundtrip_uint64.teal | 2 +- .../roundtrip/app_roundtrip_uint64[1].teal | 2 +- .../roundtrip/app_roundtrip_uint64[42].teal | 2 +- .../roundtrip/app_roundtrip_uint64[]_<0>.teal | 2 +- .../roundtrip/app_roundtrip_uint64[]_<1>.teal | 2 +- .../app_roundtrip_uint64[]_<42>.teal | 2 +- .../teal/roundtrip/app_roundtrip_uint8.teal | 2 +- .../unit/teal/abi/app_fn_0arg_uint64_ret.teal | 2 +- tests/unit/teal/abi/app_fn_1arg_1ret.teal | 2 +- .../teal/abi/app_fn_1tt_arg_uint64_ret.teal | 2 +- .../unit/teal/abi/app_fn_2mixed_arg_1ret.teal | 2 +- ...ser_guide_snippet_ABIReturnSubroutine.teal | 6 ++-- 52 files changed, 79 insertions(+), 91 deletions(-) diff --git a/pyteal/ast/abi/method_return.py b/pyteal/ast/abi/method_return.py index 7d7af0ed7..5324c6344 100644 --- a/pyteal/ast/abi/method_return.py +++ b/pyteal/ast/abi/method_return.py @@ -22,9 +22,9 @@ def __teal__(self, options: "CompileOptions") -> Tuple[TealBlock, TealSimpleBloc raise TealInputError( f"current version {options.version} is lower than log's min version {Op.log.min_version}" ) - return Log( - Concat(Bytes("base16", RETURN_METHOD_SELECTOR), self.arg.encode()) - ).__teal__(options) + return Log(Concat(Bytes(RETURN_METHOD_SELECTOR), self.arg.encode())).__teal__( + options + ) def __str__(self) -> str: return f"(MethodReturn {self.arg.type_spec()})" diff --git a/pyteal/ast/router.py b/pyteal/ast/router.py index bc4e093f4..0b4707de4 100644 --- a/pyteal/ast/router.py +++ b/pyteal/ast/router.py @@ -1,6 +1,6 @@ from dataclasses import dataclass, field, fields, astuple -from typing import Any, cast, Optional -from enum import Enum +from typing import cast, Optional +from enum import IntFlag import algosdk.abi as sdk_abi @@ -43,7 +43,7 @@ ################################################################################ -class CallConfig(Enum): +class CallConfig(IntFlag): """ CallConfigs: a "bitset"-like class for more fine-grained control over `call or create` for a method about an OnComplete case. @@ -61,21 +61,6 @@ class CallConfig(Enum): CREATE = 2 ALL = 3 - def __or__(self, other: object) -> "CallConfig": - if not isinstance(other, CallConfig): - raise TealInputError("CallConfig must be compared with same class") - return CallConfig(self.value | other.value) - - def __and__(self, other: object) -> "CallConfig": - if not isinstance(other, CallConfig): - raise TealInputError("CallConfig must be compared with same class") - return CallConfig(self.value & other.value) - - def __eq__(self, other: object) -> bool: - if not isinstance(other, CallConfig): - raise TealInputError("CallConfig must be compared with same class") - return self.value == other.value - CallConfig.__module__ = "pyteal" @@ -299,7 +284,7 @@ def _wrap_handler( ) -> Expr: """This is a helper function that handles transaction arguments passing in bare-app-call/abi-method handlers. - If `is_abi_method` is True, then it can only be `ABIReturnSubroutine`, + If `is_method_call` is True, then it can only be `ABIReturnSubroutine`, otherwise: - both `ABIReturnSubroutine` and `Subroutine` takes 0 argument on the stack. - all three cases have none (or void) type. @@ -515,7 +500,7 @@ def add_method_handler( ) ) - def contract_construct(self) -> dict[str, Any]: + def contract_construct(self) -> sdk_abi.Contract: """A helper function in constructing contract JSON object. It takes out the method signatures from approval program `ProgramNode`'s, @@ -528,9 +513,9 @@ def contract_construct(self) -> dict[str, Any]: method_collections = [ sdk_abi.Method.from_signature(sig) for sig in self.added_method_sig ] - return sdk_abi.Contract(self.name, method_collections).dictify() + return sdk_abi.Contract(self.name, method_collections) - def build_program(self) -> tuple[Expr, Expr, dict[str, Any]]: + def build_program(self) -> tuple[Expr, Expr, sdk_abi.Contract]: """ Constructs ASTs for approval and clear-state programs from the registered methods in the router, also generates a JSON object of contract to allow client read and call the methods easily. @@ -552,7 +537,7 @@ def compile_program( version: int = DEFAULT_TEAL_VERSION, assembleConstants: bool = False, optimize: OptimizeOptions = None, - ) -> tuple[str, str, dict[str, Any]]: + ) -> tuple[str, str, sdk_abi.Contract]: """ Combining `build_program` and `compileTeal`, compiles built Approval and ClearState programs and returns Contract JSON object for off-chain calling. diff --git a/pyteal/ast/router_test.py b/pyteal/ast/router_test.py index 1ba8a72fc..4649057f7 100644 --- a/pyteal/ast/router_test.py +++ b/pyteal/ast/router_test.py @@ -490,8 +490,8 @@ def test_contract_json_obj(): method_list.append(sdk_abi.Method.from_signature(subroutine.method_signature())) sdk_contract = sdk_abi.Contract(contract_name, method_list) contract = router.contract_construct() - assert sdk_contract.desc == contract["desc"] - assert sdk_contract.name == contract["name"] - assert sdk_contract.networks == contract["networks"] + assert sdk_contract.desc == contract.desc + assert sdk_contract.name == contract.name + assert sdk_contract.networks == contract.networks for method in sdk_contract.methods: - assert method.dictify() in contract["methods"] + assert method in contract.methods diff --git a/pyteal/compiler/compiler_test.py b/pyteal/compiler/compiler_test.py index 9966a0503..7c6c80fff 100644 --- a/pyteal/compiler/compiler_test.py +++ b/pyteal/compiler/compiler_test.py @@ -2042,7 +2042,7 @@ def abi_sum( load 0 callsub abisum_0 store 1 -byte 0x151F7C75 +byte 0x151f7c75 load 1 itob concat @@ -2124,7 +2124,7 @@ def conditional_factorial( load 0 callsub conditionalfactorial_0 store 1 -byte 0x151F7C75 +byte 0x151f7c75 load 1 itob concat @@ -2401,7 +2401,7 @@ def all_laid_to_args( load 46 callsub alllaidtoargs_5 store 47 -bytec_0 // 0x151F7C75 +bytec_0 // 0x151f7c75 load 47 itob concat @@ -2419,7 +2419,7 @@ def all_laid_to_args( load 25 callsub mod_4 store 26 -bytec_0 // 0x151F7C75 +bytec_0 // 0x151f7c75 load 26 itob concat @@ -2437,7 +2437,7 @@ def all_laid_to_args( load 19 callsub div_3 store 20 -bytec_0 // 0x151F7C75 +bytec_0 // 0x151f7c75 load 20 itob concat @@ -2455,7 +2455,7 @@ def all_laid_to_args( load 13 callsub mul_2 store 14 -bytec_0 // 0x151F7C75 +bytec_0 // 0x151f7c75 load 14 itob concat @@ -2473,7 +2473,7 @@ def all_laid_to_args( load 7 callsub sub_1 store 8 -bytec_0 // 0x151F7C75 +bytec_0 // 0x151f7c75 load 8 itob concat @@ -2491,7 +2491,7 @@ def all_laid_to_args( load 1 callsub add_0 store 2 -bytec_0 // 0x151F7C75 +bytec_0 // 0x151f7c75 load 2 itob concat diff --git a/pyteal/compiler/constants.py b/pyteal/compiler/constants.py index b47215588..415b7d19e 100644 --- a/pyteal/compiler/constants.py +++ b/pyteal/compiler/constants.py @@ -104,7 +104,7 @@ def extractMethodSigValue(op: TealOp) -> bytes: methodSignature = methodSignature[1:-1] else: raise TealInternalError( - "Method signature opcode error: signatue {} not wrapped with double-quotes".format( + "Method signature opcode error: signature {} not wrapped with double-quotes".format( methodSignature ) ) diff --git a/pyteal/config.py b/pyteal/config.py index 304993bb2..f905ad529 100644 --- a/pyteal/config.py +++ b/pyteal/config.py @@ -1,3 +1,6 @@ +from algosdk.atomic_transaction_composer import ABI_RETURN_HASH + + # Maximum size of an atomic transaction group. MAX_GROUP_SIZE = 16 @@ -5,7 +8,7 @@ NUM_SLOTS = 256 # Method return selector in base16 -RETURN_METHOD_SELECTOR = "0x151F7C75" +RETURN_METHOD_SELECTOR = ABI_RETURN_HASH # Method argument number limit METHOD_ARG_NUM_LIMIT = 15 diff --git a/tests/integration/teal/roundtrip/app_roundtrip_().teal b/tests/integration/teal/roundtrip/app_roundtrip_().teal index b59fbd275..6ce9ab897 100644 --- a/tests/integration/teal/roundtrip/app_roundtrip_().teal +++ b/tests/integration/teal/roundtrip/app_roundtrip_().teal @@ -4,7 +4,7 @@ store 1 load 1 callsub roundtripper_1 store 0 -byte 0x151F7C75 +byte 0x151f7c75 load 0 concat log diff --git a/tests/integration/teal/roundtrip/app_roundtrip_(bool).teal b/tests/integration/teal/roundtrip/app_roundtrip_(bool).teal index 6344eb38b..2760bf66f 100644 --- a/tests/integration/teal/roundtrip/app_roundtrip_(bool).teal +++ b/tests/integration/teal/roundtrip/app_roundtrip_(bool).teal @@ -4,7 +4,7 @@ store 2 load 2 callsub roundtripper_1 store 1 -byte 0x151F7C75 +byte 0x151f7c75 load 1 concat log diff --git a/tests/integration/teal/roundtrip/app_roundtrip_(bool)[10].teal b/tests/integration/teal/roundtrip/app_roundtrip_(bool)[10].teal index 56210aa2a..4023bb75d 100644 --- a/tests/integration/teal/roundtrip/app_roundtrip_(bool)[10].teal +++ b/tests/integration/teal/roundtrip/app_roundtrip_(bool)[10].teal @@ -4,7 +4,7 @@ store 2 load 2 callsub roundtripper_2 store 1 -byte 0x151F7C75 +byte 0x151f7c75 load 1 concat log diff --git a/tests/integration/teal/roundtrip/app_roundtrip_(bool,byte).teal b/tests/integration/teal/roundtrip/app_roundtrip_(bool,byte).teal index 44f609078..87b114561 100644 --- a/tests/integration/teal/roundtrip/app_roundtrip_(bool,byte).teal +++ b/tests/integration/teal/roundtrip/app_roundtrip_(bool,byte).teal @@ -4,7 +4,7 @@ store 3 load 3 callsub roundtripper_1 store 2 -byte 0x151F7C75 +byte 0x151f7c75 load 2 concat log diff --git a/tests/integration/teal/roundtrip/app_roundtrip_(bool,byte,address,string).teal b/tests/integration/teal/roundtrip/app_roundtrip_(bool,byte,address,string).teal index 3af93e1c1..591bfd847 100644 --- a/tests/integration/teal/roundtrip/app_roundtrip_(bool,byte,address,string).teal +++ b/tests/integration/teal/roundtrip/app_roundtrip_(bool,byte,address,string).teal @@ -4,7 +4,7 @@ store 5 load 5 callsub roundtripper_1 store 4 -byte 0x151F7C75 +byte 0x151f7c75 load 4 concat log 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 index f66d64459..7544bb298 100644 --- 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 @@ -4,7 +4,7 @@ store 6 load 6 callsub roundtripper_2 store 5 -byte 0x151F7C75 +byte 0x151f7c75 load 5 concat log diff --git a/tests/integration/teal/roundtrip/app_roundtrip_(bool,byte,address,string,uint64).teal b/tests/integration/teal/roundtrip/app_roundtrip_(bool,byte,address,string,uint64).teal index 25e2ab3d7..110511e15 100644 --- a/tests/integration/teal/roundtrip/app_roundtrip_(bool,byte,address,string,uint64).teal +++ b/tests/integration/teal/roundtrip/app_roundtrip_(bool,byte,address,string,uint64).teal @@ -4,7 +4,7 @@ store 6 load 6 callsub roundtripper_1 store 5 -byte 0x151F7C75 +byte 0x151f7c75 load 5 concat log diff --git a/tests/integration/teal/roundtrip/app_roundtrip_(bool,uint64,uint32).teal b/tests/integration/teal/roundtrip/app_roundtrip_(bool,uint64,uint32).teal index 7f8aabc87..6cd11c2f6 100644 --- a/tests/integration/teal/roundtrip/app_roundtrip_(bool,uint64,uint32).teal +++ b/tests/integration/teal/roundtrip/app_roundtrip_(bool,uint64,uint32).teal @@ -4,7 +4,7 @@ store 4 load 4 callsub roundtripper_1 store 3 -byte 0x151F7C75 +byte 0x151f7c75 load 3 concat log diff --git a/tests/integration/teal/roundtrip/app_roundtrip_(byte).teal b/tests/integration/teal/roundtrip/app_roundtrip_(byte).teal index 2a4c05465..4e2b05add 100644 --- a/tests/integration/teal/roundtrip/app_roundtrip_(byte).teal +++ b/tests/integration/teal/roundtrip/app_roundtrip_(byte).teal @@ -4,7 +4,7 @@ store 2 load 2 callsub roundtripper_1 store 1 -byte 0x151F7C75 +byte 0x151f7c75 load 1 concat log diff --git a/tests/integration/teal/roundtrip/app_roundtrip_(byte,bool,uint64).teal b/tests/integration/teal/roundtrip/app_roundtrip_(byte,bool,uint64).teal index cf613fa8c..f96dfc286 100644 --- a/tests/integration/teal/roundtrip/app_roundtrip_(byte,bool,uint64).teal +++ b/tests/integration/teal/roundtrip/app_roundtrip_(byte,bool,uint64).teal @@ -4,7 +4,7 @@ store 4 load 4 callsub roundtripper_1 store 3 -byte 0x151F7C75 +byte 0x151f7c75 load 3 concat log diff --git a/tests/integration/teal/roundtrip/app_roundtrip_(byte[4],(bool,bool),uint64,address)[]_<7>.teal b/tests/integration/teal/roundtrip/app_roundtrip_(byte[4],(bool,bool),uint64,address)[]_<7>.teal index c9b93fc85..e0e119c13 100644 --- a/tests/integration/teal/roundtrip/app_roundtrip_(byte[4],(bool,bool),uint64,address)[]_<7>.teal +++ b/tests/integration/teal/roundtrip/app_roundtrip_(byte[4],(bool,bool),uint64,address)[]_<7>.teal @@ -4,7 +4,7 @@ store 5 load 5 callsub roundtripper_2 store 4 -byte 0x151F7C75 +byte 0x151f7c75 load 4 concat log diff --git a/tests/integration/teal/roundtrip/app_roundtrip_(uint16).teal b/tests/integration/teal/roundtrip/app_roundtrip_(uint16).teal index 57d92721d..ec95bd989 100644 --- a/tests/integration/teal/roundtrip/app_roundtrip_(uint16).teal +++ b/tests/integration/teal/roundtrip/app_roundtrip_(uint16).teal @@ -4,7 +4,7 @@ store 2 load 2 callsub roundtripper_1 store 1 -byte 0x151F7C75 +byte 0x151f7c75 load 1 concat log diff --git a/tests/integration/teal/roundtrip/app_roundtrip_(uint16,uint8,byte).teal b/tests/integration/teal/roundtrip/app_roundtrip_(uint16,uint8,byte).teal index 4af961953..0186d716b 100644 --- a/tests/integration/teal/roundtrip/app_roundtrip_(uint16,uint8,byte).teal +++ b/tests/integration/teal/roundtrip/app_roundtrip_(uint16,uint8,byte).teal @@ -4,7 +4,7 @@ store 4 load 4 callsub roundtripper_1 store 3 -byte 0x151F7C75 +byte 0x151f7c75 load 3 concat log diff --git a/tests/integration/teal/roundtrip/app_roundtrip_(uint32).teal b/tests/integration/teal/roundtrip/app_roundtrip_(uint32).teal index e8f3179f1..e7f7c8437 100644 --- a/tests/integration/teal/roundtrip/app_roundtrip_(uint32).teal +++ b/tests/integration/teal/roundtrip/app_roundtrip_(uint32).teal @@ -4,7 +4,7 @@ store 2 load 2 callsub roundtripper_1 store 1 -byte 0x151F7C75 +byte 0x151f7c75 load 1 concat log diff --git a/tests/integration/teal/roundtrip/app_roundtrip_(uint32,uint16,uint8).teal b/tests/integration/teal/roundtrip/app_roundtrip_(uint32,uint16,uint8).teal index 0194d3410..d0e0db70d 100644 --- a/tests/integration/teal/roundtrip/app_roundtrip_(uint32,uint16,uint8).teal +++ b/tests/integration/teal/roundtrip/app_roundtrip_(uint32,uint16,uint8).teal @@ -4,7 +4,7 @@ store 4 load 4 callsub roundtripper_1 store 3 -byte 0x151F7C75 +byte 0x151f7c75 load 3 concat log diff --git a/tests/integration/teal/roundtrip/app_roundtrip_(uint64).teal b/tests/integration/teal/roundtrip/app_roundtrip_(uint64).teal index 2ef9adcec..d04d1c0ae 100644 --- a/tests/integration/teal/roundtrip/app_roundtrip_(uint64).teal +++ b/tests/integration/teal/roundtrip/app_roundtrip_(uint64).teal @@ -4,7 +4,7 @@ store 2 load 2 callsub roundtripper_1 store 1 -byte 0x151F7C75 +byte 0x151f7c75 load 1 concat log diff --git a/tests/integration/teal/roundtrip/app_roundtrip_(uint64,uint32,uint16).teal b/tests/integration/teal/roundtrip/app_roundtrip_(uint64,uint32,uint16).teal index 7e33bb6ac..c47b327be 100644 --- a/tests/integration/teal/roundtrip/app_roundtrip_(uint64,uint32,uint16).teal +++ b/tests/integration/teal/roundtrip/app_roundtrip_(uint64,uint32,uint16).teal @@ -4,7 +4,7 @@ store 4 load 4 callsub roundtripper_1 store 3 -byte 0x151F7C75 +byte 0x151f7c75 load 3 concat log diff --git a/tests/integration/teal/roundtrip/app_roundtrip_(uint8).teal b/tests/integration/teal/roundtrip/app_roundtrip_(uint8).teal index 2a4c05465..4e2b05add 100644 --- a/tests/integration/teal/roundtrip/app_roundtrip_(uint8).teal +++ b/tests/integration/teal/roundtrip/app_roundtrip_(uint8).teal @@ -4,7 +4,7 @@ store 2 load 2 callsub roundtripper_1 store 1 -byte 0x151F7C75 +byte 0x151f7c75 load 1 concat log 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 8f7115164..c42b55cfc 100644 --- a/tests/integration/teal/roundtrip/app_roundtrip_(uint8,byte,bool).teal +++ b/tests/integration/teal/roundtrip/app_roundtrip_(uint8,byte,bool).teal @@ -4,10 +4,10 @@ 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) +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 diff --git a/tests/integration/teal/roundtrip/app_roundtrip_address.teal b/tests/integration/teal/roundtrip/app_roundtrip_address.teal index 0bc930d45..1a621ddc5 100644 --- a/tests/integration/teal/roundtrip/app_roundtrip_address.teal +++ b/tests/integration/teal/roundtrip/app_roundtrip_address.teal @@ -4,7 +4,7 @@ store 1 load 1 callsub roundtripper_2 store 0 -byte 0x151F7C75 +byte 0x151f7c75 load 0 concat log diff --git a/tests/integration/teal/roundtrip/app_roundtrip_address[]_<10>.teal b/tests/integration/teal/roundtrip/app_roundtrip_address[]_<10>.teal index c406dcc4c..5667061f6 100644 --- a/tests/integration/teal/roundtrip/app_roundtrip_address[]_<10>.teal +++ b/tests/integration/teal/roundtrip/app_roundtrip_address[]_<10>.teal @@ -4,7 +4,7 @@ store 1 load 1 callsub roundtripper_3 store 0 -byte 0x151F7C75 +byte 0x151f7c75 load 0 concat log diff --git a/tests/integration/teal/roundtrip/app_roundtrip_bool.teal b/tests/integration/teal/roundtrip/app_roundtrip_bool.teal index bc91494eb..2f6390409 100644 --- a/tests/integration/teal/roundtrip/app_roundtrip_bool.teal +++ b/tests/integration/teal/roundtrip/app_roundtrip_bool.teal @@ -8,7 +8,7 @@ store 1 load 1 callsub roundtripper_1 store 0 -byte 0x151F7C75 +byte 0x151f7c75 load 0 concat log diff --git a/tests/integration/teal/roundtrip/app_roundtrip_bool[1].teal b/tests/integration/teal/roundtrip/app_roundtrip_bool[1].teal index 399bcf910..c79af3556 100644 --- a/tests/integration/teal/roundtrip/app_roundtrip_bool[1].teal +++ b/tests/integration/teal/roundtrip/app_roundtrip_bool[1].teal @@ -4,7 +4,7 @@ store 1 load 1 callsub roundtripper_2 store 0 -byte 0x151F7C75 +byte 0x151f7c75 load 0 concat log diff --git a/tests/integration/teal/roundtrip/app_roundtrip_bool[3][]_<11>.teal b/tests/integration/teal/roundtrip/app_roundtrip_bool[3][]_<11>.teal index 0c8f9c611..04d6e6153 100644 --- a/tests/integration/teal/roundtrip/app_roundtrip_bool[3][]_<11>.teal +++ b/tests/integration/teal/roundtrip/app_roundtrip_bool[3][]_<11>.teal @@ -4,7 +4,7 @@ store 1 load 1 callsub roundtripper_3 store 0 -byte 0x151F7C75 +byte 0x151f7c75 load 0 concat log diff --git a/tests/integration/teal/roundtrip/app_roundtrip_bool[42].teal b/tests/integration/teal/roundtrip/app_roundtrip_bool[42].teal index c0026b483..9d1fcc1eb 100644 --- a/tests/integration/teal/roundtrip/app_roundtrip_bool[42].teal +++ b/tests/integration/teal/roundtrip/app_roundtrip_bool[42].teal @@ -4,7 +4,7 @@ store 1 load 1 callsub roundtripper_2 store 0 -byte 0x151F7C75 +byte 0x151f7c75 load 0 concat log diff --git a/tests/integration/teal/roundtrip/app_roundtrip_bool[]_<0>.teal b/tests/integration/teal/roundtrip/app_roundtrip_bool[]_<0>.teal index 213f6c9af..710b6a859 100644 --- a/tests/integration/teal/roundtrip/app_roundtrip_bool[]_<0>.teal +++ b/tests/integration/teal/roundtrip/app_roundtrip_bool[]_<0>.teal @@ -4,7 +4,7 @@ store 1 load 1 callsub roundtripper_1 store 0 -byte 0x151F7C75 +byte 0x151f7c75 load 0 concat log diff --git a/tests/integration/teal/roundtrip/app_roundtrip_bool[]_<1>.teal b/tests/integration/teal/roundtrip/app_roundtrip_bool[]_<1>.teal index 3371ff0cd..9f38b5965 100644 --- a/tests/integration/teal/roundtrip/app_roundtrip_bool[]_<1>.teal +++ b/tests/integration/teal/roundtrip/app_roundtrip_bool[]_<1>.teal @@ -4,7 +4,7 @@ store 1 load 1 callsub roundtripper_2 store 0 -byte 0x151F7C75 +byte 0x151f7c75 load 0 concat log diff --git a/tests/integration/teal/roundtrip/app_roundtrip_bool[]_<42>.teal b/tests/integration/teal/roundtrip/app_roundtrip_bool[]_<42>.teal index 8b4204e83..e15d49686 100644 --- a/tests/integration/teal/roundtrip/app_roundtrip_bool[]_<42>.teal +++ b/tests/integration/teal/roundtrip/app_roundtrip_bool[]_<42>.teal @@ -4,7 +4,7 @@ store 1 load 1 callsub roundtripper_2 store 0 -byte 0x151F7C75 +byte 0x151f7c75 load 0 concat log diff --git a/tests/integration/teal/roundtrip/app_roundtrip_byte.teal b/tests/integration/teal/roundtrip/app_roundtrip_byte.teal index 851809b8e..b3d294763 100644 --- a/tests/integration/teal/roundtrip/app_roundtrip_byte.teal +++ b/tests/integration/teal/roundtrip/app_roundtrip_byte.teal @@ -6,7 +6,7 @@ store 1 load 1 callsub roundtripper_1 store 0 -byte 0x151F7C75 +byte 0x151f7c75 load 0 concat log diff --git a/tests/integration/teal/roundtrip/app_roundtrip_string_<0>.teal b/tests/integration/teal/roundtrip/app_roundtrip_string_<0>.teal index 5ae3edff2..6ab75fba8 100644 --- a/tests/integration/teal/roundtrip/app_roundtrip_string_<0>.teal +++ b/tests/integration/teal/roundtrip/app_roundtrip_string_<0>.teal @@ -4,7 +4,7 @@ store 1 load 1 callsub roundtripper_1 store 0 -byte 0x151F7C75 +byte 0x151f7c75 load 0 concat log diff --git a/tests/integration/teal/roundtrip/app_roundtrip_string_<13>.teal b/tests/integration/teal/roundtrip/app_roundtrip_string_<13>.teal index 6350008aa..086b2b970 100644 --- a/tests/integration/teal/roundtrip/app_roundtrip_string_<13>.teal +++ b/tests/integration/teal/roundtrip/app_roundtrip_string_<13>.teal @@ -4,7 +4,7 @@ store 1 load 1 callsub roundtripper_1 store 0 -byte 0x151F7C75 +byte 0x151f7c75 load 0 concat log diff --git a/tests/integration/teal/roundtrip/app_roundtrip_string_<1>.teal b/tests/integration/teal/roundtrip/app_roundtrip_string_<1>.teal index 779c6fc1d..a97b99d60 100644 --- a/tests/integration/teal/roundtrip/app_roundtrip_string_<1>.teal +++ b/tests/integration/teal/roundtrip/app_roundtrip_string_<1>.teal @@ -4,7 +4,7 @@ store 1 load 1 callsub roundtripper_1 store 0 -byte 0x151F7C75 +byte 0x151f7c75 load 0 concat log diff --git a/tests/integration/teal/roundtrip/app_roundtrip_uint16.teal b/tests/integration/teal/roundtrip/app_roundtrip_uint16.teal index dc7d82530..81e911ee3 100644 --- a/tests/integration/teal/roundtrip/app_roundtrip_uint16.teal +++ b/tests/integration/teal/roundtrip/app_roundtrip_uint16.teal @@ -6,7 +6,7 @@ store 1 load 1 callsub roundtripper_1 store 0 -byte 0x151F7C75 +byte 0x151f7c75 load 0 concat log diff --git a/tests/integration/teal/roundtrip/app_roundtrip_uint32.teal b/tests/integration/teal/roundtrip/app_roundtrip_uint32.teal index a46200138..ff6b2cb0a 100644 --- a/tests/integration/teal/roundtrip/app_roundtrip_uint32.teal +++ b/tests/integration/teal/roundtrip/app_roundtrip_uint32.teal @@ -6,7 +6,7 @@ store 1 load 1 callsub roundtripper_1 store 0 -byte 0x151F7C75 +byte 0x151f7c75 load 0 concat log diff --git a/tests/integration/teal/roundtrip/app_roundtrip_uint64.teal b/tests/integration/teal/roundtrip/app_roundtrip_uint64.teal index 9a9169df4..c20d6fec7 100644 --- a/tests/integration/teal/roundtrip/app_roundtrip_uint64.teal +++ b/tests/integration/teal/roundtrip/app_roundtrip_uint64.teal @@ -5,7 +5,7 @@ store 1 load 1 callsub roundtripper_1 store 0 -byte 0x151F7C75 +byte 0x151f7c75 load 0 concat log diff --git a/tests/integration/teal/roundtrip/app_roundtrip_uint64[1].teal b/tests/integration/teal/roundtrip/app_roundtrip_uint64[1].teal index 3252a6442..11a1cc180 100644 --- a/tests/integration/teal/roundtrip/app_roundtrip_uint64[1].teal +++ b/tests/integration/teal/roundtrip/app_roundtrip_uint64[1].teal @@ -4,7 +4,7 @@ store 1 load 1 callsub roundtripper_2 store 0 -byte 0x151F7C75 +byte 0x151f7c75 load 0 concat log diff --git a/tests/integration/teal/roundtrip/app_roundtrip_uint64[42].teal b/tests/integration/teal/roundtrip/app_roundtrip_uint64[42].teal index 6afe66a2f..1f780d0f4 100644 --- a/tests/integration/teal/roundtrip/app_roundtrip_uint64[42].teal +++ b/tests/integration/teal/roundtrip/app_roundtrip_uint64[42].teal @@ -4,7 +4,7 @@ store 1 load 1 callsub roundtripper_2 store 0 -byte 0x151F7C75 +byte 0x151f7c75 load 0 concat log diff --git a/tests/integration/teal/roundtrip/app_roundtrip_uint64[]_<0>.teal b/tests/integration/teal/roundtrip/app_roundtrip_uint64[]_<0>.teal index 213f6c9af..710b6a859 100644 --- a/tests/integration/teal/roundtrip/app_roundtrip_uint64[]_<0>.teal +++ b/tests/integration/teal/roundtrip/app_roundtrip_uint64[]_<0>.teal @@ -4,7 +4,7 @@ store 1 load 1 callsub roundtripper_1 store 0 -byte 0x151F7C75 +byte 0x151f7c75 load 0 concat log diff --git a/tests/integration/teal/roundtrip/app_roundtrip_uint64[]_<1>.teal b/tests/integration/teal/roundtrip/app_roundtrip_uint64[]_<1>.teal index 70c2016c9..570b2cb20 100644 --- a/tests/integration/teal/roundtrip/app_roundtrip_uint64[]_<1>.teal +++ b/tests/integration/teal/roundtrip/app_roundtrip_uint64[]_<1>.teal @@ -4,7 +4,7 @@ store 1 load 1 callsub roundtripper_2 store 0 -byte 0x151F7C75 +byte 0x151f7c75 load 0 concat log diff --git a/tests/integration/teal/roundtrip/app_roundtrip_uint64[]_<42>.teal b/tests/integration/teal/roundtrip/app_roundtrip_uint64[]_<42>.teal index d2ae51113..869f062b1 100644 --- a/tests/integration/teal/roundtrip/app_roundtrip_uint64[]_<42>.teal +++ b/tests/integration/teal/roundtrip/app_roundtrip_uint64[]_<42>.teal @@ -4,7 +4,7 @@ store 1 load 1 callsub roundtripper_2 store 0 -byte 0x151F7C75 +byte 0x151f7c75 load 0 concat log diff --git a/tests/integration/teal/roundtrip/app_roundtrip_uint8.teal b/tests/integration/teal/roundtrip/app_roundtrip_uint8.teal index 851809b8e..b3d294763 100644 --- a/tests/integration/teal/roundtrip/app_roundtrip_uint8.teal +++ b/tests/integration/teal/roundtrip/app_roundtrip_uint8.teal @@ -6,7 +6,7 @@ store 1 load 1 callsub roundtripper_1 store 0 -byte 0x151F7C75 +byte 0x151f7c75 load 0 concat log diff --git a/tests/unit/teal/abi/app_fn_0arg_uint64_ret.teal b/tests/unit/teal/abi/app_fn_0arg_uint64_ret.teal index 54a1a0a58..858410b51 100644 --- a/tests/unit/teal/abi/app_fn_0arg_uint64_ret.teal +++ b/tests/unit/teal/abi/app_fn_0arg_uint64_ret.teal @@ -1,7 +1,7 @@ #pragma version 6 callsub fn0arguint64ret_0 store 1 -byte 0x151F7C75 +byte 0x151f7c75 load 1 itob concat diff --git a/tests/unit/teal/abi/app_fn_1arg_1ret.teal b/tests/unit/teal/abi/app_fn_1arg_1ret.teal index 6ae354dac..f9d4574c7 100644 --- a/tests/unit/teal/abi/app_fn_1arg_1ret.teal +++ b/tests/unit/teal/abi/app_fn_1arg_1ret.teal @@ -5,7 +5,7 @@ store 3 load 3 callsub fn1arg1ret_0 store 2 -byte 0x151F7C75 +byte 0x151f7c75 load 2 itob concat 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 index bae9169fc..791abfc8a 100644 --- a/tests/unit/teal/abi/app_fn_1tt_arg_uint64_ret.teal +++ b/tests/unit/teal/abi/app_fn_1tt_arg_uint64_ret.teal @@ -2,7 +2,7 @@ txna ApplicationArgs 0 callsub fn1ttarguint64ret_0 store 2 -byte 0x151F7C75 +byte 0x151f7c75 load 2 itob concat diff --git a/tests/unit/teal/abi/app_fn_2mixed_arg_1ret.teal b/tests/unit/teal/abi/app_fn_2mixed_arg_1ret.teal index ed0ce47e3..51c058bd6 100644 --- a/tests/unit/teal/abi/app_fn_2mixed_arg_1ret.teal +++ b/tests/unit/teal/abi/app_fn_2mixed_arg_1ret.teal @@ -8,7 +8,7 @@ load 4 int 5 callsub fn2mixedarg1ret_0 store 3 -byte 0x151F7C75 +byte 0x151f7c75 load 3 itob concat 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 a19e893be..3d844d44d 100644 --- a/tests/unit/teal/user_guide/user_guide_snippet_ABIReturnSubroutine.teal +++ b/tests/unit/teal/user_guide/user_guide_snippet_ABIReturnSubroutine.teal @@ -4,7 +4,7 @@ store 0 // 0: x load 0 // [x] callsub abisum_0 store 1 -byte 0x151F7C75 +byte 0x151f7c75 load 1 itob concat @@ -20,8 +20,8 @@ store 3 // 3: 0 int 0 // [0] store 4 // 4: 0 abisum_0_l1: // [] -load 4 -load 2 +load 4 +load 2 int 0 // [0, x, 0] extract_uint16 // [0, len(x)] store 6 // 6: len(x) From 34ee8dc52b20ada77b03a75fa44aac4b9c8135bf Mon Sep 17 00:00:00 2001 From: Hang Su Date: Thu, 26 May 2022 11:33:46 -0400 Subject: [PATCH 160/188] review comments partly --- pyteal/ast/router.py | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/pyteal/ast/router.py b/pyteal/ast/router.py index 0b4707de4..92a6036b6 100644 --- a/pyteal/ast/router.py +++ b/pyteal/ast/router.py @@ -2,10 +2,11 @@ from typing import cast, Optional from enum import IntFlag -import algosdk.abi as sdk_abi +from algosdk import abi as sdk_abi +from algosdk import encoding from pyteal.config import METHOD_ARG_NUM_LIMIT -from pyteal.errors import TealInputError, TealInternalError +from pyteal.errors import TealInputError from pyteal.types import TealType from pyteal.compiler.compiler import compileTeal, DEFAULT_TEAL_VERSION, OptimizeOptions from pyteal.ir.ops import Mode @@ -263,7 +264,7 @@ class Router: def __init__( self, name: str, - bare_calls: OnCompleteActions, + bare_calls: OnCompleteActions = None, ) -> None: """ Args: @@ -274,9 +275,11 @@ def __init__( self.name: str = name self.categorized_approval_ast = CategorizedCondNodes() self.categorized_clear_state_ast = CategorizedCondNodes() - self.added_method_sig: set[str] = set() - self.__add_bare_call(bare_calls) + self.method_sig_to_selector: dict[str, bytes] = dict() + self.method_selector_to_sig: dict[bytes, str] = dict() + if bare_calls: + self.__add_bare_call(bare_calls) @staticmethod def _wrap_handler( @@ -402,8 +405,6 @@ def _wrap_handler( def __add_bare_call(self, oc_actions: OnCompleteActions) -> None: action_type = Expr | SubroutineFnWrapper | ABIReturnSubroutine - if oc_actions.is_empty(): - raise TealInternalError("the OnCompleteActions is empty.") cs_calls, approval_calls = oc_actions._clear_state_n_other_oc() if cs_calls.on_call: on_call = cast(action_type, cs_calls.on_call) @@ -449,9 +450,17 @@ def add_method_handler( raise TealInputError( f"registered method {method_signature} is never executed" ) - if method_signature in self.added_method_sig: + method_selector = encoding.checksum(bytes(method_signature, "utf-8"))[:4] + + if method_signature in self.method_sig_to_selector: raise TealInputError(f"re-registering method {method_signature} detected") - self.added_method_sig.add(method_signature) + if method_selector in self.method_selector_to_sig: + raise TealInputError( + f"re-registering method {method_signature} has hash collision " + f"with {self.method_selector_to_sig[method_selector]}" + ) + self.method_sig_to_selector[method_signature] = method_selector + self.method_selector_to_sig[method_selector] = method_signature wrapped = Router._wrap_handler(True, method_call) @@ -511,7 +520,9 @@ def contract_construct(self) -> sdk_abi.Contract: approval program's method signatures and `self.name`. """ method_collections = [ - sdk_abi.Method.from_signature(sig) for sig in self.added_method_sig + sdk_abi.Method.from_signature(sig) + for sig in self.method_sig_to_selector + if isinstance(sig, str) ] return sdk_abi.Contract(self.name, method_collections) From 94d618bea17637a046c214886234c20980d9ee74 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Thu, 26 May 2022 11:54:53 -0400 Subject: [PATCH 161/188] OnCompleteActions -> BareCallActions --- pyteal/__init__.pyi | 2 +- pyteal/ast/__init__.py | 4 ++-- pyteal/ast/router.py | 8 ++++---- pyteal/ast/router_test.py | 4 ++-- pyteal/compiler/compiler_test.py | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/pyteal/__init__.pyi b/pyteal/__init__.pyi index a85e8278b..4512db3c5 100644 --- a/pyteal/__init__.pyi +++ b/pyteal/__init__.pyi @@ -43,6 +43,7 @@ __all__ = [ "AssetHolding", "AssetParam", "Balance", + "BareCallActions", "BinaryExpr", "BitLen", "BitwiseAnd", @@ -141,7 +142,6 @@ __all__ = [ "Not", "OnComplete", "OnCompleteAction", - "OnCompleteActions", "Op", "OpUp", "OpUpMode", diff --git a/pyteal/ast/__init__.py b/pyteal/ast/__init__.py index e02be6312..db066e760 100644 --- a/pyteal/ast/__init__.py +++ b/pyteal/ast/__init__.py @@ -143,7 +143,7 @@ CallConfig, CallConfigs, OnCompleteAction, - OnCompleteActions, + BareCallActions, ) # abi @@ -290,7 +290,7 @@ "CallConfig", "CallConfigs", "OnCompleteAction", - "OnCompleteActions", + "BareCallActions", "abi", "EcdsaCurve", "EcdsaVerify", diff --git a/pyteal/ast/router.py b/pyteal/ast/router.py index 92a6036b6..9bd58add3 100644 --- a/pyteal/ast/router.py +++ b/pyteal/ast/router.py @@ -182,7 +182,7 @@ def is_empty(self) -> bool: @dataclass(frozen=True) -class OnCompleteActions: +class BareCallActions: """ OnCompletion Actions keep track of bare-call registrations to all OnCompletion cases. """ @@ -219,7 +219,7 @@ def is_empty(self) -> bool: return True -OnCompleteActions.__module__ = "pyteal" +BareCallActions.__module__ = "pyteal" @dataclass(frozen=True) @@ -264,7 +264,7 @@ class Router: def __init__( self, name: str, - bare_calls: OnCompleteActions = None, + bare_calls: BareCallActions = None, ) -> None: """ Args: @@ -403,7 +403,7 @@ def _wrap_handler( Approve(), ) - def __add_bare_call(self, oc_actions: OnCompleteActions) -> None: + def __add_bare_call(self, oc_actions: BareCallActions) -> None: action_type = Expr | SubroutineFnWrapper | ABIReturnSubroutine cs_calls, approval_calls = oc_actions._clear_state_n_other_oc() if cs_calls.on_call: diff --git a/pyteal/ast/router_test.py b/pyteal/ast/router_test.py index 4649057f7..3e067e929 100644 --- a/pyteal/ast/router_test.py +++ b/pyteal/ast/router_test.py @@ -241,7 +241,7 @@ def test_add_method(): ] router = pt.Router( "routerForMethodTest", - pt.OnCompleteActions(clear_state=pt.OnCompleteAction.call_only(pt.Approve())), + pt.BareCallActions(clear_state=pt.OnCompleteAction.call_only(pt.Approve())), ) for subroutine in normal_subroutine: with pytest.raises(pt.TealInputError) as must_be_abi: @@ -480,7 +480,7 @@ def test_contract_json_obj(): filter(lambda x: isinstance(x, pt.ABIReturnSubroutine), GOOD_SUBROUTINE_CASES) ) contract_name = "contract_name" - on_complete_actions = pt.OnCompleteActions( + on_complete_actions = pt.BareCallActions( clear_state=pt.OnCompleteAction.call_only(safe_clear_state_delete) ) router = pt.Router(contract_name, on_complete_actions) diff --git a/pyteal/compiler/compiler_test.py b/pyteal/compiler/compiler_test.py index 7c6c80fff..0c02acd79 100644 --- a/pyteal/compiler/compiler_test.py +++ b/pyteal/compiler/compiler_test.py @@ -2196,7 +2196,7 @@ def access_b4_store(magic_num: pt.abi.Uint64, *, output: pt.abi.Uint64): def test_router_app(): - on_completion_actions = pt.OnCompleteActions( + on_completion_actions = pt.BareCallActions( clear_state=pt.OnCompleteAction.call_only(pt.Approve()) ) From f7185b9c36bd5ef30ba6422d253192dcef905796 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Thu, 26 May 2022 12:30:35 -0400 Subject: [PATCH 162/188] disclaimer change, rename CallConfigs -> MethodConfig --- pyteal/__init__.pyi | 2 +- pyteal/ast/__init__.py | 4 ++-- pyteal/ast/router.py | 23 ++++++++++++----------- pyteal/ast/router_test.py | 2 +- pyteal/compiler/compiler_test.py | 7 ++++--- 5 files changed, 20 insertions(+), 18 deletions(-) diff --git a/pyteal/__init__.pyi b/pyteal/__init__.pyi index 4512db3c5..b7e7c34a2 100644 --- a/pyteal/__init__.pyi +++ b/pyteal/__init__.pyi @@ -71,7 +71,6 @@ __all__ = [ "BytesXor", "BytesZero", "CallConfig", - "CallConfigs", "CompileOptions", "Concat", "Cond", @@ -128,6 +127,7 @@ __all__ = [ "METHOD_ARG_NUM_LIMIT", "MIN_TEAL_VERSION", "MaybeValue", + "MethodConfig", "MethodSignature", "MinBalance", "Minus", diff --git a/pyteal/ast/__init__.py b/pyteal/ast/__init__.py index db066e760..68c6fd149 100644 --- a/pyteal/ast/__init__.py +++ b/pyteal/ast/__init__.py @@ -141,7 +141,7 @@ from pyteal.ast.router import ( Router, CallConfig, - CallConfigs, + MethodConfig, OnCompleteAction, BareCallActions, ) @@ -288,7 +288,7 @@ "Continue", "Router", "CallConfig", - "CallConfigs", + "MethodConfig", "OnCompleteAction", "BareCallActions", "abi", diff --git a/pyteal/ast/router.py b/pyteal/ast/router.py index 9bd58add3..4e5c87b83 100644 --- a/pyteal/ast/router.py +++ b/pyteal/ast/router.py @@ -34,13 +34,8 @@ # ABI-Router is still taking shape and is subject to backwards incompatible # # changes. # # # -# * For ARC-4 Application definition, feel encouraged to use ABI-Router. # -# Expect a best-effort attempt to minimize backwards incompatible changes # -# along with a migration path. # -# # -# * USE AT YOUR OWN RISK. # -# Based on feedback, the API and usage patterns will change more freely and # -# with less effort to provide migration paths. # +# Based on feedback, the API and usage patterns are likely to change. # +# Expect migration issues. # ################################################################################ @@ -67,7 +62,7 @@ class CallConfig(IntFlag): @dataclass(frozen=True) -class CallConfigs: +class MethodConfig: """ CallConfigs keep track of one method registration's CallConfigs for all OnComplete cases. @@ -410,7 +405,7 @@ def __add_bare_call(self, oc_actions: BareCallActions) -> None: on_call = cast(action_type, cs_calls.on_call) wrapped = Router._wrap_handler(False, on_call) self.categorized_clear_state_ast.bare_calls.append( - CondNode(Int(1), wrapped) + CondNode(Txn.application_args.length() == Int(0), wrapped) ) if cs_calls.on_create: on_create = cast(action_type, cs_calls.on_create) @@ -423,7 +418,13 @@ def __add_bare_call(self, oc_actions: BareCallActions) -> None: on_call = cast(action_type, approval_bac.on_call) wrapped = Router._wrap_handler(False, on_call) self.categorized_approval_ast.bare_calls.append( - CondNode(Txn.on_completion() == oc, wrapped) + CondNode( + And( + Txn.application_args.length() == Int(0), + Txn.on_completion() == oc, + ), + wrapped, + ) ) if approval_bac.on_create: on_create = cast(action_type, approval_bac.on_create) @@ -439,7 +440,7 @@ def add_method_handler( self, method_call: ABIReturnSubroutine, overriding_name: str = None, - call_configs: CallConfigs = CallConfigs(), + call_configs: MethodConfig = MethodConfig(), ) -> None: if not isinstance(method_call, ABIReturnSubroutine): raise TealInputError( diff --git a/pyteal/ast/router_test.py b/pyteal/ast/router_test.py index 3e067e929..8011119cf 100644 --- a/pyteal/ast/router_test.py +++ b/pyteal/ast/router_test.py @@ -258,7 +258,7 @@ def test_add_method(): ) oc_names = [camel_to_snake(oc.name) for oc in on_complete_set] for call_config in full_perm_call_configs_for_ocs: - method_call_configs: pt.CallConfigs = pt.CallConfigs( + method_call_configs: pt.MethodConfig = pt.MethodConfig( **dict(zip(oc_names, call_config)) ) if method_call_configs.is_never(): diff --git a/pyteal/compiler/compiler_test.py b/pyteal/compiler/compiler_test.py index 0c02acd79..b7ca28a81 100644 --- a/pyteal/compiler/compiler_test.py +++ b/pyteal/compiler/compiler_test.py @@ -2609,11 +2609,12 @@ def all_laid_to_args( assert expected_ap == actual_ap_compiled expected_csp = """#pragma version 6 -intcblock 1 -intc_0 // 1 +txn NumAppArgs +pushint 0 // 0 +== bnz main_l2 err main_l2: -intc_0 // 1 +pushint 1 // 1 return""".strip() assert expected_csp == actual_csp_compiled From 2958bc049007e64d31f78ec491efb2e3de1f812d Mon Sep 17 00:00:00 2001 From: Hang Su Date: Thu, 26 May 2022 13:51:36 -0400 Subject: [PATCH 163/188] disclaimer move to docstring --- pyteal/ast/router.py | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/pyteal/ast/router.py b/pyteal/ast/router.py index 4e5c87b83..23718a8aa 100644 --- a/pyteal/ast/router.py +++ b/pyteal/ast/router.py @@ -28,17 +28,6 @@ from pyteal.ast.return_ import Approve -################################################################################ -# DISCLAIMER # -################################################################################ -# ABI-Router is still taking shape and is subject to backwards incompatible # -# changes. # -# # -# Based on feedback, the API and usage patterns are likely to change. # -# Expect migration issues. # -################################################################################ - - class CallConfig(IntFlag): """ CallConfigs: a "bitset"-like class for more fine-grained control over @@ -254,6 +243,11 @@ class Router: Class that help constructs: - a *Generalized* ARC-4 app's approval/clear-state programs - and a contract JSON object allowing for easily read and call methods in the contract + + *DISCLAIMER*: ABI-Router is still taking shape and is subject to backwards incompatible changes. + + * Based on feedback, the API and usage patterns are likely to change. + * Expect migration issues. """ def __init__( From 6f386ddce5eeb46007056250baaa024542b5a53e Mon Sep 17 00:00:00 2001 From: Hang Su Date: Thu, 26 May 2022 13:53:14 -0400 Subject: [PATCH 164/188] better naming for naming prefix --- pyteal/__init__.py | 4 ++-- pyteal/__init__.pyi | 4 ++-- pyteal/ast/abi/method_return.py | 4 ++-- pyteal/config.py | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pyteal/__init__.py b/pyteal/__init__.py index cc4e7ca76..e0b3ed3cd 100644 --- a/pyteal/__init__.py +++ b/pyteal/__init__.py @@ -20,7 +20,7 @@ from pyteal.config import ( MAX_GROUP_SIZE, NUM_SLOTS, - RETURN_METHOD_SELECTOR, + RETURN_HASH_PREFIX, METHOD_ARG_NUM_LIMIT, ) @@ -42,7 +42,7 @@ "TealCompileError", "MAX_GROUP_SIZE", "NUM_SLOTS", - "RETURN_METHOD_SELECTOR", + "RETURN_HASH_PREFIX", "METHOD_ARG_NUM_LIMIT", ] ) diff --git a/pyteal/__init__.pyi b/pyteal/__init__.pyi index b7e7c34a2..2ad254d0a 100644 --- a/pyteal/__init__.pyi +++ b/pyteal/__init__.pyi @@ -23,7 +23,7 @@ from pyteal.errors import ( from pyteal.config import ( MAX_GROUP_SIZE, NUM_SLOTS, - RETURN_METHOD_SELECTOR, + RETURN_HASH_PREFIX, METHOD_ARG_NUM_LIMIT, ) @@ -148,7 +148,7 @@ __all__ = [ "OptimizeOptions", "Or", "Pop", - "RETURN_METHOD_SELECTOR", + "RETURN_HASH_PREFIX", "Reject", "Return", "Router", diff --git a/pyteal/ast/abi/method_return.py b/pyteal/ast/abi/method_return.py index 5324c6344..b8058456a 100644 --- a/pyteal/ast/abi/method_return.py +++ b/pyteal/ast/abi/method_return.py @@ -4,7 +4,7 @@ from pyteal.errors import TealInputError from pyteal.ast import Expr, Log, Concat, Bytes from pyteal.ir import TealBlock, TealSimpleBlock, Op -from pyteal.config import RETURN_METHOD_SELECTOR +from pyteal.config import RETURN_HASH_PREFIX if TYPE_CHECKING: from pyteal.compiler import CompileOptions @@ -22,7 +22,7 @@ def __teal__(self, options: "CompileOptions") -> Tuple[TealBlock, TealSimpleBloc raise TealInputError( f"current version {options.version} is lower than log's min version {Op.log.min_version}" ) - return Log(Concat(Bytes(RETURN_METHOD_SELECTOR), self.arg.encode())).__teal__( + return Log(Concat(Bytes(RETURN_HASH_PREFIX), self.arg.encode())).__teal__( options ) diff --git a/pyteal/config.py b/pyteal/config.py index f905ad529..9d5b24e19 100644 --- a/pyteal/config.py +++ b/pyteal/config.py @@ -8,7 +8,7 @@ NUM_SLOTS = 256 # Method return selector in base16 -RETURN_METHOD_SELECTOR = ABI_RETURN_HASH +RETURN_HASH_PREFIX = ABI_RETURN_HASH # Method argument number limit METHOD_ARG_NUM_LIMIT = 15 From aa69b0f598dc24bba267bbaf498d5480bb1a835c Mon Sep 17 00:00:00 2001 From: Hang Su Date: Thu, 26 May 2022 16:52:27 -0400 Subject: [PATCH 165/188] new ast builder --- pyteal/__init__.pyi | 1 + pyteal/ast/__init__.py | 2 + pyteal/ast/router.py | 351 ++++++++++++++++--------------- pyteal/ast/router_test.py | 10 +- pyteal/compiler/compiler_test.py | 109 +++++++--- 5 files changed, 262 insertions(+), 211 deletions(-) diff --git a/pyteal/__init__.pyi b/pyteal/__init__.pyi index 2ad254d0a..999652df5 100644 --- a/pyteal/__init__.pyi +++ b/pyteal/__init__.pyi @@ -29,6 +29,7 @@ from pyteal.config import ( __all__ = [ "ABIReturnSubroutine", + "ASTBuilder", "AccountParam", "Add", "Addr", diff --git a/pyteal/ast/__init__.py b/pyteal/ast/__init__.py index 68c6fd149..f288bdacc 100644 --- a/pyteal/ast/__init__.py +++ b/pyteal/ast/__init__.py @@ -140,6 +140,7 @@ from pyteal.ast.ecdsa import EcdsaCurve, EcdsaVerify, EcdsaDecompress, EcdsaRecover from pyteal.ast.router import ( Router, + ASTBuilder, CallConfig, MethodConfig, OnCompleteAction, @@ -287,6 +288,7 @@ "Break", "Continue", "Router", + "ASTBuilder", "CallConfig", "MethodConfig", "OnCompleteAction", diff --git a/pyteal/ast/router.py b/pyteal/ast/router.py index 23718a8aa..fbfd7052c 100644 --- a/pyteal/ast/router.py +++ b/pyteal/ast/router.py @@ -6,7 +6,7 @@ from algosdk import encoding from pyteal.config import METHOD_ARG_NUM_LIMIT -from pyteal.errors import TealInputError +from pyteal.errors import TealInputError, TealInternalError from pyteal.types import TealType from pyteal.compiler.compiler import compileTeal, DEFAULT_TEAL_VERSION, OptimizeOptions from pyteal.ir.ops import Mode @@ -92,35 +92,56 @@ def arc4_compliant(cls): def is_arc4_compliant(self) -> bool: return self == self.arc4_compliant() - def _oc_under_call_config(self, call_config: CallConfig) -> list[EnumInt]: - if not isinstance(call_config, CallConfig): - raise TealInputError( - "generate condition based on CallConfigs should be based on CallConfig" - ) - config_oc_pairs: list[tuple[CallConfig, EnumInt]] = [ - (self.no_op, OnComplete.NoOp), - (self.opt_in, OnComplete.OptIn), - (self.close_out, OnComplete.CloseOut), - (self.clear_state, OnComplete.ClearState), - (self.update_application, OnComplete.UpdateApplication), - (self.delete_application, OnComplete.DeleteApplication), - ] - return [ - oc - for oc_config, oc in config_oc_pairs - if (oc_config & call_config) != CallConfig.NEVER - ] - - def _has_clear_state(self, cc: CallConfig) -> bool: - return (self.clear_state & cc) != CallConfig.NEVER + @staticmethod + def __condition_under_config(cc: CallConfig) -> Expr | int: + match cc: + case CallConfig.NEVER: + return 0 + case CallConfig.CALL: + return Txn.application_id() != Int(0) + case CallConfig.CREATE: + return Txn.application_id() == Int(0) + case CallConfig.ALL: + return 1 + case _: + raise TealInternalError("CallConfig scope exceeding!") + + def approval_cond(self) -> Expr | int: + config_oc_pairs: dict[CallConfig, EnumInt] = { + self.no_op: OnComplete.NoOp, + self.opt_in: OnComplete.OptIn, + self.close_out: OnComplete.CloseOut, + self.update_application: OnComplete.UpdateApplication, + self.delete_application: OnComplete.DeleteApplication, + } + if all(config == CallConfig.NEVER for config in config_oc_pairs): + return 0 + elif all(config == CallConfig.ALL for config in config_oc_pairs): + return 1 + else: + cond_list = [] + for config in config_oc_pairs: + config_cond = self.__condition_under_config(config) + match config_cond: + case Expr(): + cond_list.append( + And( + Txn.on_completion() == config_oc_pairs[config], + config_cond, + ) + ) + case 1: + cond_list.append(Txn.on_completion() == config_oc_pairs[config]) + case 0: + continue + case _: + raise TealInternalError( + "condition_under_config scope exceeding!" + ) + return Or(*cond_list) - def _approval_program_oc(self, cc: CallConfig) -> list[EnumInt]: - return list( - filter( - lambda x: str(x) != str(OnComplete.ClearState), - self._oc_under_call_config(cc), - ) - ) + def clear_state_cond(self) -> Expr | int: + return self.__condition_under_config(self.clear_state) @dataclass(frozen=True) @@ -184,17 +205,6 @@ class BareCallActions: kw_only=True, default=OnCompleteAction.never() ) - def _clear_state_n_other_oc( - self, - ) -> tuple[OnCompleteAction, dict[EnumInt, OnCompleteAction]]: - return self.clear_state, { - OnComplete.CloseOut: self.close_out, - OnComplete.DeleteApplication: self.delete_application, - OnComplete.NoOp: self.no_op, - OnComplete.OptIn: self.opt_in, - OnComplete.UpdateApplication: self.update_application, - } - def is_empty(self) -> bool: for action_field in fields(self): action: OnCompleteAction = getattr(self, action_field.name) @@ -202,6 +212,54 @@ def is_empty(self) -> bool: return False return True + def approval_construction(self) -> Optional[Expr]: + oc_action_pair: dict[EnumInt, OnCompleteAction] = { + OnComplete.NoOp: self.no_op, + OnComplete.OptIn: self.opt_in, + OnComplete.CloseOut: self.close_out, + OnComplete.UpdateApplication: self.update_application, + OnComplete.DeleteApplication: self.delete_application, + } + if all(oca.is_empty() for oca in oc_action_pair.values()): + return None + conditions_n_branches: list[CondNode] = list() + for oc, oca in oc_action_pair.items(): + if oca.on_call: + conditions_n_branches.append( + CondNode( + And(Txn.on_completion() == oc, Txn.application_id() != Int(0)), + ASTBuilder.wrap_handler(False, oca.on_call), + ) + ) + if oca.on_create: + conditions_n_branches.append( + CondNode( + And(Txn.on_completion() == oc, Txn.application_id() == Int(0)), + ASTBuilder.wrap_handler(False, oca.on_create), + ) + ) + return Cond(*[[n.condition, n.branch] for n in conditions_n_branches]) + + def clear_state_construction(self) -> Optional[Expr]: + if self.clear_state.is_empty(): + return None + conditions_n_branches: list[CondNode] = list() + if self.clear_state.on_call: + conditions_n_branches.append( + CondNode( + Txn.application_id() != Int(0), + ASTBuilder.wrap_handler(False, self.clear_state.on_call), + ) + ) + if self.clear_state.on_create: + conditions_n_branches.append( + CondNode( + Txn.application_id() == Int(0), + ASTBuilder.wrap_handler(False, self.clear_state.on_create), + ) + ) + return Cond(*[[n.condition, n.branch] for n in conditions_n_branches]) + BareCallActions.__module__ = "pyteal" @@ -216,62 +274,11 @@ class CondNode: @dataclass -class CategorizedCondNodes: - method_calls_create: list[CondNode] = field(default_factory=list) - bare_calls_create: list[CondNode] = field(default_factory=list) - method_calls: list[CondNode] = field(default_factory=list) - bare_calls: list[CondNode] = field(default_factory=list) - - def program_construction(self) -> Expr: - concatenated_ast = ( - self.method_calls_create - + self.bare_calls_create - + self.method_calls - + self.bare_calls - ) - if not concatenated_ast: - raise TealInputError("ABIRouter: Cannot build program with an empty AST") - program: Cond = Cond(*[[n.condition, n.branch] for n in concatenated_ast]) - return program - - -CategorizedCondNodes.__module__ = "pyteal" - - -class Router: - """ - Class that help constructs: - - a *Generalized* ARC-4 app's approval/clear-state programs - - and a contract JSON object allowing for easily read and call methods in the contract - - *DISCLAIMER*: ABI-Router is still taking shape and is subject to backwards incompatible changes. - - * Based on feedback, the API and usage patterns are likely to change. - * Expect migration issues. - """ - - def __init__( - self, - name: str, - bare_calls: BareCallActions = None, - ) -> None: - """ - Args: - name: the name of the smart contract, used in the JSON object. - bare_calls: the bare app call registered for each on_completion. - """ - - self.name: str = name - self.categorized_approval_ast = CategorizedCondNodes() - self.categorized_clear_state_ast = CategorizedCondNodes() - - self.method_sig_to_selector: dict[str, bytes] = dict() - self.method_selector_to_sig: dict[bytes, str] = dict() - if bare_calls: - self.__add_bare_call(bare_calls) +class ASTBuilder: + conditions_n_branches: list[CondNode] = field(default_factory=list) @staticmethod - def _wrap_handler( + def wrap_handler( is_method_call: bool, handler: ABIReturnSubroutine | SubroutineFnWrapper | Expr ) -> Expr: """This is a helper function that handles transaction arguments passing in bare-app-call/abi-method handlers. @@ -392,41 +399,77 @@ def _wrap_handler( Approve(), ) - def __add_bare_call(self, oc_actions: BareCallActions) -> None: - action_type = Expr | SubroutineFnWrapper | ABIReturnSubroutine - cs_calls, approval_calls = oc_actions._clear_state_n_other_oc() - if cs_calls.on_call: - on_call = cast(action_type, cs_calls.on_call) - wrapped = Router._wrap_handler(False, on_call) - self.categorized_clear_state_ast.bare_calls.append( - CondNode(Txn.application_args.length() == Int(0), wrapped) - ) - if cs_calls.on_create: - on_create = cast(action_type, cs_calls.on_create) - wrapped = Router._wrap_handler(False, on_create) - self.categorized_clear_state_ast.bare_calls_create.append( - CondNode(Txn.application_id() == Int(0), wrapped) - ) - for oc, approval_bac in approval_calls.items(): - if approval_bac.on_call: - on_call = cast(action_type, approval_bac.on_call) - wrapped = Router._wrap_handler(False, on_call) - self.categorized_approval_ast.bare_calls.append( + def add_method_to_ast( + self, method_signature: str, cond: Expr | int, handler: ABIReturnSubroutine + ) -> None: + walk_in_cond = Txn.application_args[0] == MethodSignature(method_signature) + match cond: + case Expr(): + self.conditions_n_branches.append( CondNode( - And( - Txn.application_args.length() == Int(0), - Txn.on_completion() == oc, - ), - wrapped, + walk_in_cond, Cond([cond, self.wrap_handler(True, handler)]) ) ) - if approval_bac.on_create: - on_create = cast(action_type, approval_bac.on_create) - wrapped = Router._wrap_handler(False, on_create) - self.categorized_approval_ast.bare_calls_create.append( + case 1: + self.conditions_n_branches.append( + CondNode(walk_in_cond, self.wrap_handler(True, handler)) + ) + case 0: + return + case _: + raise TealInputError("Invalid condition input for add_method_to_ast") + + def program_construction(self) -> Expr: + if not self.conditions_n_branches: + raise TealInputError("ABIRouter: Cannot build program with an empty AST") + return Cond(*[[n.condition, n.branch] for n in self.conditions_n_branches]) + + +class Router: + """ + Class that help constructs: + - a *Generalized* ARC-4 app's approval/clear-state programs + - and a contract JSON object allowing for easily read and call methods in the contract + + *DISCLAIMER*: ABI-Router is still taking shape and is subject to backwards incompatible changes. + + * Based on feedback, the API and usage patterns are likely to change. + * Expect migration issues. + """ + + def __init__( + self, + name: str, + bare_calls: BareCallActions = None, + ) -> None: + """ + Args: + name: the name of the smart contract, used in the JSON object. + bare_calls: the bare app call registered for each on_completion. + """ + + self.name: str = name + self.approval_ast = ASTBuilder() + self.clear_state_ast = ASTBuilder() + + self.method_sig_to_selector: dict[str, bytes] = dict() + self.method_selector_to_sig: dict[bytes, str] = dict() + + if bare_calls and not bare_calls.is_empty(): + bare_call_approval = bare_calls.approval_construction() + if bare_call_approval: + self.approval_ast.conditions_n_branches.append( CondNode( - And(Txn.application_id() == Int(0), Txn.on_completion() == oc), - wrapped, + Txn.application_args.length() == Int(0), + cast(Expr, bare_call_approval), + ) + ) + bare_call_clear = bare_calls.clear_state_construction() + if bare_call_clear: + self.clear_state_ast.conditions_n_branches.append( + CondNode( + Txn.application_args.length() == Int(0), + cast(Expr, bare_call_clear), ) ) @@ -457,52 +500,14 @@ def add_method_handler( self.method_sig_to_selector[method_signature] = method_selector self.method_selector_to_sig[method_selector] = method_signature - wrapped = Router._wrap_handler(True, method_call) - - create_has_clear_state = call_configs._has_clear_state(CallConfig.CREATE) - create_others = call_configs._approval_program_oc(CallConfig.CREATE) - call_has_clear_state = call_configs._has_clear_state(CallConfig.CALL) - call_others = call_configs._approval_program_oc(CallConfig.CALL) - - if create_has_clear_state: - self.categorized_clear_state_ast.method_calls_create.append( - CondNode( - And( - Txn.application_id() == Int(0), - Txn.application_args[0] == MethodSignature(method_signature), - ), - wrapped, - ) - ) - if call_has_clear_state: - self.categorized_clear_state_ast.method_calls.append( - CondNode( - Txn.application_args[0] == MethodSignature(method_signature), - wrapped, - ) - ) - - if create_others: - self.categorized_approval_ast.method_calls_create.append( - CondNode( - And( - Txn.application_id() == Int(0), - Txn.application_args[0] == MethodSignature(method_signature), - Or(*[Txn.on_completion() == oc for oc in create_others]), - ), - wrapped, - ) - ) - if call_others: - self.categorized_approval_ast.method_calls.append( - CondNode( - And( - Txn.application_args[0] == MethodSignature(method_signature), - Or(*[Txn.on_completion() == oc for oc in call_others]), - ), - wrapped, - ) - ) + method_approval_cond = call_configs.approval_cond() + method_clear_state_cond = call_configs.clear_state_cond() + self.approval_ast.add_method_to_ast( + method_signature, method_approval_cond, method_call + ) + self.clear_state_ast.add_method_to_ast( + method_signature, method_clear_state_cond, method_call + ) def contract_construct(self) -> sdk_abi.Contract: """A helper function in constructing contract JSON object. @@ -532,8 +537,8 @@ def build_program(self) -> tuple[Expr, Expr, sdk_abi.Contract]: contract: JSON object of contract to allow client start off-chain call """ return ( - self.categorized_approval_ast.program_construction(), - self.categorized_clear_state_ast.program_construction(), + self.approval_ast.program_construction(), + self.clear_state_ast.program_construction(), self.contract_construct(), ) diff --git a/pyteal/ast/router_test.py b/pyteal/ast/router_test.py index 8011119cf..e08ee4f32 100644 --- a/pyteal/ast/router_test.py +++ b/pyteal/ast/router_test.py @@ -364,7 +364,7 @@ def test_wrap_handler_bare_call(): pt.Log(pt.Bytes("message")), ] for bare_call in BARE_CALL_CASES: - wrapped: pt.Expr = pt.Router._wrap_handler(False, bare_call) + wrapped: pt.Expr = pt.ASTBuilder.wrap_handler(False, bare_call) match bare_call: case pt.Expr(): if bare_call.has_return(): @@ -404,24 +404,24 @@ def test_wrap_handler_bare_call(): ] for error_case, error_msg in ERROR_CASES: with pytest.raises(pt.TealInputError) as bug: - pt.Router._wrap_handler(False, error_case) + pt.ASTBuilder.wrap_handler(False, error_case) assert error_msg in str(bug) def test_wrap_handler_method_call(): with pytest.raises(pt.TealInputError) as bug: - pt.Router._wrap_handler(True, not_registrable) + pt.ASTBuilder.wrap_handler(True, not_registrable) assert "method call ABIReturnSubroutine is not routable" in str(bug) with pytest.raises(pt.TealInputError) as bug: - pt.Router._wrap_handler(True, safe_clear_state_delete) + pt.ASTBuilder.wrap_handler(True, safe_clear_state_delete) assert "method call should be only registering ABIReturnSubroutine" in str(bug) ONLY_ABI_SUBROUTINE_CASES = list( filter(lambda x: isinstance(x, pt.ABIReturnSubroutine), GOOD_SUBROUTINE_CASES) ) for abi_subroutine in ONLY_ABI_SUBROUTINE_CASES: - wrapped: pt.Expr = pt.Router._wrap_handler(True, abi_subroutine) + wrapped: pt.Expr = pt.ASTBuilder.wrap_handler(True, abi_subroutine) assembled_wrapped: pt.TealBlock = assemble_helper(wrapped) args: list[pt.abi.BaseType] = [ diff --git a/pyteal/compiler/compiler_test.py b/pyteal/compiler/compiler_test.py index b7ca28a81..4fd33a250 100644 --- a/pyteal/compiler/compiler_test.py +++ b/pyteal/compiler/compiler_test.py @@ -2284,53 +2284,39 @@ def all_laid_to_args( txna ApplicationArgs 0 pushbytes 0xfe6bdf69 // "add(uint64,uint64)uint64" == -txn OnCompletion -intc_0 // NoOp -== -&& -bnz main_l12 +bnz main_l22 txna ApplicationArgs 0 pushbytes 0x78b488b7 // "sub(uint64,uint64)uint64" == -txn OnCompletion -intc_0 // NoOp -== -&& -bnz main_l11 +bnz main_l19 txna ApplicationArgs 0 pushbytes 0xe2f188c5 // "mul(uint64,uint64)uint64" == -txn OnCompletion -intc_0 // NoOp -== -&& -bnz main_l10 +bnz main_l16 txna ApplicationArgs 0 pushbytes 0x16e80f08 // "div(uint64,uint64)uint64" == -txn OnCompletion -intc_0 // NoOp -== -&& -bnz main_l9 +bnz main_l13 txna ApplicationArgs 0 pushbytes 0x4dfc58ae // "mod(uint64,uint64)uint64" == -txn OnCompletion -intc_0 // NoOp -== -&& -bnz main_l8 +bnz main_l10 txna ApplicationArgs 0 pushbytes 0x487ce2fd // "all_laid_to_args(uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64)uint64" == +bnz main_l7 +err +main_l7: txn OnCompletion intc_0 // NoOp == +txn ApplicationID +intc_0 // 0 +!= && -bnz main_l7 +bnz main_l9 err -main_l7: +main_l9: txna ApplicationArgs 1 btoi store 30 @@ -2408,7 +2394,17 @@ def all_laid_to_args( log intc_1 // 1 return -main_l8: +main_l10: +txn OnCompletion +intc_0 // NoOp +== +txn ApplicationID +intc_0 // 0 +!= +&& +bnz main_l12 +err +main_l12: txna ApplicationArgs 1 btoi store 24 @@ -2426,7 +2422,17 @@ def all_laid_to_args( log intc_1 // 1 return -main_l9: +main_l13: +txn OnCompletion +intc_0 // NoOp +== +txn ApplicationID +intc_0 // 0 +!= +&& +bnz main_l15 +err +main_l15: txna ApplicationArgs 1 btoi store 18 @@ -2444,7 +2450,17 @@ def all_laid_to_args( log intc_1 // 1 return -main_l10: +main_l16: +txn OnCompletion +intc_0 // NoOp +== +txn ApplicationID +intc_0 // 0 +!= +&& +bnz main_l18 +err +main_l18: txna ApplicationArgs 1 btoi store 12 @@ -2462,7 +2478,17 @@ def all_laid_to_args( log intc_1 // 1 return -main_l11: +main_l19: +txn OnCompletion +intc_0 // NoOp +== +txn ApplicationID +intc_0 // 0 +!= +&& +bnz main_l21 +err +main_l21: txna ApplicationArgs 1 btoi store 6 @@ -2480,7 +2506,17 @@ def all_laid_to_args( log intc_1 // 1 return -main_l12: +main_l22: +txn OnCompletion +intc_0 // NoOp +== +txn ApplicationID +intc_0 // 0 +!= +&& +bnz main_l24 +err +main_l24: txna ApplicationArgs 1 btoi store 0 @@ -2609,12 +2645,19 @@ def all_laid_to_args( assert expected_ap == actual_ap_compiled expected_csp = """#pragma version 6 +intcblock 0 txn NumAppArgs -pushint 0 // 0 +intc_0 // 0 == bnz main_l2 err main_l2: +txn ApplicationID +intc_0 // 0 +!= +bnz main_l4 +err +main_l4: pushint 1 // 1 return""".strip() assert expected_csp == actual_csp_compiled From e7a49f1a525559836fd76d907ad8f3005cf505fa Mon Sep 17 00:00:00 2001 From: Hang Su Date: Fri, 27 May 2022 11:47:20 -0400 Subject: [PATCH 166/188] review comments --- pyteal/ast/router.py | 4 ++- pyteal/compiler/compiler_test.py | 44 ++++++++++++-------------------- 2 files changed, 19 insertions(+), 29 deletions(-) diff --git a/pyteal/ast/router.py b/pyteal/ast/router.py index fbfd7052c..9edcc5853 100644 --- a/pyteal/ast/router.py +++ b/pyteal/ast/router.py @@ -17,6 +17,7 @@ SubroutineFnWrapper, ABIReturnSubroutine, ) +from pyteal.ast.assert_ import Assert from pyteal.ast.cond import Cond from pyteal.ast.expr import Expr from pyteal.ast.app import OnComplete, EnumInt @@ -407,7 +408,8 @@ def add_method_to_ast( case Expr(): self.conditions_n_branches.append( CondNode( - walk_in_cond, Cond([cond, self.wrap_handler(True, handler)]) + walk_in_cond, + Seq(Assert(cond), self.wrap_handler(True, handler)), ) ) case 1: diff --git a/pyteal/compiler/compiler_test.py b/pyteal/compiler/compiler_test.py index 4fd33a250..844fd44a0 100644 --- a/pyteal/compiler/compiler_test.py +++ b/pyteal/compiler/compiler_test.py @@ -2284,23 +2284,23 @@ def all_laid_to_args( txna ApplicationArgs 0 pushbytes 0xfe6bdf69 // "add(uint64,uint64)uint64" == -bnz main_l22 +bnz main_l12 txna ApplicationArgs 0 pushbytes 0x78b488b7 // "sub(uint64,uint64)uint64" == -bnz main_l19 +bnz main_l11 txna ApplicationArgs 0 pushbytes 0xe2f188c5 // "mul(uint64,uint64)uint64" == -bnz main_l16 +bnz main_l10 txna ApplicationArgs 0 pushbytes 0x16e80f08 // "div(uint64,uint64)uint64" == -bnz main_l13 +bnz main_l9 txna ApplicationArgs 0 pushbytes 0x4dfc58ae // "mod(uint64,uint64)uint64" == -bnz main_l10 +bnz main_l8 txna ApplicationArgs 0 pushbytes 0x487ce2fd // "all_laid_to_args(uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64)uint64" == @@ -2314,9 +2314,7 @@ def all_laid_to_args( intc_0 // 0 != && -bnz main_l9 -err -main_l9: +assert txna ApplicationArgs 1 btoi store 30 @@ -2394,7 +2392,7 @@ def all_laid_to_args( log intc_1 // 1 return -main_l10: +main_l8: txn OnCompletion intc_0 // NoOp == @@ -2402,9 +2400,7 @@ def all_laid_to_args( intc_0 // 0 != && -bnz main_l12 -err -main_l12: +assert txna ApplicationArgs 1 btoi store 24 @@ -2422,7 +2418,7 @@ def all_laid_to_args( log intc_1 // 1 return -main_l13: +main_l9: txn OnCompletion intc_0 // NoOp == @@ -2430,9 +2426,7 @@ def all_laid_to_args( intc_0 // 0 != && -bnz main_l15 -err -main_l15: +assert txna ApplicationArgs 1 btoi store 18 @@ -2450,7 +2444,7 @@ def all_laid_to_args( log intc_1 // 1 return -main_l16: +main_l10: txn OnCompletion intc_0 // NoOp == @@ -2458,9 +2452,7 @@ def all_laid_to_args( intc_0 // 0 != && -bnz main_l18 -err -main_l18: +assert txna ApplicationArgs 1 btoi store 12 @@ -2478,7 +2470,7 @@ def all_laid_to_args( log intc_1 // 1 return -main_l19: +main_l11: txn OnCompletion intc_0 // NoOp == @@ -2486,9 +2478,7 @@ def all_laid_to_args( intc_0 // 0 != && -bnz main_l21 -err -main_l21: +assert txna ApplicationArgs 1 btoi store 6 @@ -2506,7 +2496,7 @@ def all_laid_to_args( log intc_1 // 1 return -main_l22: +main_l12: txn OnCompletion intc_0 // NoOp == @@ -2514,9 +2504,7 @@ def all_laid_to_args( intc_0 // 0 != && -bnz main_l24 -err -main_l24: +assert txna ApplicationArgs 1 btoi store 0 From 22cf520ac6e56e01574f0f02a3d3960d5523c417 Mon Sep 17 00:00:00 2001 From: Hang Su <87964331+ahangsu@users.noreply.github.com> Date: Fri, 27 May 2022 13:25:59 -0400 Subject: [PATCH 167/188] An attempt to add decorator syntax to abi-router (#370) * an attempt to use decorator * no return, or we will have python-level issue? --- pyteal/ast/router.py | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/pyteal/ast/router.py b/pyteal/ast/router.py index 9edcc5853..6a391fb0c 100644 --- a/pyteal/ast/router.py +++ b/pyteal/ast/router.py @@ -1,5 +1,5 @@ from dataclasses import dataclass, field, fields, astuple -from typing import cast, Optional +from typing import cast, Optional, Callable from enum import IntFlag from algosdk import abi as sdk_abi @@ -511,6 +511,35 @@ def add_method_handler( method_signature, method_clear_state_cond, method_call ) + def method( + self, + func: Callable = None, + /, + *, + overriding_name: str = None, + no_op: CallConfig = CallConfig.CALL, + opt_in: CallConfig = CallConfig.NEVER, + close_out: CallConfig = CallConfig.NEVER, + clear_state: CallConfig = CallConfig.NEVER, + update_application: CallConfig = CallConfig.NEVER, + delete_application: CallConfig = CallConfig.NEVER, + ): + def wrap(_func): + wrapped_subroutine = ABIReturnSubroutine(func) + call_configs = MethodConfig( + no_op=no_op, + opt_in=opt_in, + close_out=close_out, + clear_state=clear_state, + update_application=update_application, + delete_application=delete_application, + ) + self.add_method_handler(wrapped_subroutine, overriding_name, call_configs) + + if not func: + return wrap + return wrap(func) + def contract_construct(self) -> sdk_abi.Contract: """A helper function in constructing contract JSON object. From f66b7e4b1d449ca849e742e8d57bc323ddfbddf3 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Fri, 27 May 2022 13:59:27 -0400 Subject: [PATCH 168/188] minor fix --- pyteal/ast/router.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyteal/ast/router.py b/pyteal/ast/router.py index 6a391fb0c..4612c68d2 100644 --- a/pyteal/ast/router.py +++ b/pyteal/ast/router.py @@ -525,7 +525,7 @@ def method( delete_application: CallConfig = CallConfig.NEVER, ): def wrap(_func): - wrapped_subroutine = ABIReturnSubroutine(func) + wrapped_subroutine = ABIReturnSubroutine(_func) call_configs = MethodConfig( no_op=no_op, opt_in=opt_in, From c42da1e050f4e8ab4854123d369cd86dc844b44e Mon Sep 17 00:00:00 2001 From: Hang Su Date: Fri, 27 May 2022 15:21:45 -0400 Subject: [PATCH 169/188] per pr comment on on-complete-action --- pyteal/ast/router.py | 132 ++++++++++++++++++------------- pyteal/ast/router_test.py | 12 +++ pyteal/compiler/compiler_test.py | 4 +- 3 files changed, 92 insertions(+), 56 deletions(-) diff --git a/pyteal/ast/router.py b/pyteal/ast/router.py index 4612c68d2..850b17e99 100644 --- a/pyteal/ast/router.py +++ b/pyteal/ast/router.py @@ -47,6 +47,19 @@ class CallConfig(IntFlag): CREATE = 2 ALL = 3 + def condition_under_config(self) -> Expr | int: + match self: + case CallConfig.NEVER: + return 0 + case CallConfig.CALL: + return Txn.application_id() != Int(0) + case CallConfig.CREATE: + return Txn.application_id() == Int(0) + case CallConfig.ALL: + return 1 + case _: + raise TealInternalError(f"unexpected CallConfig {self}") + CallConfig.__module__ = "pyteal" @@ -54,7 +67,7 @@ class CallConfig(IntFlag): @dataclass(frozen=True) class MethodConfig: """ - CallConfigs keep track of one method registration's CallConfigs for all OnComplete cases. + MethodConfig keep track of one method registration's CallConfigs for all OnComplete cases. By ARC-0004 spec: If an Application is called with greater than zero Application call arguments (NOT a bare Application call), @@ -93,20 +106,6 @@ def arc4_compliant(cls): def is_arc4_compliant(self) -> bool: return self == self.arc4_compliant() - @staticmethod - def __condition_under_config(cc: CallConfig) -> Expr | int: - match cc: - case CallConfig.NEVER: - return 0 - case CallConfig.CALL: - return Txn.application_id() != Int(0) - case CallConfig.CREATE: - return Txn.application_id() == Int(0) - case CallConfig.ALL: - return 1 - case _: - raise TealInternalError("CallConfig scope exceeding!") - def approval_cond(self) -> Expr | int: config_oc_pairs: dict[CallConfig, EnumInt] = { self.no_op: OnComplete.NoOp, @@ -122,7 +121,7 @@ def approval_cond(self) -> Expr | int: else: cond_list = [] for config in config_oc_pairs: - config_cond = self.__condition_under_config(config) + config_cond = config.condition_under_config() match config_cond: case Expr(): cond_list.append( @@ -137,12 +136,12 @@ def approval_cond(self) -> Expr | int: continue case _: raise TealInternalError( - "condition_under_config scope exceeding!" + f"unexpected condition_under_config: {config_cond}" ) return Or(*cond_list) def clear_state_cond(self) -> Expr | int: - return self.__condition_under_config(self.clear_state) + return self.clear_state.condition_under_config() @dataclass(frozen=True) @@ -151,12 +150,18 @@ class OnCompleteAction: OnComplete Action, registers bare calls to one single OnCompletion case. """ - on_create: Optional[Expr | SubroutineFnWrapper | ABIReturnSubroutine] = field( - kw_only=True, default=None - ) - on_call: Optional[Expr | SubroutineFnWrapper | ABIReturnSubroutine] = field( + action: Optional[Expr | SubroutineFnWrapper | ABIReturnSubroutine] = field( kw_only=True, default=None ) + call_config: CallConfig = field(kw_only=True, default=CallConfig.NEVER) + + def __post_init__(self): + if (self.call_config == CallConfig.NEVER and self.action) or ( + self.call_config != CallConfig.NEVER and not self.action + ): + raise TealInputError( + f"action {self.action} and call_config {self.call_config} contradicts" + ) @staticmethod def never() -> "OnCompleteAction": @@ -166,22 +171,22 @@ def never() -> "OnCompleteAction": def create_only( f: Expr | SubroutineFnWrapper | ABIReturnSubroutine, ) -> "OnCompleteAction": - return OnCompleteAction(on_create=f) + return OnCompleteAction(action=f, call_config=CallConfig.CREATE) @staticmethod def call_only( f: Expr | SubroutineFnWrapper | ABIReturnSubroutine, ) -> "OnCompleteAction": - return OnCompleteAction(on_call=f) + return OnCompleteAction(action=f, call_config=CallConfig.CALL) @staticmethod def always( f: Expr | SubroutineFnWrapper | ABIReturnSubroutine, ) -> "OnCompleteAction": - return OnCompleteAction(on_create=f, on_call=f) + return OnCompleteAction(action=f, call_config=CallConfig.ALL) def is_empty(self) -> bool: - return not (self.on_call or self.on_create) + return not self.action and self.call_config == CallConfig.NEVER OnCompleteAction.__module__ = "pyteal" @@ -190,7 +195,7 @@ def is_empty(self) -> bool: @dataclass(frozen=True) class BareCallActions: """ - OnCompletion Actions keep track of bare-call registrations to all OnCompletion cases. + BareCallActions keep track of bare-call registrations to all OnCompletion cases. """ close_out: OnCompleteAction = field(kw_only=True, default=OnCompleteAction.never()) @@ -225,41 +230,57 @@ def approval_construction(self) -> Optional[Expr]: return None conditions_n_branches: list[CondNode] = list() for oc, oca in oc_action_pair.items(): - if oca.on_call: - conditions_n_branches.append( - CondNode( - And(Txn.on_completion() == oc, Txn.application_id() != Int(0)), - ASTBuilder.wrap_handler(False, oca.on_call), - ) - ) - if oca.on_create: - conditions_n_branches.append( - CondNode( - And(Txn.on_completion() == oc, Txn.application_id() == Int(0)), - ASTBuilder.wrap_handler(False, oca.on_create), + if oca.is_empty(): + continue + wrapped_handler = ASTBuilder.wrap_handler( + False, + cast(Expr | SubroutineFnWrapper | ABIReturnSubroutine, oca.action), + ) + match oca.call_config: + case CallConfig.ALL: + cond_body = wrapped_handler + case CallConfig.CALL | CallConfig.CREATE: + cond_body = Seq( + Assert(cast(Expr, oca.call_config.condition_under_config())), + wrapped_handler, ) + case _: + raise TealInternalError(f"Unexpected CallConfig: {oca.call_config}") + conditions_n_branches.append( + CondNode( + Txn.on_completion() == oc, + cond_body, ) + ) return Cond(*[[n.condition, n.branch] for n in conditions_n_branches]) def clear_state_construction(self) -> Optional[Expr]: if self.clear_state.is_empty(): return None - conditions_n_branches: list[CondNode] = list() - if self.clear_state.on_call: - conditions_n_branches.append( - CondNode( - Txn.application_id() != Int(0), - ASTBuilder.wrap_handler(False, self.clear_state.on_call), + + wrapped_handler = ASTBuilder.wrap_handler( + False, + cast( + Expr | SubroutineFnWrapper | ABIReturnSubroutine, + self.clear_state.action, + ), + ) + match self.clear_state.call_config: + case CallConfig.ALL: + return wrapped_handler + case CallConfig.CALL | CallConfig.CREATE: + return Seq( + Assert( + cast( + Expr, self.clear_state.call_config.condition_under_config() + ) + ), + wrapped_handler, ) - ) - if self.clear_state.on_create: - conditions_n_branches.append( - CondNode( - Txn.application_id() == Int(0), - ASTBuilder.wrap_handler(False, self.clear_state.on_create), + case _: + raise TealInternalError( + f"Unexpected CallConfig: {self.clear_state.call_config}" ) - ) - return Cond(*[[n.condition, n.branch] for n in conditions_n_branches]) BareCallActions.__module__ = "pyteal" @@ -502,6 +523,11 @@ def add_method_handler( self.method_sig_to_selector[method_signature] = method_selector self.method_selector_to_sig[method_selector] = method_signature + if call_configs.is_arc4_compliant(): + self.approval_ast.add_method_to_ast(method_signature, 1, method_call) + self.clear_state_ast.add_method_to_ast(method_signature, 1, method_call) + return + method_approval_cond = call_configs.approval_cond() method_clear_state_cond = call_configs.clear_state_cond() self.approval_ast.add_method_to_ast( diff --git a/pyteal/ast/router_test.py b/pyteal/ast/router_test.py index e08ee4f32..2ac523139 100644 --- a/pyteal/ast/router_test.py +++ b/pyteal/ast/router_test.py @@ -224,6 +224,18 @@ def camel_to_snake(name: str) -> str: return "".join(["_" + c.lower() if c.isupper() else c for c in name]).lstrip("_") +def test_method_config(): + on_complete_pow_set = power_set(ON_COMPLETE_CASES) + for on_complete_set in on_complete_pow_set: + oc_names = [camel_to_snake(oc.name) for oc in on_complete_set] + full_perm_call_configs_for_ocs = full_perm_gen( + CALL_CONFIGS, len(on_complete_set) + ) + for call_config in full_perm_call_configs_for_ocs: + _ = pt.MethodConfig(**dict(zip(oc_names, call_config))) + # method_config: pt.MethodConfig + + def test_add_bare_call(): pass diff --git a/pyteal/compiler/compiler_test.py b/pyteal/compiler/compiler_test.py index 844fd44a0..dac8aefe5 100644 --- a/pyteal/compiler/compiler_test.py +++ b/pyteal/compiler/compiler_test.py @@ -2643,9 +2643,7 @@ def all_laid_to_args( txn ApplicationID intc_0 // 0 != -bnz main_l4 -err -main_l4: +assert pushint 1 // 1 return""".strip() assert expected_csp == actual_csp_compiled From c0af62e38348485688e4ec2247ebb0e01114dab0 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Fri, 27 May 2022 16:28:37 -0400 Subject: [PATCH 170/188] eliminate potential issue --- pyteal/ast/router.py | 43 +++++++++++++++++---------------------- pyteal/ast/router_test.py | 11 +++++++++- 2 files changed, 29 insertions(+), 25 deletions(-) diff --git a/pyteal/ast/router.py b/pyteal/ast/router.py index 850b17e99..018b007f8 100644 --- a/pyteal/ast/router.py +++ b/pyteal/ast/router.py @@ -107,31 +107,26 @@ def is_arc4_compliant(self) -> bool: return self == self.arc4_compliant() def approval_cond(self) -> Expr | int: - config_oc_pairs: dict[CallConfig, EnumInt] = { - self.no_op: OnComplete.NoOp, - self.opt_in: OnComplete.OptIn, - self.close_out: OnComplete.CloseOut, - self.update_application: OnComplete.UpdateApplication, - self.delete_application: OnComplete.DeleteApplication, - } + config_oc_pairs: list[tuple[CallConfig, EnumInt]] = [ + (self.no_op, OnComplete.NoOp), + (self.opt_in, OnComplete.OptIn), + (self.close_out, OnComplete.CloseOut), + (self.update_application, OnComplete.UpdateApplication), + (self.delete_application, OnComplete.DeleteApplication), + ] if all(config == CallConfig.NEVER for config in config_oc_pairs): return 0 elif all(config == CallConfig.ALL for config in config_oc_pairs): return 1 else: cond_list = [] - for config in config_oc_pairs: + for config, oc in config_oc_pairs: config_cond = config.condition_under_config() match config_cond: case Expr(): - cond_list.append( - And( - Txn.on_completion() == config_oc_pairs[config], - config_cond, - ) - ) + cond_list.append(And(Txn.on_completion() == oc, config_cond)) case 1: - cond_list.append(Txn.on_completion() == config_oc_pairs[config]) + cond_list.append(Txn.on_completion() == oc) case 0: continue case _: @@ -219,17 +214,17 @@ def is_empty(self) -> bool: return True def approval_construction(self) -> Optional[Expr]: - oc_action_pair: dict[EnumInt, OnCompleteAction] = { - OnComplete.NoOp: self.no_op, - OnComplete.OptIn: self.opt_in, - OnComplete.CloseOut: self.close_out, - OnComplete.UpdateApplication: self.update_application, - OnComplete.DeleteApplication: self.delete_application, - } - if all(oca.is_empty() for oca in oc_action_pair.values()): + oc_action_pair: list[tuple[EnumInt, OnCompleteAction]] = [ + (OnComplete.NoOp, self.no_op), + (OnComplete.OptIn, self.opt_in), + (OnComplete.CloseOut, self.close_out), + (OnComplete.UpdateApplication, self.update_application), + (OnComplete.DeleteApplication, self.delete_application), + ] + if all(oca.is_empty() for _, oca in oc_action_pair): return None conditions_n_branches: list[CondNode] = list() - for oc, oca in oc_action_pair.items(): + for oc, oca in oc_action_pair: if oca.is_empty(): continue wrapped_handler = ASTBuilder.wrap_handler( diff --git a/pyteal/ast/router_test.py b/pyteal/ast/router_test.py index 2ac523139..ab3c37750 100644 --- a/pyteal/ast/router_test.py +++ b/pyteal/ast/router_test.py @@ -232,7 +232,16 @@ def test_method_config(): CALL_CONFIGS, len(on_complete_set) ) for call_config in full_perm_call_configs_for_ocs: - _ = pt.MethodConfig(**dict(zip(oc_names, call_config))) + mc = pt.MethodConfig(**dict(zip(oc_names, call_config))) + approval_list = [ + mc.no_op, + mc.opt_in, + mc.close_out, + mc.update_application, + mc.delete_application, + ] + len(approval_list) + # method_config: pt.MethodConfig From 78ca6fbe5c617a9ad1f1c200d05b0bbc80067066 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Fri, 27 May 2022 17:50:13 -0400 Subject: [PATCH 171/188] update call config testcase --- pyteal/ast/router.py | 4 ++-- pyteal/ast/router_test.py | 37 ++++++++++++++++++++++++++----------- 2 files changed, 28 insertions(+), 13 deletions(-) diff --git a/pyteal/ast/router.py b/pyteal/ast/router.py index 018b007f8..c7b16f21b 100644 --- a/pyteal/ast/router.py +++ b/pyteal/ast/router.py @@ -114,9 +114,9 @@ def approval_cond(self) -> Expr | int: (self.update_application, OnComplete.UpdateApplication), (self.delete_application, OnComplete.DeleteApplication), ] - if all(config == CallConfig.NEVER for config in config_oc_pairs): + if all(config == CallConfig.NEVER for config, _ in config_oc_pairs): return 0 - elif all(config == CallConfig.ALL for config in config_oc_pairs): + elif all(config == CallConfig.ALL for config, _ in config_oc_pairs): return 1 else: cond_list = [] diff --git a/pyteal/ast/router_test.py b/pyteal/ast/router_test.py index ab3c37750..470fc9ec3 100644 --- a/pyteal/ast/router_test.py +++ b/pyteal/ast/router_test.py @@ -177,14 +177,6 @@ def not_registrable(lhs: pt.abi.Uint64, rhs: pt.Expr, *, output: pt.abi.Uint64): ] -CALL_CONFIGS = [ - pt.CallConfig.NEVER, - pt.CallConfig.CALL, - pt.CallConfig.CREATE, - pt.CallConfig.ALL, -] - - def power_set(no_dup_list: list, length_override: int = None): if length_override is None: length_override = len(no_dup_list) @@ -203,7 +195,7 @@ def full_perm_gen(non_dup_list: list, perm_length: int): for index in range(len(non_dup_list) ** perm_length): index_list_basis = [] temp = index - for i in range(perm_length): + for _ in range(perm_length): index_list_basis.append(non_dup_list[temp % len(non_dup_list)]) temp //= len(non_dup_list) yield index_list_basis @@ -224,12 +216,35 @@ def camel_to_snake(name: str) -> str: return "".join(["_" + c.lower() if c.isupper() else c for c in name]).lstrip("_") +def test_call_config(): + for cc in pt.CallConfig: + cond_on_cc: pt.Expr | int = cc.condition_under_config() + match cond_on_cc: + case pt.Expr(): + expected_cc = ( + (pt.Txn.application_id() == pt.Int(0)) + if cc == pt.CallConfig.CREATE + else (pt.Txn.application_id() != pt.Int(0)) + ) + with pt.TealComponent.Context.ignoreExprEquality(): + assert assemble_helper(cond_on_cc) == assemble_helper(expected_cc) + case int(): + assert cond_on_cc == int(cc) & 1 + case _: + raise pt.TealInternalError(f"unexpected cond_on_cc {cond_on_cc}") + + def test_method_config(): + never_mc = pt.MethodConfig(no_op=pt.CallConfig.NEVER) + assert never_mc.is_never() + assert never_mc.approval_cond() == 0 + assert never_mc.clear_state_cond() == 0 + on_complete_pow_set = power_set(ON_COMPLETE_CASES) for on_complete_set in on_complete_pow_set: oc_names = [camel_to_snake(oc.name) for oc in on_complete_set] full_perm_call_configs_for_ocs = full_perm_gen( - CALL_CONFIGS, len(on_complete_set) + list(pt.CallConfig), len(on_complete_set) ) for call_config in full_perm_call_configs_for_ocs: mc = pt.MethodConfig(**dict(zip(oc_names, call_config))) @@ -275,7 +290,7 @@ def test_add_method(): abi_subroutine_cases, on_complete_pow_set ): full_perm_call_configs_for_ocs = full_perm_gen( - CALL_CONFIGS, len(on_complete_set) + list(pt.CallConfig), len(on_complete_set) ) oc_names = [camel_to_snake(oc.name) for oc in on_complete_set] for call_config in full_perm_call_configs_for_ocs: From 4aa69c62c0efbf14485d06f2b937baa4afab2010 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Fri, 27 May 2022 19:40:20 -0400 Subject: [PATCH 172/188] add testcase for on complete action --- pyteal/__init__.py | 4 ++-- pyteal/__init__.pyi | 4 ++-- pyteal/ast/router.py | 12 ++++++------ pyteal/ast/router_test.py | 29 +++++++++++++++++++++-------- pyteal/config.py | 2 +- 5 files changed, 32 insertions(+), 19 deletions(-) diff --git a/pyteal/__init__.py b/pyteal/__init__.py index e0b3ed3cd..8087c15c3 100644 --- a/pyteal/__init__.py +++ b/pyteal/__init__.py @@ -21,7 +21,7 @@ MAX_GROUP_SIZE, NUM_SLOTS, RETURN_HASH_PREFIX, - METHOD_ARG_NUM_LIMIT, + METHOD_ARG_NUM_CUTOFF, ) # begin __all__ @@ -43,7 +43,7 @@ "MAX_GROUP_SIZE", "NUM_SLOTS", "RETURN_HASH_PREFIX", - "METHOD_ARG_NUM_LIMIT", + "METHOD_ARG_NUM_CUTOFF", ] ) # end __all__ diff --git a/pyteal/__init__.pyi b/pyteal/__init__.pyi index 999652df5..ccba1bb67 100644 --- a/pyteal/__init__.pyi +++ b/pyteal/__init__.pyi @@ -24,7 +24,7 @@ from pyteal.config import ( MAX_GROUP_SIZE, NUM_SLOTS, RETURN_HASH_PREFIX, - METHOD_ARG_NUM_LIMIT, + METHOD_ARG_NUM_CUTOFF, ) __all__ = [ @@ -125,7 +125,7 @@ __all__ = [ "Lt", "MAX_GROUP_SIZE", "MAX_TEAL_VERSION", - "METHOD_ARG_NUM_LIMIT", + "METHOD_ARG_NUM_CUTOFF", "MIN_TEAL_VERSION", "MaybeValue", "MethodConfig", diff --git a/pyteal/ast/router.py b/pyteal/ast/router.py index c7b16f21b..79d1ce845 100644 --- a/pyteal/ast/router.py +++ b/pyteal/ast/router.py @@ -5,7 +5,7 @@ from algosdk import abi as sdk_abi from algosdk import encoding -from pyteal.config import METHOD_ARG_NUM_LIMIT +from pyteal.config import METHOD_ARG_NUM_CUTOFF from pyteal.errors import TealInputError, TealInternalError from pyteal.types import TealType from pyteal.compiler.compiler import compileTeal, DEFAULT_TEAL_VERSION, OptimizeOptions @@ -365,9 +365,9 @@ def wrap_handler( arg_type_specs = cast( list[abi.TypeSpec], handler.subroutine.expected_arg_types ) - if handler.subroutine.argument_count() > METHOD_ARG_NUM_LIMIT: - last_arg_specs_grouped = arg_type_specs[METHOD_ARG_NUM_LIMIT - 1 :] - arg_type_specs = arg_type_specs[: METHOD_ARG_NUM_LIMIT - 1] + if handler.subroutine.argument_count() > METHOD_ARG_NUM_CUTOFF: + last_arg_specs_grouped = arg_type_specs[METHOD_ARG_NUM_CUTOFF - 1 :] + arg_type_specs = arg_type_specs[: METHOD_ARG_NUM_CUTOFF - 1] last_arg_spec = abi.TupleTypeSpec(*last_arg_specs_grouped) arg_type_specs.append(last_arg_spec) @@ -379,10 +379,10 @@ def wrap_handler( for i in range(len(arg_type_specs)) ] - if handler.subroutine.argument_count() > METHOD_ARG_NUM_LIMIT: + if handler.subroutine.argument_count() > METHOD_ARG_NUM_CUTOFF: tuple_arg_type_specs: list[abi.TypeSpec] = cast( list[abi.TypeSpec], - handler.subroutine.expected_arg_types[METHOD_ARG_NUM_LIMIT - 1 :], + handler.subroutine.expected_arg_types[METHOD_ARG_NUM_CUTOFF - 1 :], ) tuple_abi_args: list[abi.BaseType] = [ t_arg_ts.new_instance() for t_arg_ts in tuple_arg_type_specs diff --git a/pyteal/ast/router_test.py b/pyteal/ast/router_test.py index 470fc9ec3..16c9be55f 100644 --- a/pyteal/ast/router_test.py +++ b/pyteal/ast/router_test.py @@ -237,9 +237,16 @@ def test_call_config(): def test_method_config(): never_mc = pt.MethodConfig(no_op=pt.CallConfig.NEVER) assert never_mc.is_never() + assert not never_mc.is_arc4_compliant() assert never_mc.approval_cond() == 0 assert never_mc.clear_state_cond() == 0 + all_mc = pt.MethodConfig.arc4_compliant() + assert not all_mc.is_never() + assert all_mc.is_arc4_compliant() + assert all_mc.approval_cond() == 1 + assert all_mc.clear_state_cond() == 1 + on_complete_pow_set = power_set(ON_COMPLETE_CASES) for on_complete_set in on_complete_pow_set: oc_names = [camel_to_snake(oc.name) for oc in on_complete_set] @@ -260,8 +267,14 @@ def test_method_config(): # method_config: pt.MethodConfig -def test_add_bare_call(): - pass +def test_on_complete_action(): + with pytest.raises(pt.TealInputError) as contradict_err: + pt.OnCompleteAction(action=pt.Seq(), call_config=pt.CallConfig.NEVER) + assert "contradicts" in str(contradict_err) + assert pt.OnCompleteAction.never().is_empty() + assert pt.OnCompleteAction.call_only(pt.Seq()).call_config == pt.CallConfig.CALL + assert pt.OnCompleteAction.create_only(pt.Seq()).call_config == pt.CallConfig.CREATE + assert pt.OnCompleteAction.always(pt.Seq()).call_config == pt.CallConfig.ALL def test_add_method(): @@ -469,26 +482,26 @@ def test_wrap_handler_method_call(): loading: list[pt.Expr] - if abi_subroutine.subroutine.argument_count() > pt.METHOD_ARG_NUM_LIMIT: + if abi_subroutine.subroutine.argument_count() > pt.METHOD_ARG_NUM_CUTOFF: sdk_last_arg = pt.abi.TupleTypeSpec( *[ spec for spec in typing.cast( list[pt.abi.TypeSpec], abi_subroutine.subroutine.expected_arg_types, - )[pt.METHOD_ARG_NUM_LIMIT - 1 :] + )[pt.METHOD_ARG_NUM_CUTOFF - 1 :] ] ).new_instance() loading = [ arg.decode(pt.Txn.application_args[index + 1]) - for index, arg in enumerate(args[: pt.METHOD_ARG_NUM_LIMIT - 1]) + for index, arg in enumerate(args[: pt.METHOD_ARG_NUM_CUTOFF - 1]) ] loading.append( - sdk_last_arg.decode(pt.Txn.application_args[pt.METHOD_ARG_NUM_LIMIT]) + sdk_last_arg.decode(pt.Txn.application_args[pt.METHOD_ARG_NUM_CUTOFF]) ) - for i in range(pt.METHOD_ARG_NUM_LIMIT - 1, len(args)): + for i in range(pt.METHOD_ARG_NUM_CUTOFF - 1, len(args)): loading.append( - sdk_last_arg[i - pt.METHOD_ARG_NUM_LIMIT + 1].store_into(args[i]) + sdk_last_arg[i - pt.METHOD_ARG_NUM_CUTOFF + 1].store_into(args[i]) ) else: loading = [ diff --git a/pyteal/config.py b/pyteal/config.py index 9d5b24e19..e32fda458 100644 --- a/pyteal/config.py +++ b/pyteal/config.py @@ -11,4 +11,4 @@ RETURN_HASH_PREFIX = ABI_RETURN_HASH # Method argument number limit -METHOD_ARG_NUM_LIMIT = 15 +METHOD_ARG_NUM_CUTOFF = 15 From 7df09965453b24b766ca7c828576203870dcd16a Mon Sep 17 00:00:00 2001 From: Hang Su Date: Fri, 27 May 2022 19:46:01 -0400 Subject: [PATCH 173/188] per comments --- pyteal/__init__.pyi | 1 - pyteal/ast/__init__.py | 2 - pyteal/ast/int.py | 3 -- pyteal/ast/router.py | 4 +- pyteal/ast/router_test.py | 21 ++++----- pyteal/compiler/compiler_test.py | 79 +++++++++++++++----------------- 6 files changed, 50 insertions(+), 60 deletions(-) diff --git a/pyteal/__init__.pyi b/pyteal/__init__.pyi index ccba1bb67..5e49e918e 100644 --- a/pyteal/__init__.pyi +++ b/pyteal/__init__.pyi @@ -29,7 +29,6 @@ from pyteal.config import ( __all__ = [ "ABIReturnSubroutine", - "ASTBuilder", "AccountParam", "Add", "Addr", diff --git a/pyteal/ast/__init__.py b/pyteal/ast/__init__.py index f288bdacc..68c6fd149 100644 --- a/pyteal/ast/__init__.py +++ b/pyteal/ast/__init__.py @@ -140,7 +140,6 @@ from pyteal.ast.ecdsa import EcdsaCurve, EcdsaVerify, EcdsaDecompress, EcdsaRecover from pyteal.ast.router import ( Router, - ASTBuilder, CallConfig, MethodConfig, OnCompleteAction, @@ -288,7 +287,6 @@ "Break", "Continue", "Router", - "ASTBuilder", "CallConfig", "MethodConfig", "OnCompleteAction", diff --git a/pyteal/ast/int.py b/pyteal/ast/int.py index cc06710eb..26ef8cff5 100644 --- a/pyteal/ast/int.py +++ b/pyteal/ast/int.py @@ -64,8 +64,5 @@ def __str__(self): def type_of(self): return TealType.uint64 - def __hash__(self) -> int: - return hash(str(self)) - EnumInt.__module__ = "pyteal" diff --git a/pyteal/ast/router.py b/pyteal/ast/router.py index 79d1ce845..6954b8720 100644 --- a/pyteal/ast/router.py +++ b/pyteal/ast/router.py @@ -537,7 +537,7 @@ def method( func: Callable = None, /, *, - overriding_name: str = None, + name: str = None, no_op: CallConfig = CallConfig.CALL, opt_in: CallConfig = CallConfig.NEVER, close_out: CallConfig = CallConfig.NEVER, @@ -555,7 +555,7 @@ def wrap(_func): update_application=update_application, delete_application=delete_application, ) - self.add_method_handler(wrapped_subroutine, overriding_name, call_configs) + self.add_method_handler(wrapped_subroutine, name, call_configs) if not func: return wrap diff --git a/pyteal/ast/router_test.py b/pyteal/ast/router_test.py index 16c9be55f..20cf4af4d 100644 --- a/pyteal/ast/router_test.py +++ b/pyteal/ast/router_test.py @@ -1,4 +1,6 @@ import pyteal as pt +from pyteal.ast.router import ASTBuilder + import itertools import pytest @@ -6,6 +8,7 @@ import typing import algosdk.abi as sdk_abi + options = pt.CompileOptions(version=5) @@ -188,7 +191,7 @@ def power_set(no_dup_list: list, length_override: int = None): def full_perm_gen(non_dup_list: list, perm_length: int): if perm_length < 0: - raise + raise pt.TealInputError("input permutation length must be non-negative") elif perm_length == 0: yield [] return @@ -413,7 +416,7 @@ def test_wrap_handler_bare_call(): pt.Log(pt.Bytes("message")), ] for bare_call in BARE_CALL_CASES: - wrapped: pt.Expr = pt.ASTBuilder.wrap_handler(False, bare_call) + wrapped: pt.Expr = ASTBuilder.wrap_handler(False, bare_call) match bare_call: case pt.Expr(): if bare_call.has_return(): @@ -453,24 +456,24 @@ def test_wrap_handler_bare_call(): ] for error_case, error_msg in ERROR_CASES: with pytest.raises(pt.TealInputError) as bug: - pt.ASTBuilder.wrap_handler(False, error_case) + ASTBuilder.wrap_handler(False, error_case) assert error_msg in str(bug) def test_wrap_handler_method_call(): with pytest.raises(pt.TealInputError) as bug: - pt.ASTBuilder.wrap_handler(True, not_registrable) + ASTBuilder.wrap_handler(True, not_registrable) assert "method call ABIReturnSubroutine is not routable" in str(bug) with pytest.raises(pt.TealInputError) as bug: - pt.ASTBuilder.wrap_handler(True, safe_clear_state_delete) + ASTBuilder.wrap_handler(True, safe_clear_state_delete) assert "method call should be only registering ABIReturnSubroutine" in str(bug) ONLY_ABI_SUBROUTINE_CASES = list( filter(lambda x: isinstance(x, pt.ABIReturnSubroutine), GOOD_SUBROUTINE_CASES) ) for abi_subroutine in ONLY_ABI_SUBROUTINE_CASES: - wrapped: pt.Expr = pt.ASTBuilder.wrap_handler(True, abi_subroutine) + wrapped: pt.Expr = ASTBuilder.wrap_handler(True, abi_subroutine) assembled_wrapped: pt.TealBlock = assemble_helper(wrapped) args: list[pt.abi.BaseType] = [ @@ -539,8 +542,4 @@ def test_contract_json_obj(): method_list.append(sdk_abi.Method.from_signature(subroutine.method_signature())) sdk_contract = sdk_abi.Contract(contract_name, method_list) contract = router.contract_construct() - assert sdk_contract.desc == contract.desc - assert sdk_contract.name == contract.name - assert sdk_contract.networks == contract.networks - for method in sdk_contract.methods: - assert method in contract.methods + assert contract == sdk_contract diff --git a/pyteal/compiler/compiler_test.py b/pyteal/compiler/compiler_test.py index dac8aefe5..4eb239673 100644 --- a/pyteal/compiler/compiler_test.py +++ b/pyteal/compiler/compiler_test.py @@ -2274,44 +2274,42 @@ def all_laid_to_args( router.add_method_handler(all_laid_to_args) - actual_ap_compiled, actual_csp_compiled, _ = router.compile_program( - version=6, assembleConstants=True - ) + actual_ap_compiled, actual_csp_compiled, _ = router.compile_program(version=6) + print(actual_ap_compiled) + print(actual_csp_compiled) expected_ap = """#pragma version 6 -intcblock 0 1 -bytecblock 0x151f7c75 txna ApplicationArgs 0 -pushbytes 0xfe6bdf69 // "add(uint64,uint64)uint64" +method "add(uint64,uint64)uint64" == bnz main_l12 txna ApplicationArgs 0 -pushbytes 0x78b488b7 // "sub(uint64,uint64)uint64" +method "sub(uint64,uint64)uint64" == bnz main_l11 txna ApplicationArgs 0 -pushbytes 0xe2f188c5 // "mul(uint64,uint64)uint64" +method "mul(uint64,uint64)uint64" == bnz main_l10 txna ApplicationArgs 0 -pushbytes 0x16e80f08 // "div(uint64,uint64)uint64" +method "div(uint64,uint64)uint64" == bnz main_l9 txna ApplicationArgs 0 -pushbytes 0x4dfc58ae // "mod(uint64,uint64)uint64" +method "mod(uint64,uint64)uint64" == bnz main_l8 txna ApplicationArgs 0 -pushbytes 0x487ce2fd // "all_laid_to_args(uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64)uint64" +method "all_laid_to_args(uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64)uint64" == bnz main_l7 err main_l7: txn OnCompletion -intc_0 // NoOp +int NoOp == txn ApplicationID -intc_0 // 0 +int 0 != && assert @@ -2360,11 +2358,11 @@ def all_laid_to_args( txna ApplicationArgs 15 store 44 load 44 -intc_0 // 0 +int 0 extract_uint64 store 45 load 44 -pushint 8 // 8 +int 8 extract_uint64 store 46 load 30 @@ -2385,19 +2383,19 @@ def all_laid_to_args( load 46 callsub alllaidtoargs_5 store 47 -bytec_0 // 0x151f7c75 +byte 0x151f7c75 load 47 itob concat log -intc_1 // 1 +int 1 return main_l8: txn OnCompletion -intc_0 // NoOp +int NoOp == txn ApplicationID -intc_0 // 0 +int 0 != && assert @@ -2411,19 +2409,19 @@ def all_laid_to_args( load 25 callsub mod_4 store 26 -bytec_0 // 0x151f7c75 +byte 0x151f7c75 load 26 itob concat log -intc_1 // 1 +int 1 return main_l9: txn OnCompletion -intc_0 // NoOp +int NoOp == txn ApplicationID -intc_0 // 0 +int 0 != && assert @@ -2437,19 +2435,19 @@ def all_laid_to_args( load 19 callsub div_3 store 20 -bytec_0 // 0x151f7c75 +byte 0x151f7c75 load 20 itob concat log -intc_1 // 1 +int 1 return main_l10: txn OnCompletion -intc_0 // NoOp +int NoOp == txn ApplicationID -intc_0 // 0 +int 0 != && assert @@ -2463,19 +2461,19 @@ def all_laid_to_args( load 13 callsub mul_2 store 14 -bytec_0 // 0x151f7c75 +byte 0x151f7c75 load 14 itob concat log -intc_1 // 1 +int 1 return main_l11: txn OnCompletion -intc_0 // NoOp +int NoOp == txn ApplicationID -intc_0 // 0 +int 0 != && assert @@ -2489,19 +2487,19 @@ def all_laid_to_args( load 7 callsub sub_1 store 8 -bytec_0 // 0x151f7c75 +byte 0x151f7c75 load 8 itob concat log -intc_1 // 1 +int 1 return main_l12: txn OnCompletion -intc_0 // NoOp +int NoOp == txn ApplicationID -intc_0 // 0 +int 0 != && assert @@ -2515,12 +2513,12 @@ def all_laid_to_args( load 1 callsub add_0 store 2 -bytec_0 // 0x151f7c75 +byte 0x151f7c75 load 2 itob concat log -intc_1 // 1 +int 1 return // add @@ -2633,17 +2631,16 @@ def all_laid_to_args( assert expected_ap == actual_ap_compiled expected_csp = """#pragma version 6 -intcblock 0 txn NumAppArgs -intc_0 // 0 +int 0 == bnz main_l2 err main_l2: txn ApplicationID -intc_0 // 0 +int 0 != assert -pushint 1 // 1 +int 1 return""".strip() assert expected_csp == actual_csp_compiled From 1753a944551c4ea907f73e1edeeef539da4a5f94 Mon Sep 17 00:00:00 2001 From: Ben Guidarelli Date: Tue, 31 May 2022 10:01:30 -0400 Subject: [PATCH 174/188] Add a check in `method_signature` to disallow reference return types (#368) * new ast builder * adding check in method_signature to raise error if its attempted to be called on method with reference type as return value * use type spec instead of str * adding recursive type checking method and using it in subroutine method signature * Adding one more test case for extra nesting * appease the linter Co-authored-by: Hang Su --- pyteal/ast/abi/__init__.py | 2 ++ pyteal/ast/abi/util.py | 25 ++++++++++++++++++++++++- pyteal/ast/subroutine.py | 11 +++++++++++ pyteal/ast/subroutine_test.py | 33 ++++++++++++++++++++++++++++++++- 4 files changed, 69 insertions(+), 2 deletions(-) diff --git a/pyteal/ast/abi/__init__.py b/pyteal/ast/abi/__init__.py index 983cf7685..c789c3556 100644 --- a/pyteal/ast/abi/__init__.py +++ b/pyteal/ast/abi/__init__.py @@ -49,6 +49,7 @@ make, size_of, type_spec_from_annotation, + contains_type_spec, ) __all__ = [ @@ -103,4 +104,5 @@ "size_of", "algosdk_from_annotation", "algosdk_from_type_spec", + "contains_type_spec", ] diff --git a/pyteal/ast/abi/util.py b/pyteal/ast/abi/util.py index 5b39509df..b01675b6d 100644 --- a/pyteal/ast/abi/util.py +++ b/pyteal/ast/abi/util.py @@ -1,4 +1,4 @@ -from typing import TypeVar, Any, Literal, get_origin, get_args, cast +from typing import Sequence, TypeVar, Any, Literal, get_origin, get_args, cast import algosdk.abi @@ -235,6 +235,29 @@ def type_spec_from_annotation(annotation: Any) -> TypeSpec: T = TypeVar("T", bound=BaseType) +def contains_type_spec(ts: TypeSpec, targets: Sequence[TypeSpec]) -> bool: + from pyteal.ast.abi.array_dynamic import DynamicArrayTypeSpec + from pyteal.ast.abi.array_static import StaticArrayTypeSpec + from pyteal.ast.abi.tuple import TupleTypeSpec + + stack: list[TypeSpec] = [ts] + + while stack: + current = stack.pop() + if current in targets: + return True + + match current: + case TupleTypeSpec(): + stack.extend(current.value_type_specs()) + case DynamicArrayTypeSpec(): + stack.append(current.value_type_spec()) + case StaticArrayTypeSpec(): + stack.append(current.value_type_spec()) + + return False + + def size_of(t: type[T]) -> int: """Get the size in bytes of an ABI type. Must be a static type""" diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index 75c98ae76..221de6306 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -595,6 +595,17 @@ def method_signature(self, overriding_name: str = None) -> str: "Only registrable methods may return a method signature" ) + ret_type = self.type_of() + if isinstance(ret_type, abi.TypeSpec) and abi.contains_type_spec( + ret_type, + [ + abi.AccountTypeSpec(), + abi.AssetTypeSpec(), + abi.ApplicationTypeSpec(), + ], + ): + raise TealInputError("Reference types may not be used as return values") + args = [str(v) for v in self.subroutine.abi_args.values()] if overriding_name is None: overriding_name = self.name() diff --git a/pyteal/ast/subroutine_test.py b/pyteal/ast/subroutine_test.py index 3d1934cdf..a58df675f 100644 --- a/pyteal/ast/subroutine_test.py +++ b/pyteal/ast/subroutine_test.py @@ -5,7 +5,7 @@ from dataclasses import dataclass import pyteal as pt -from pyteal.ast.subroutine import evaluate_subroutine +from pyteal.ast.subroutine import ABIReturnSubroutine, evaluate_subroutine options = pt.CompileOptions(version=5) @@ -237,6 +237,37 @@ def fn_w_tuple1arg( case.definition.method_signature() +def test_subroutine_return_reference(): + @ABIReturnSubroutine + def invalid_ret_type(*, output: pt.abi.Account): + return output.set(0) + + with pytest.raises(pt.TealInputError): + invalid_ret_type.method_signature() + + @ABIReturnSubroutine + def invalid_ret_type_collection( + *, output: pt.abi.Tuple2[pt.abi.Account, pt.abi.Uint64] + ): + return output.set(pt.abi.Account(), pt.abi.Uint64()) + + with pytest.raises(pt.TealInputError): + invalid_ret_type_collection.method_signature() + + @ABIReturnSubroutine + def invalid_ret_type_collection_nested( + *, output: pt.abi.DynamicArray[pt.abi.Tuple2[pt.abi.Account, pt.abi.Uint64]] + ): + return output.set( + pt.abi.make( + pt.abi.DynamicArray[pt.abi.Tuple2[pt.abi.Account, pt.abi.Uint64]] + ) + ) + + with pytest.raises(pt.TealInputError): + invalid_ret_type_collection_nested.method_signature() + + def test_subroutine_definition_validate(): """ DFS through SubroutineDefinition.validate()'s logic From 5b9c54761c140a68e19755aa4341f2d7f09ca038 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Tue, 31 May 2022 14:16:30 -0400 Subject: [PATCH 175/188] add new compiler tests for router --- pyteal/compiler/compiler_test.py | 857 ++++++++++++++++++++++++++++--- 1 file changed, 772 insertions(+), 85 deletions(-) diff --git a/pyteal/compiler/compiler_test.py b/pyteal/compiler/compiler_test.py index 4eb239673..2c5b4159e 100644 --- a/pyteal/compiler/compiler_test.py +++ b/pyteal/compiler/compiler_test.py @@ -2196,115 +2196,763 @@ def access_b4_store(magic_num: pt.abi.Uint64, *, output: pt.abi.Uint64): def test_router_app(): + def add_methods_to_router(router: pt.Router): + @pt.ABIReturnSubroutine + def add( + a: pt.abi.Uint64, b: pt.abi.Uint64, *, output: pt.abi.Uint64 + ) -> pt.Expr: + return output.set(a.get() + b.get()) + + router.add_method_handler(add) + + @pt.ABIReturnSubroutine + def sub( + a: pt.abi.Uint64, b: pt.abi.Uint64, *, output: pt.abi.Uint64 + ) -> pt.Expr: + return output.set(a.get() - b.get()) + + router.add_method_handler(sub) + + @pt.ABIReturnSubroutine + def mul( + a: pt.abi.Uint64, b: pt.abi.Uint64, *, output: pt.abi.Uint64 + ) -> pt.Expr: + return output.set(a.get() * b.get()) + + router.add_method_handler(mul) + + @pt.ABIReturnSubroutine + def div( + a: pt.abi.Uint64, b: pt.abi.Uint64, *, output: pt.abi.Uint64 + ) -> pt.Expr: + return output.set(a.get() / b.get()) + + router.add_method_handler(div) + + @pt.ABIReturnSubroutine + def mod( + a: pt.abi.Uint64, b: pt.abi.Uint64, *, output: pt.abi.Uint64 + ) -> pt.Expr: + return output.set(a.get() % b.get()) + + router.add_method_handler(mod) + + @pt.ABIReturnSubroutine + def all_laid_to_args( + _a: pt.abi.Uint64, + _b: pt.abi.Uint64, + _c: pt.abi.Uint64, + _d: pt.abi.Uint64, + _e: pt.abi.Uint64, + _f: pt.abi.Uint64, + _g: pt.abi.Uint64, + _h: pt.abi.Uint64, + _i: pt.abi.Uint64, + _j: pt.abi.Uint64, + _k: pt.abi.Uint64, + _l: pt.abi.Uint64, + _m: pt.abi.Uint64, + _n: pt.abi.Uint64, + _o: pt.abi.Uint64, + _p: pt.abi.Uint64, + *, + output: pt.abi.Uint64, + ): + return output.set( + _a.get() + + _b.get() + + _c.get() + + _d.get() + + _e.get() + + _f.get() + + _g.get() + + _h.get() + + _i.get() + + _j.get() + + _k.get() + + _l.get() + + _m.get() + + _n.get() + + _o.get() + + _p.get() + ) + + router.add_method_handler(all_laid_to_args) + + @pt.ABIReturnSubroutine + def empty_return_subroutine() -> pt.Expr: + return pt.Log(pt.Bytes("appear in both approval and clear state")) + + router.add_method_handler( + empty_return_subroutine, + call_configs=pt.MethodConfig( + opt_in=pt.CallConfig.ALL, close_out=pt.CallConfig.CALL + ), + ) + + @pt.ABIReturnSubroutine + def log_1(*, output: pt.abi.Uint64) -> pt.Expr: + return output.set(1) + + router.add_method_handler( + log_1, + call_configs=pt.MethodConfig( + opt_in=pt.CallConfig.CALL, clear_state=pt.CallConfig.CALL + ), + ) + + @pt.ABIReturnSubroutine + def log_creation(*, output: pt.abi.String) -> pt.Expr: + return output.set("logging creation") + + router.add_method_handler( + log_creation, call_configs=pt.MethodConfig(no_op=pt.CallConfig.CREATE) + ) + on_completion_actions = pt.BareCallActions( - clear_state=pt.OnCompleteAction.call_only(pt.Approve()) + opt_in=pt.OnCompleteAction.call_only(pt.Log(pt.Bytes("optin call"))), + clear_state=pt.OnCompleteAction.call_only(pt.Approve()), ) - router = pt.Router("ASimpleQuestionablyRobustContract", on_completion_actions) - - @pt.ABIReturnSubroutine - def add(a: pt.abi.Uint64, b: pt.abi.Uint64, *, output: pt.abi.Uint64) -> pt.Expr: - return output.set(a.get() + b.get()) + _router_with_oc = pt.Router( + "ASimpleQuestionablyRobustContract", on_completion_actions + ) + add_methods_to_router(_router_with_oc) + ( + actual_ap_with_oc_compiled, + actual_csp_with_oc_compiled, + _, + ) = _router_with_oc.compile_program(version=6) + + expected_ap_with_oc = """#pragma version 6 +txn NumAppArgs +int 0 +== +bnz main_l20 +txna ApplicationArgs 0 +method "add(uint64,uint64)uint64" +== +bnz main_l19 +txna ApplicationArgs 0 +method "sub(uint64,uint64)uint64" +== +bnz main_l18 +txna ApplicationArgs 0 +method "mul(uint64,uint64)uint64" +== +bnz main_l17 +txna ApplicationArgs 0 +method "div(uint64,uint64)uint64" +== +bnz main_l16 +txna ApplicationArgs 0 +method "mod(uint64,uint64)uint64" +== +bnz main_l15 +txna ApplicationArgs 0 +method "all_laid_to_args(uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64)uint64" +== +bnz main_l14 +txna ApplicationArgs 0 +method "empty_return_subroutine()void" +== +bnz main_l13 +txna ApplicationArgs 0 +method "log_1()uint64" +== +bnz main_l12 +txna ApplicationArgs 0 +method "log_creation()string" +== +bnz main_l11 +err +main_l11: +txn OnCompletion +int NoOp +== +txn ApplicationID +int 0 +== +&& +assert +callsub logcreation_8 +store 67 +byte 0x151f7c75 +load 67 +concat +log +int 1 +return +main_l12: +txn OnCompletion +int NoOp +== +txn ApplicationID +int 0 +!= +&& +txn OnCompletion +int OptIn +== +txn ApplicationID +int 0 +!= +&& +|| +assert +callsub log1_7 +store 65 +byte 0x151f7c75 +load 65 +itob +concat +log +int 1 +return +main_l13: +txn OnCompletion +int NoOp +== +txn ApplicationID +int 0 +!= +&& +txn OnCompletion +int OptIn +== +|| +txn OnCompletion +int CloseOut +== +txn ApplicationID +int 0 +!= +&& +|| +assert +callsub emptyreturnsubroutine_6 +int 1 +return +main_l14: +txn OnCompletion +int NoOp +== +txn ApplicationID +int 0 +!= +&& +assert +txna ApplicationArgs 1 +btoi +store 30 +txna ApplicationArgs 2 +btoi +store 31 +txna ApplicationArgs 3 +btoi +store 32 +txna ApplicationArgs 4 +btoi +store 33 +txna ApplicationArgs 5 +btoi +store 34 +txna ApplicationArgs 6 +btoi +store 35 +txna ApplicationArgs 7 +btoi +store 36 +txna ApplicationArgs 8 +btoi +store 37 +txna ApplicationArgs 9 +btoi +store 38 +txna ApplicationArgs 10 +btoi +store 39 +txna ApplicationArgs 11 +btoi +store 40 +txna ApplicationArgs 12 +btoi +store 41 +txna ApplicationArgs 13 +btoi +store 42 +txna ApplicationArgs 14 +btoi +store 43 +txna ApplicationArgs 15 +store 44 +load 44 +int 0 +extract_uint64 +store 45 +load 44 +int 8 +extract_uint64 +store 46 +load 30 +load 31 +load 32 +load 33 +load 34 +load 35 +load 36 +load 37 +load 38 +load 39 +load 40 +load 41 +load 42 +load 43 +load 45 +load 46 +callsub alllaidtoargs_5 +store 47 +byte 0x151f7c75 +load 47 +itob +concat +log +int 1 +return +main_l15: +txn OnCompletion +int NoOp +== +txn ApplicationID +int 0 +!= +&& +assert +txna ApplicationArgs 1 +btoi +store 24 +txna ApplicationArgs 2 +btoi +store 25 +load 24 +load 25 +callsub mod_4 +store 26 +byte 0x151f7c75 +load 26 +itob +concat +log +int 1 +return +main_l16: +txn OnCompletion +int NoOp +== +txn ApplicationID +int 0 +!= +&& +assert +txna ApplicationArgs 1 +btoi +store 18 +txna ApplicationArgs 2 +btoi +store 19 +load 18 +load 19 +callsub div_3 +store 20 +byte 0x151f7c75 +load 20 +itob +concat +log +int 1 +return +main_l17: +txn OnCompletion +int NoOp +== +txn ApplicationID +int 0 +!= +&& +assert +txna ApplicationArgs 1 +btoi +store 12 +txna ApplicationArgs 2 +btoi +store 13 +load 12 +load 13 +callsub mul_2 +store 14 +byte 0x151f7c75 +load 14 +itob +concat +log +int 1 +return +main_l18: +txn OnCompletion +int NoOp +== +txn ApplicationID +int 0 +!= +&& +assert +txna ApplicationArgs 1 +btoi +store 6 +txna ApplicationArgs 2 +btoi +store 7 +load 6 +load 7 +callsub sub_1 +store 8 +byte 0x151f7c75 +load 8 +itob +concat +log +int 1 +return +main_l19: +txn OnCompletion +int NoOp +== +txn ApplicationID +int 0 +!= +&& +assert +txna ApplicationArgs 1 +btoi +store 0 +txna ApplicationArgs 2 +btoi +store 1 +load 0 +load 1 +callsub add_0 +store 2 +byte 0x151f7c75 +load 2 +itob +concat +log +int 1 +return +main_l20: +txn OnCompletion +int OptIn +== +bnz main_l22 +err +main_l22: +txn ApplicationID +int 0 +!= +assert +byte "optin call" +log +int 1 +return - router.add_method_handler(add) +// add +add_0: +store 4 +store 3 +load 3 +load 4 ++ +store 5 +load 5 +retsub - @pt.ABIReturnSubroutine - def sub(a: pt.abi.Uint64, b: pt.abi.Uint64, *, output: pt.abi.Uint64) -> pt.Expr: - return output.set(a.get() - b.get()) +// sub +sub_1: +store 10 +store 9 +load 9 +load 10 +- +store 11 +load 11 +retsub - router.add_method_handler(sub) +// mul +mul_2: +store 16 +store 15 +load 15 +load 16 +* +store 17 +load 17 +retsub - @pt.ABIReturnSubroutine - def mul(a: pt.abi.Uint64, b: pt.abi.Uint64, *, output: pt.abi.Uint64) -> pt.Expr: - return output.set(a.get() * b.get()) +// div +div_3: +store 22 +store 21 +load 21 +load 22 +/ +store 23 +load 23 +retsub - router.add_method_handler(mul) +// mod +mod_4: +store 28 +store 27 +load 27 +load 28 +% +store 29 +load 29 +retsub - @pt.ABIReturnSubroutine - def div(a: pt.abi.Uint64, b: pt.abi.Uint64, *, output: pt.abi.Uint64) -> pt.Expr: - return output.set(a.get() / b.get()) +// all_laid_to_args +alllaidtoargs_5: +store 63 +store 62 +store 61 +store 60 +store 59 +store 58 +store 57 +store 56 +store 55 +store 54 +store 53 +store 52 +store 51 +store 50 +store 49 +store 48 +load 48 +load 49 ++ +load 50 ++ +load 51 ++ +load 52 ++ +load 53 ++ +load 54 ++ +load 55 ++ +load 56 ++ +load 57 ++ +load 58 ++ +load 59 ++ +load 60 ++ +load 61 ++ +load 62 ++ +load 63 ++ +store 64 +load 64 +retsub - router.add_method_handler(div) +// empty_return_subroutine +emptyreturnsubroutine_6: +byte "appear in both approval and clear state" +log +retsub - @pt.ABIReturnSubroutine - def mod(a: pt.abi.Uint64, b: pt.abi.Uint64, *, output: pt.abi.Uint64) -> pt.Expr: - return output.set(a.get() % b.get()) +// log_1 +log1_7: +int 1 +store 66 +load 66 +retsub - router.add_method_handler(mod) +// log_creation +logcreation_8: +byte "logging creation" +len +itob +extract 6 0 +byte "logging creation" +concat +store 68 +load 68 +retsub""".strip() + assert expected_ap_with_oc == actual_ap_with_oc_compiled - @pt.ABIReturnSubroutine - def all_laid_to_args( - _a: pt.abi.Uint64, - _b: pt.abi.Uint64, - _c: pt.abi.Uint64, - _d: pt.abi.Uint64, - _e: pt.abi.Uint64, - _f: pt.abi.Uint64, - _g: pt.abi.Uint64, - _h: pt.abi.Uint64, - _i: pt.abi.Uint64, - _j: pt.abi.Uint64, - _k: pt.abi.Uint64, - _l: pt.abi.Uint64, - _m: pt.abi.Uint64, - _n: pt.abi.Uint64, - _o: pt.abi.Uint64, - _p: pt.abi.Uint64, - *, - output: pt.abi.Uint64, - ): - return output.set( - _a.get() - + _b.get() - + _c.get() - + _d.get() - + _e.get() - + _f.get() - + _g.get() - + _h.get() - + _i.get() - + _j.get() - + _k.get() - + _l.get() - + _m.get() - + _n.get() - + _o.get() - + _p.get() - ) + expected_csp_with_oc = """#pragma version 6 +txn NumAppArgs +int 0 +== +bnz main_l4 +txna ApplicationArgs 0 +method "log_1()uint64" +== +bnz main_l3 +err +main_l3: +txn ApplicationID +int 0 +!= +assert +callsub log1_0 +store 1 +byte 0x151f7c75 +load 1 +itob +concat +log +int 1 +return +main_l4: +txn ApplicationID +int 0 +!= +assert +int 1 +return - router.add_method_handler(all_laid_to_args) +// log_1 +log1_0: +int 1 +store 0 +load 0 +retsub""".strip() + assert expected_csp_with_oc == actual_csp_with_oc_compiled - actual_ap_compiled, actual_csp_compiled, _ = router.compile_program(version=6) - print(actual_ap_compiled) - print(actual_csp_compiled) + _router_without_oc = pt.Router("yetAnotherContractConstructedFromRouter") + add_methods_to_router(_router_without_oc) + ( + actual_ap_without_oc_compiled, + actual_csp_without_oc_compiled, + _, + ) = _router_without_oc.compile_program(version=6) - expected_ap = """#pragma version 6 + expected_ap_without_oc = """#pragma version 6 txna ApplicationArgs 0 method "add(uint64,uint64)uint64" == -bnz main_l12 +bnz main_l18 txna ApplicationArgs 0 method "sub(uint64,uint64)uint64" == -bnz main_l11 +bnz main_l17 txna ApplicationArgs 0 method "mul(uint64,uint64)uint64" == -bnz main_l10 +bnz main_l16 txna ApplicationArgs 0 method "div(uint64,uint64)uint64" == -bnz main_l9 +bnz main_l15 txna ApplicationArgs 0 method "mod(uint64,uint64)uint64" == -bnz main_l8 +bnz main_l14 txna ApplicationArgs 0 method "all_laid_to_args(uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64)uint64" == -bnz main_l7 +bnz main_l13 +txna ApplicationArgs 0 +method "empty_return_subroutine()void" +== +bnz main_l12 +txna ApplicationArgs 0 +method "log_1()uint64" +== +bnz main_l11 +txna ApplicationArgs 0 +method "log_creation()string" +== +bnz main_l10 err -main_l7: +main_l10: +txn OnCompletion +int NoOp +== +txn ApplicationID +int 0 +== +&& +assert +callsub logcreation_8 +store 67 +byte 0x151f7c75 +load 67 +concat +log +int 1 +return +main_l11: +txn OnCompletion +int NoOp +== +txn ApplicationID +int 0 +!= +&& +txn OnCompletion +int OptIn +== +txn ApplicationID +int 0 +!= +&& +|| +assert +callsub log1_7 +store 65 +byte 0x151f7c75 +load 65 +itob +concat +log +int 1 +return +main_l12: +txn OnCompletion +int NoOp +== +txn ApplicationID +int 0 +!= +&& +txn OnCompletion +int OptIn +== +|| +txn OnCompletion +int CloseOut +== +txn ApplicationID +int 0 +!= +&& +|| +assert +callsub emptyreturnsubroutine_6 +int 1 +return +main_l13: txn OnCompletion int NoOp == @@ -2390,7 +3038,7 @@ def all_laid_to_args( log int 1 return -main_l8: +main_l14: txn OnCompletion int NoOp == @@ -2416,7 +3064,7 @@ def all_laid_to_args( log int 1 return -main_l9: +main_l15: txn OnCompletion int NoOp == @@ -2442,7 +3090,7 @@ def all_laid_to_args( log int 1 return -main_l10: +main_l16: txn OnCompletion int NoOp == @@ -2468,7 +3116,7 @@ def all_laid_to_args( log int 1 return -main_l11: +main_l17: txn OnCompletion int NoOp == @@ -2494,7 +3142,7 @@ def all_laid_to_args( log int 1 return -main_l12: +main_l18: txn OnCompletion int NoOp == @@ -2627,12 +3275,37 @@ def all_laid_to_args( + store 64 load 64 +retsub + +// empty_return_subroutine +emptyreturnsubroutine_6: +byte "appear in both approval and clear state" +log +retsub + +// log_1 +log1_7: +int 1 +store 66 +load 66 +retsub + +// log_creation +logcreation_8: +byte "logging creation" +len +itob +extract 6 0 +byte "logging creation" +concat +store 68 +load 68 retsub""".strip() - assert expected_ap == actual_ap_compiled + assert actual_ap_without_oc_compiled == expected_ap_without_oc - expected_csp = """#pragma version 6 -txn NumAppArgs -int 0 + expected_csp_without_oc = """#pragma version 6 +txna ApplicationArgs 0 +method "log_1()uint64" == bnz main_l2 err @@ -2641,6 +3314,20 @@ def all_laid_to_args( int 0 != assert +callsub log1_0 +store 1 +byte 0x151f7c75 +load 1 +itob +concat +log +int 1 +return + +// log_1 +log1_0: int 1 -return""".strip() - assert expected_csp == actual_csp_compiled +store 0 +load 0 +retsub""".strip() + assert actual_csp_without_oc_compiled == expected_csp_without_oc From 3a76144fbbf3b0f3fbc02e64cc6f2abd19eab249 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Tue, 31 May 2022 14:44:45 -0400 Subject: [PATCH 176/188] fix one test for wrapping bare calls --- pyteal/ast/router_test.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/pyteal/ast/router_test.py b/pyteal/ast/router_test.py index 20cf4af4d..ad54feaf0 100644 --- a/pyteal/ast/router_test.py +++ b/pyteal/ast/router_test.py @@ -417,16 +417,21 @@ def test_wrap_handler_bare_call(): ] for bare_call in BARE_CALL_CASES: wrapped: pt.Expr = ASTBuilder.wrap_handler(False, bare_call) + expected: pt.Expr match bare_call: case pt.Expr(): if bare_call.has_return(): - assert wrapped == bare_call + expected = bare_call else: - assert wrapped == pt.Seq(bare_call, pt.Approve()) + expected = pt.Seq(bare_call, pt.Approve()) case pt.SubroutineFnWrapper() | pt.ABIReturnSubroutine(): - assert wrapped == pt.Seq(bare_call(), pt.Approve()) + expected = pt.Seq(bare_call(), pt.Approve()) case _: raise pt.TealInputError("how you got here?") + wrapped_assemble = assemble_helper(wrapped) + wrapped_helper = assemble_helper(expected) + with pt.TealComponent.Context.ignoreExprEquality(): + assert wrapped_assemble == wrapped_helper ERROR_CASES = [ ( From eee9545d798471705a58da7f0e779823c526e5c2 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Tue, 31 May 2022 15:30:11 -0400 Subject: [PATCH 177/188] comments fixing --- pyteal/ast/router.py | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/pyteal/ast/router.py b/pyteal/ast/router.py index 6954b8720..ff80636ef 100644 --- a/pyteal/ast/router.py +++ b/pyteal/ast/router.py @@ -31,7 +31,7 @@ class CallConfig(IntFlag): """ - CallConfigs: a "bitset"-like class for more fine-grained control over + CallConfig: a "bitset"-like class for more fine-grained control over `call or create` for a method about an OnComplete case. This enumeration class allows for specifying one of the four following cases: @@ -67,19 +67,10 @@ def condition_under_config(self) -> Expr | int: @dataclass(frozen=True) class MethodConfig: """ - MethodConfig keep track of one method registration's CallConfigs for all OnComplete cases. + MethodConfig keep track of one method's CallConfigs for all OnComplete cases. - By ARC-0004 spec: - If an Application is called with greater than zero Application call arguments (NOT a bare Application call), - the Application MUST always treat the first argument as a method selector and invoke the specified method, - regardless of the OnCompletion action of the Application call. - This applies to Application creation transactions as well, where the supplied Application ID is 0. - - The `CallConfigs` implementation generalized contract method call such that method call is allowed - for certain OnCompletions. - - The `arc4_compliant` method constructs a `CallConfigs` that allows a method call to be executed - under any OnCompletion, which is "arc4-compliant". + The `MethodConfig` implementation generalized contract method call such that the registered + method call is paired with certain OnCompletion conditions and creation conditions. """ no_op: CallConfig = field(kw_only=True, default=CallConfig.CALL) @@ -545,6 +536,18 @@ def method( update_application: CallConfig = CallConfig.NEVER, delete_application: CallConfig = CallConfig.NEVER, ): + """ + A decorator style method registration by decorating over a python function, + which is internally converted to ABIReturnSubroutine, and taking keyword arguments + for each OnCompletes' `CallConfig`. + + NOTE: + By default, all OnCompletes other than `NoOp` are set to `CallConfig.NEVER`, + while `no_op` field is always `CALL`. + If one wants to change `no_op`, we need to change `no_op = CallConfig.ALL`, + for example, as a decorator argument. + """ + def wrap(_func): wrapped_subroutine = ABIReturnSubroutine(_func) call_configs = MethodConfig( From b2c025e84237ccf467fac9f32a2da5912df12878 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Tue, 31 May 2022 17:23:46 -0400 Subject: [PATCH 178/188] simplified oc-action post_init check --- pyteal/ast/router.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pyteal/ast/router.py b/pyteal/ast/router.py index ff80636ef..783972a6d 100644 --- a/pyteal/ast/router.py +++ b/pyteal/ast/router.py @@ -142,9 +142,7 @@ class OnCompleteAction: call_config: CallConfig = field(kw_only=True, default=CallConfig.NEVER) def __post_init__(self): - if (self.call_config == CallConfig.NEVER and self.action) or ( - self.call_config != CallConfig.NEVER and not self.action - ): + if bool(self.call_config) ^ bool(self.action): raise TealInputError( f"action {self.action} and call_config {self.call_config} contradicts" ) From c6019b0cf9230e04e355982ece3c5079f4b9cc51 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Tue, 31 May 2022 17:38:19 -0400 Subject: [PATCH 179/188] naming variables --- pyteal/ast/router.py | 24 +++++++++++++----------- pyteal/compiler/compiler_test.py | 6 +++--- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/pyteal/ast/router.py b/pyteal/ast/router.py index 783972a6d..c18bdfa42 100644 --- a/pyteal/ast/router.py +++ b/pyteal/ast/router.py @@ -144,7 +144,7 @@ class OnCompleteAction: def __post_init__(self): if bool(self.call_config) ^ bool(self.action): raise TealInputError( - f"action {self.action} and call_config {self.call_config} contradicts" + f"action {self.action} and call_config {str(self.call_config)} contradicts" ) @staticmethod @@ -229,7 +229,9 @@ def approval_construction(self) -> Optional[Expr]: wrapped_handler, ) case _: - raise TealInternalError(f"Unexpected CallConfig: {oca.call_config}") + raise TealInternalError( + f"Unexpected CallConfig: {str(oca.call_config)}" + ) conditions_n_branches.append( CondNode( Txn.on_completion() == oc, @@ -263,7 +265,7 @@ def clear_state_construction(self) -> Optional[Expr]: ) case _: raise TealInternalError( - f"Unexpected CallConfig: {self.clear_state.call_config}" + f"Unexpected CallConfig: {str(self.clear_state.call_config)}" ) @@ -484,14 +486,14 @@ def add_method_handler( self, method_call: ABIReturnSubroutine, overriding_name: str = None, - call_configs: MethodConfig = MethodConfig(), + method_config: MethodConfig = MethodConfig(), ) -> None: if not isinstance(method_call, ABIReturnSubroutine): raise TealInputError( "for adding method handler, must be ABIReturnSubroutine" ) method_signature = method_call.method_signature(overriding_name) - if call_configs.is_never(): + if method_config.is_never(): raise TealInputError( f"registered method {method_signature} is never executed" ) @@ -507,13 +509,13 @@ def add_method_handler( self.method_sig_to_selector[method_signature] = method_selector self.method_selector_to_sig[method_selector] = method_signature - if call_configs.is_arc4_compliant(): + if method_config.is_arc4_compliant(): self.approval_ast.add_method_to_ast(method_signature, 1, method_call) self.clear_state_ast.add_method_to_ast(method_signature, 1, method_call) return - method_approval_cond = call_configs.approval_cond() - method_clear_state_cond = call_configs.clear_state_cond() + method_approval_cond = method_config.approval_cond() + method_clear_state_cond = method_config.clear_state_cond() self.approval_ast.add_method_to_ast( method_signature, method_approval_cond, method_call ) @@ -599,7 +601,7 @@ def compile_program( self, *, version: int = DEFAULT_TEAL_VERSION, - assembleConstants: bool = False, + assemble_constants: bool = False, optimize: OptimizeOptions = None, ) -> tuple[str, str, sdk_abi.Contract]: """ @@ -616,14 +618,14 @@ def compile_program( ap, Mode.Application, version=version, - assembleConstants=assembleConstants, + assembleConstants=assemble_constants, optimize=optimize, ) csp_compiled = compileTeal( csp, Mode.Application, version=version, - assembleConstants=assembleConstants, + assembleConstants=assemble_constants, optimize=optimize, ) return ap_compiled, csp_compiled, contract diff --git a/pyteal/compiler/compiler_test.py b/pyteal/compiler/compiler_test.py index 2c5b4159e..fbceff073 100644 --- a/pyteal/compiler/compiler_test.py +++ b/pyteal/compiler/compiler_test.py @@ -2285,7 +2285,7 @@ def empty_return_subroutine() -> pt.Expr: router.add_method_handler( empty_return_subroutine, - call_configs=pt.MethodConfig( + method_config=pt.MethodConfig( opt_in=pt.CallConfig.ALL, close_out=pt.CallConfig.CALL ), ) @@ -2296,7 +2296,7 @@ def log_1(*, output: pt.abi.Uint64) -> pt.Expr: router.add_method_handler( log_1, - call_configs=pt.MethodConfig( + method_config=pt.MethodConfig( opt_in=pt.CallConfig.CALL, clear_state=pt.CallConfig.CALL ), ) @@ -2306,7 +2306,7 @@ def log_creation(*, output: pt.abi.String) -> pt.Expr: return output.set("logging creation") router.add_method_handler( - log_creation, call_configs=pt.MethodConfig(no_op=pt.CallConfig.CREATE) + log_creation, method_config=pt.MethodConfig(no_op=pt.CallConfig.CREATE) ) on_completion_actions = pt.BareCallActions( From 5e41d1378656c6c21c6350a8ee5ddb4f6f0ef4c5 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Tue, 31 May 2022 17:54:06 -0400 Subject: [PATCH 180/188] comments --- pyteal/ast/router_test.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/pyteal/ast/router_test.py b/pyteal/ast/router_test.py index ad54feaf0..a2c296765 100644 --- a/pyteal/ast/router_test.py +++ b/pyteal/ast/router_test.py @@ -190,11 +190,26 @@ def power_set(no_dup_list: list, length_override: int = None): def full_perm_gen(non_dup_list: list, perm_length: int): + """ + This function serves as a generator for all possible vectors of length `perm_length`, + each of whose entries are one of the elements in `non_dup_list`, + which is a list of non-duplicated elements. + + Args: + non_dup_list: must be a list of elements with no duplication + perm_length: must be a non-negative number indicating resulting length of the vector + """ if perm_length < 0: raise pt.TealInputError("input permutation length must be non-negative") + elif len(set(non_dup_list)) != len(non_dup_list): + raise pt.TealInputError(f"input non_dup_list {non_dup_list} has duplications") elif perm_length == 0: yield [] return + # we can index all possible cases of vectors with an index in range + # [0, |non_dup_list| ^ perm_length - 1] + # by converting an index into |non_dup_list|-based number, + # we can get the vector mapped by the index. for index in range(len(non_dup_list) ** perm_length): index_list_basis = [] temp = index From 197a9d3e5146105a6bc1f9119e165405f8b20eb8 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Tue, 31 May 2022 18:05:32 -0400 Subject: [PATCH 181/188] comments --- pyteal/ast/router_test.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/pyteal/ast/router_test.py b/pyteal/ast/router_test.py index a2c296765..dae7e037d 100644 --- a/pyteal/ast/router_test.py +++ b/pyteal/ast/router_test.py @@ -181,6 +181,20 @@ def not_registrable(lhs: pt.abi.Uint64, rhs: pt.Expr, *, output: pt.abi.Uint64): def power_set(no_dup_list: list, length_override: int = None): + """ + This function serves as a generator for all possible elements in power_set + over `non_dup_list`, which is a list of non-duplicated elements (matches property of a set). + + The cardinality of a powerset is 2^|non_dup_list|, so we can iterate from 0 to 2^|non_dup_list| - 1 + to index each element in such power_set. + By binary representation of each index, we can see it as an allowance over each element in `no_dup_list`, + and generate a unique subset of `non_dup_list`, which yields as an element of power_set of `no_dup_list`. + + Args: + no_dup_list: a list of elements with no duplication + length_override: a number indicating the largest size of super_set element, + must be in range [1, len(no_dup_list)]. + """ if length_override is None: length_override = len(no_dup_list) assert 1 <= length_override <= len(no_dup_list) From dbffe615bb91fe1d017af9d0bbff1a95d51dd7e5 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Wed, 1 Jun 2022 12:18:54 -0400 Subject: [PATCH 182/188] testcase for method config update --- pyteal/ast/router_test.py | 202 +++++++++++--------------------------- 1 file changed, 57 insertions(+), 145 deletions(-) diff --git a/pyteal/ast/router_test.py b/pyteal/ast/router_test.py index dae7e037d..7826ae053 100644 --- a/pyteal/ast/router_test.py +++ b/pyteal/ast/router_test.py @@ -1,10 +1,6 @@ import pyteal as pt from pyteal.ast.router import ASTBuilder - -import itertools import pytest - -# import random import typing import algosdk.abi as sdk_abi @@ -203,7 +199,7 @@ def power_set(no_dup_list: list, length_override: int = None): yield [elem for mask, elem in zip(masks, no_dup_list) if i & mask] -def full_perm_gen(non_dup_list: list, perm_length: int): +def full_ordered_combination_gen(non_dup_list: list, perm_length: int): """ This function serves as a generator for all possible vectors of length `perm_length`, each of whose entries are one of the elements in `non_dup_list`, @@ -280,23 +276,67 @@ def test_method_config(): assert all_mc.clear_state_cond() == 1 on_complete_pow_set = power_set(ON_COMPLETE_CASES) + approval_check_names_n_ocs = [ + (camel_to_snake(oc.name), oc) + for oc in ON_COMPLETE_CASES + if str(oc) != str(pt.OnComplete.ClearState) + ] for on_complete_set in on_complete_pow_set: oc_names = [camel_to_snake(oc.name) for oc in on_complete_set] - full_perm_call_configs_for_ocs = full_perm_gen( + ordered_call_configs = full_ordered_combination_gen( list(pt.CallConfig), len(on_complete_set) ) - for call_config in full_perm_call_configs_for_ocs: - mc = pt.MethodConfig(**dict(zip(oc_names, call_config))) - approval_list = [ - mc.no_op, - mc.opt_in, - mc.close_out, - mc.update_application, - mc.delete_application, + for call_configs in ordered_call_configs: + mc = pt.MethodConfig(**dict(zip(oc_names, call_configs))) + match mc.clear_state: + case pt.CallConfig.NEVER: + assert mc.clear_state_cond() == 0 + case pt.CallConfig.ALL: + assert mc.clear_state_cond() == 1 + case pt.CallConfig.CALL: + with pt.TealComponent.Context.ignoreExprEquality(): + assert assemble_helper( + mc.clear_state_cond() + ) == assemble_helper(pt.Txn.application_id() != pt.Int(0)) + case pt.CallConfig.CREATE: + with pt.TealComponent.Context.ignoreExprEquality(): + assert assemble_helper( + mc.clear_state_cond() + ) == assemble_helper(pt.Txn.application_id() == pt.Int(0)) + if mc.is_never() or all( + getattr(mc, i) == pt.CallConfig.NEVER + for i, _ in approval_check_names_n_ocs + ): + assert mc.approval_cond() == 0 + continue + elif mc.is_arc4_compliant() or all( + getattr(mc, i) == pt.CallConfig.ALL + for i, _ in approval_check_names_n_ocs + ): + assert mc.approval_cond() == 1 + continue + list_of_cc = [ + ( + typing.cast(pt.CallConfig, getattr(mc, i)).condition_under_config(), + oc, + ) + for i, oc in approval_check_names_n_ocs ] - len(approval_list) - - # method_config: pt.MethodConfig + list_of_expressions = [] + for expr_or_int, oc in list_of_cc: + match expr_or_int: + case pt.Expr(): + list_of_expressions.append( + pt.And(pt.Txn.on_completion() == oc, expr_or_int) + ) + case 0: + continue + case 1: + list_of_expressions.append(pt.Txn.on_completion() == oc) + with pt.TealComponent.Context.ignoreExprEquality(): + assert assemble_helper(mc.approval_cond()) == assemble_helper( + pt.Or(*list_of_expressions) + ) def test_on_complete_action(): @@ -309,134 +349,6 @@ def test_on_complete_action(): assert pt.OnCompleteAction.always(pt.Seq()).call_config == pt.CallConfig.ALL -def test_add_method(): - abi_subroutine_cases = [ - abi_ret - for abi_ret in GOOD_SUBROUTINE_CASES - if isinstance(abi_ret, pt.ABIReturnSubroutine) - ] - normal_subroutine = [ - subroutine - for subroutine in GOOD_SUBROUTINE_CASES - if not isinstance(subroutine, pt.ABIReturnSubroutine) - ] - router = pt.Router( - "routerForMethodTest", - pt.BareCallActions(clear_state=pt.OnCompleteAction.call_only(pt.Approve())), - ) - for subroutine in normal_subroutine: - with pytest.raises(pt.TealInputError) as must_be_abi: - router.add_method_handler(subroutine) - assert "for adding method handler, must be ABIReturnSubroutine" in str( - must_be_abi - ) - on_complete_pow_set = power_set(ON_COMPLETE_CASES) - for handler, on_complete_set in itertools.product( - abi_subroutine_cases, on_complete_pow_set - ): - full_perm_call_configs_for_ocs = full_perm_gen( - list(pt.CallConfig), len(on_complete_set) - ) - oc_names = [camel_to_snake(oc.name) for oc in on_complete_set] - for call_config in full_perm_call_configs_for_ocs: - method_call_configs: pt.MethodConfig = pt.MethodConfig( - **dict(zip(oc_names, call_config)) - ) - if method_call_configs.is_never(): - with pytest.raises(pt.TealInputError) as call_config_never: - router.add_method_handler(handler, None, method_call_configs) - assert "is never executed" in str(call_config_never) - continue - - # router.add_method_handler(handler, None, method_call_configs) - # on_create = method_call_configs.oc_under_call_config(pt.CallConfig.CREATE) - # on_call = method_call_configs.oc_under_call_config(pt.CallConfig.CALL) - - -# for subroutine, on_completes, is_creation in itertools.product( -# GOOD_SUBROUTINE_CASES, ON_COMPLETE_COMBINED_CASES, [False, True] -# ): -# if len(on_completes) == 0: -# with pytest.raises(pt.TealInputError) as err_no_oc: -# pt.Router.parse_conditions( -# subroutine if is_abi_subroutine else None, -# on_completes, -# is_creation, -# ) -# assert "on complete input should be non-empty list" in str(err_no_oc) -# continue -# -# mutated_on_completes = on_completes + [random.choice(on_completes)] -# with pytest.raises(pt.TealInputError) as err_dup_oc: -# pt.Router.parse_conditions( -# subroutine if is_abi_subroutine else None, -# mutated_on_completes, -# is_creation, -# ) -# assert "has duplicated on_complete(s)" in str(err_dup_oc) -# -# ( -# approval_condition_list, -# clear_state_condition_list, -# ) = pt.Router.parse_conditions( -# subroutine if is_abi_subroutine else None, -# on_completes, -# is_creation, -# ) -# -# if not oncomplete_is_in_oc_list(pt.OnComplete.ClearState, on_completes): -# assert len(clear_state_condition_list) == 0 -# -# assembled_ap_condition_list: list[pt.TealBlock] = [ -# assemble_helper(expr) for expr in approval_condition_list -# ] -# assembled_csp_condition_list: list[pt.TealBlock] = [ -# assemble_helper(expr) for expr in clear_state_condition_list -# ] -# if is_creation: -# creation_condition: pt.Expr = pt.Txn.application_id() == pt.Int(0) -# assembled_condition = assemble_helper(creation_condition) -# with pt.TealComponent.Context.ignoreExprEquality(): -# assert assembled_condition in assembled_ap_condition_list -# -# subroutine_arg_cond: pt.Expr -# if is_abi_subroutine: -# subroutine_arg_cond = ( -# pt.MethodSignature(typing.cast(str, method_sig)) -# == pt.Txn.application_args[0] -# ) -# else: -# subroutine_arg_cond = pt.Int(1) -# -# assembled_condition = assemble_helper(subroutine_arg_cond) -# with pt.TealComponent.Context.ignoreExprEquality(): -# if not ( -# len(on_completes) == 1 -# and oncomplete_is_in_oc_list(pt.OnComplete.ClearState, on_completes) -# ): -# assert assembled_condition in assembled_ap_condition_list -# -# if oncomplete_is_in_oc_list(pt.OnComplete.ClearState, on_completes): -# with pt.TealComponent.Context.ignoreExprEquality(): -# assert assembled_condition in assembled_csp_condition_list -# -# if len(on_completes) == 1 and oncomplete_is_in_oc_list( -# pt.OnComplete.ClearState, on_completes -# ): -# continue -# -# on_completes_cond: pt.Expr = pt.Or( -# *[ -# pt.Txn.on_completion() == oc -# for oc in on_completes -# if str(oc) != str(pt.OnComplete.ClearState) -# ] -# ) -# on_completes_cond_assembled = assemble_helper(on_completes_cond) -# with pt.TealComponent.Context.ignoreExprEquality(): -# assert on_completes_cond_assembled in assembled_ap_condition_list - - def test_wrap_handler_bare_call(): BARE_CALL_CASES = [ dummy_doing_nothing, From a3a8601c7481e0f3045fc0bcf73ba3b56a164bde Mon Sep 17 00:00:00 2001 From: Hang Su Date: Wed, 1 Jun 2022 14:41:14 -0400 Subject: [PATCH 183/188] add router method only in clearstate prog --- pyteal/compiler/compiler_test.py | 91 +++++++++++++++++++++++++++++--- 1 file changed, 83 insertions(+), 8 deletions(-) diff --git a/pyteal/compiler/compiler_test.py b/pyteal/compiler/compiler_test.py index fbceff073..0d22beb72 100644 --- a/pyteal/compiler/compiler_test.py +++ b/pyteal/compiler/compiler_test.py @@ -2309,6 +2309,21 @@ def log_creation(*, output: pt.abi.String) -> pt.Expr: log_creation, method_config=pt.MethodConfig(no_op=pt.CallConfig.CREATE) ) + @pt.ABIReturnSubroutine + def approve_if_odd(condition_encoding: pt.abi.Uint32) -> pt.Expr: + return ( + pt.If(condition_encoding.get() % pt.Int(2)) + .Then(pt.Approve()) + .Else(pt.Reject()) + ) + + router.add_method_handler( + approve_if_odd, + method_config=pt.MethodConfig( + no_op=pt.CallConfig.NEVER, clear_state=pt.CallConfig.CALL + ), + ) + on_completion_actions = pt.BareCallActions( opt_in=pt.OnCompleteAction.call_only(pt.Log(pt.Bytes("optin call"))), clear_state=pt.OnCompleteAction.call_only(pt.Approve()), @@ -2803,13 +2818,30 @@ def log_creation(*, output: pt.abi.String) -> pt.Expr: txn NumAppArgs int 0 == -bnz main_l4 +bnz main_l6 txna ApplicationArgs 0 method "log_1()uint64" == -bnz main_l3 +bnz main_l5 +txna ApplicationArgs 0 +method "approve_if_odd(uint32)void" +== +bnz main_l4 err -main_l3: +main_l4: +txn ApplicationID +int 0 +!= +assert +txna ApplicationArgs 1 +int 0 +extract_uint32 +store 2 +load 2 +callsub approveifodd_1 +int 1 +return +main_l5: txn ApplicationID int 0 != @@ -2823,7 +2855,7 @@ def log_creation(*, output: pt.abi.String) -> pt.Expr: log int 1 return -main_l4: +main_l6: txn ApplicationID int 0 != @@ -2836,7 +2868,20 @@ def log_creation(*, output: pt.abi.String) -> pt.Expr: int 1 store 0 load 0 -retsub""".strip() +retsub + +// approve_if_odd +approveifodd_1: +store 3 +load 3 +int 2 +% +bnz approveifodd_1_l2 +int 0 +return +approveifodd_1_l2: +int 1 +return""".strip() assert expected_csp_with_oc == actual_csp_with_oc_compiled _router_without_oc = pt.Router("yetAnotherContractConstructedFromRouter") @@ -3307,9 +3352,26 @@ def log_creation(*, output: pt.abi.String) -> pt.Expr: txna ApplicationArgs 0 method "log_1()uint64" == -bnz main_l2 +bnz main_l4 +txna ApplicationArgs 0 +method "approve_if_odd(uint32)void" +== +bnz main_l3 err -main_l2: +main_l3: +txn ApplicationID +int 0 +!= +assert +txna ApplicationArgs 1 +int 0 +extract_uint32 +store 2 +load 2 +callsub approveifodd_1 +int 1 +return +main_l4: txn ApplicationID int 0 != @@ -3329,5 +3391,18 @@ def log_creation(*, output: pt.abi.String) -> pt.Expr: int 1 store 0 load 0 -retsub""".strip() +retsub + +// approve_if_odd +approveifodd_1: +store 3 +load 3 +int 2 +% +bnz approveifodd_1_l2 +int 0 +return +approveifodd_1_l2: +int 1 +return""".strip() assert actual_csp_without_oc_compiled == expected_csp_without_oc From f6ad9ad4fa314e2a0e197e04ea2566524a61f7d5 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Wed, 1 Jun 2022 14:51:35 -0400 Subject: [PATCH 184/188] closeout -> clearstate --- pyteal/compiler/compiler_test.py | 98 +++++++++++++++++++------------- 1 file changed, 59 insertions(+), 39 deletions(-) diff --git a/pyteal/compiler/compiler_test.py b/pyteal/compiler/compiler_test.py index 0d22beb72..66364ad3f 100644 --- a/pyteal/compiler/compiler_test.py +++ b/pyteal/compiler/compiler_test.py @@ -2286,7 +2286,7 @@ def empty_return_subroutine() -> pt.Expr: router.add_method_handler( empty_return_subroutine, method_config=pt.MethodConfig( - opt_in=pt.CallConfig.ALL, close_out=pt.CallConfig.CALL + opt_in=pt.CallConfig.ALL, clear_state=pt.CallConfig.CALL ), ) @@ -2436,14 +2436,6 @@ def approve_if_odd(condition_encoding: pt.abi.Uint32) -> pt.Expr: int OptIn == || -txn OnCompletion -int CloseOut -== -txn ApplicationID -int 0 -!= -&& -|| assert callsub emptyreturnsubroutine_6 int 1 @@ -2818,17 +2810,21 @@ def approve_if_odd(condition_encoding: pt.abi.Uint32) -> pt.Expr: txn NumAppArgs int 0 == -bnz main_l6 +bnz main_l8 +txna ApplicationArgs 0 +method "empty_return_subroutine()void" +== +bnz main_l7 txna ApplicationArgs 0 method "log_1()uint64" == -bnz main_l5 +bnz main_l6 txna ApplicationArgs 0 method "approve_if_odd(uint32)void" == -bnz main_l4 +bnz main_l5 err -main_l4: +main_l5: txn ApplicationID int 0 != @@ -2838,15 +2834,15 @@ def approve_if_odd(condition_encoding: pt.abi.Uint32) -> pt.Expr: extract_uint32 store 2 load 2 -callsub approveifodd_1 +callsub approveifodd_2 int 1 return -main_l5: +main_l6: txn ApplicationID int 0 != assert -callsub log1_0 +callsub log1_1 store 1 byte 0x151f7c75 load 1 @@ -2855,7 +2851,15 @@ def approve_if_odd(condition_encoding: pt.abi.Uint32) -> pt.Expr: log int 1 return -main_l6: +main_l7: +txn ApplicationID +int 0 +!= +assert +callsub emptyreturnsubroutine_0 +int 1 +return +main_l8: txn ApplicationID int 0 != @@ -2863,23 +2867,29 @@ def approve_if_odd(condition_encoding: pt.abi.Uint32) -> pt.Expr: int 1 return +// empty_return_subroutine +emptyreturnsubroutine_0: +byte "appear in both approval and clear state" +log +retsub + // log_1 -log1_0: +log1_1: int 1 store 0 load 0 retsub // approve_if_odd -approveifodd_1: +approveifodd_2: store 3 load 3 int 2 % -bnz approveifodd_1_l2 +bnz approveifodd_2_l2 int 0 return -approveifodd_1_l2: +approveifodd_2_l2: int 1 return""".strip() assert expected_csp_with_oc == actual_csp_with_oc_compiled @@ -2985,14 +2995,6 @@ def approve_if_odd(condition_encoding: pt.abi.Uint32) -> pt.Expr: int OptIn == || -txn OnCompletion -int CloseOut -== -txn ApplicationID -int 0 -!= -&& -|| assert callsub emptyreturnsubroutine_6 int 1 @@ -3350,15 +3352,19 @@ def approve_if_odd(condition_encoding: pt.abi.Uint32) -> pt.Expr: expected_csp_without_oc = """#pragma version 6 txna ApplicationArgs 0 +method "empty_return_subroutine()void" +== +bnz main_l6 +txna ApplicationArgs 0 method "log_1()uint64" == -bnz main_l4 +bnz main_l5 txna ApplicationArgs 0 method "approve_if_odd(uint32)void" == -bnz main_l3 +bnz main_l4 err -main_l3: +main_l4: txn ApplicationID int 0 != @@ -3368,15 +3374,15 @@ def approve_if_odd(condition_encoding: pt.abi.Uint32) -> pt.Expr: extract_uint32 store 2 load 2 -callsub approveifodd_1 +callsub approveifodd_2 int 1 return -main_l4: +main_l5: txn ApplicationID int 0 != assert -callsub log1_0 +callsub log1_1 store 1 byte 0x151f7c75 load 1 @@ -3385,24 +3391,38 @@ def approve_if_odd(condition_encoding: pt.abi.Uint32) -> pt.Expr: log int 1 return +main_l6: +txn ApplicationID +int 0 +!= +assert +callsub emptyreturnsubroutine_0 +int 1 +return + +// empty_return_subroutine +emptyreturnsubroutine_0: +byte "appear in both approval and clear state" +log +retsub // log_1 -log1_0: +log1_1: int 1 store 0 load 0 retsub // approve_if_odd -approveifodd_1: +approveifodd_2: store 3 load 3 int 2 % -bnz approveifodd_1_l2 +bnz approveifodd_2_l2 int 0 return -approveifodd_1_l2: +approveifodd_2_l2: int 1 return""".strip() assert actual_csp_without_oc_compiled == expected_csp_without_oc From 4e95c20924920606b0ac90d6e908982b55a747ce Mon Sep 17 00:00:00 2001 From: Hang Su Date: Wed, 1 Jun 2022 14:57:16 -0400 Subject: [PATCH 185/188] removing is_arc4_compliant --- pyteal/ast/router.py | 8 -------- pyteal/ast/router_test.py | 4 +--- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/pyteal/ast/router.py b/pyteal/ast/router.py index c18bdfa42..5c608e318 100644 --- a/pyteal/ast/router.py +++ b/pyteal/ast/router.py @@ -94,9 +94,6 @@ def arc4_compliant(cls): delete_application=CallConfig.ALL, ) - def is_arc4_compliant(self) -> bool: - return self == self.arc4_compliant() - def approval_cond(self) -> Expr | int: config_oc_pairs: list[tuple[CallConfig, EnumInt]] = [ (self.no_op, OnComplete.NoOp), @@ -509,11 +506,6 @@ def add_method_handler( self.method_sig_to_selector[method_signature] = method_selector self.method_selector_to_sig[method_selector] = method_signature - if method_config.is_arc4_compliant(): - self.approval_ast.add_method_to_ast(method_signature, 1, method_call) - self.clear_state_ast.add_method_to_ast(method_signature, 1, method_call) - return - method_approval_cond = method_config.approval_cond() method_clear_state_cond = method_config.clear_state_cond() self.approval_ast.add_method_to_ast( diff --git a/pyteal/ast/router_test.py b/pyteal/ast/router_test.py index 7826ae053..0ae151b26 100644 --- a/pyteal/ast/router_test.py +++ b/pyteal/ast/router_test.py @@ -265,13 +265,11 @@ def test_call_config(): def test_method_config(): never_mc = pt.MethodConfig(no_op=pt.CallConfig.NEVER) assert never_mc.is_never() - assert not never_mc.is_arc4_compliant() assert never_mc.approval_cond() == 0 assert never_mc.clear_state_cond() == 0 all_mc = pt.MethodConfig.arc4_compliant() assert not all_mc.is_never() - assert all_mc.is_arc4_compliant() assert all_mc.approval_cond() == 1 assert all_mc.clear_state_cond() == 1 @@ -309,7 +307,7 @@ def test_method_config(): ): assert mc.approval_cond() == 0 continue - elif mc.is_arc4_compliant() or all( + elif all( getattr(mc, i) == pt.CallConfig.ALL for i, _ in approval_check_names_n_ocs ): From 00376a61b662d34bcb0c95cc9d26a2037eda5a1e Mon Sep 17 00:00:00 2001 From: Hang Su Date: Wed, 1 Jun 2022 15:30:00 -0400 Subject: [PATCH 186/188] update add method handler and method interface --- pyteal/ast/router.py | 60 +++++++++++++++++++++++--------- pyteal/compiler/compiler_test.py | 8 +++-- 2 files changed, 50 insertions(+), 18 deletions(-) diff --git a/pyteal/ast/router.py b/pyteal/ast/router.py index 5c608e318..485483dc8 100644 --- a/pyteal/ast/router.py +++ b/pyteal/ast/router.py @@ -73,7 +73,7 @@ class MethodConfig: method call is paired with certain OnCompletion conditions and creation conditions. """ - no_op: CallConfig = field(kw_only=True, default=CallConfig.CALL) + no_op: CallConfig = field(kw_only=True, default=CallConfig.NEVER) opt_in: CallConfig = field(kw_only=True, default=CallConfig.NEVER) close_out: CallConfig = field(kw_only=True, default=CallConfig.NEVER) clear_state: CallConfig = field(kw_only=True, default=CallConfig.NEVER) @@ -483,13 +483,15 @@ def add_method_handler( self, method_call: ABIReturnSubroutine, overriding_name: str = None, - method_config: MethodConfig = MethodConfig(), + method_config: MethodConfig = None, ) -> None: if not isinstance(method_call, ABIReturnSubroutine): raise TealInputError( "for adding method handler, must be ABIReturnSubroutine" ) method_signature = method_call.method_signature(overriding_name) + if method_config is None: + method_config = MethodConfig(no_op=CallConfig.CALL) if method_config.is_never(): raise TealInputError( f"registered method {method_signature} is never executed" @@ -521,12 +523,12 @@ def method( /, *, name: str = None, - no_op: CallConfig = CallConfig.CALL, - opt_in: CallConfig = CallConfig.NEVER, - close_out: CallConfig = CallConfig.NEVER, - clear_state: CallConfig = CallConfig.NEVER, - update_application: CallConfig = CallConfig.NEVER, - delete_application: CallConfig = CallConfig.NEVER, + no_op: CallConfig = None, + opt_in: CallConfig = None, + close_out: CallConfig = None, + clear_state: CallConfig = None, + update_application: CallConfig = None, + delete_application: CallConfig = None, ): """ A decorator style method registration by decorating over a python function, @@ -540,16 +542,42 @@ def method( for example, as a decorator argument. """ + # we use `is None` extensively for CallConfig to distinguish 2 following cases + # - None + # - CallConfig.Never + # both cases evaluate to False in if statement. def wrap(_func): wrapped_subroutine = ABIReturnSubroutine(_func) - call_configs = MethodConfig( - no_op=no_op, - opt_in=opt_in, - close_out=close_out, - clear_state=clear_state, - update_application=update_application, - delete_application=delete_application, - ) + call_configs: MethodConfig + if ( + no_op is None + and opt_in is None + and close_out is None + and clear_state is None + and update_application is None + and delete_application is None + ): + call_configs = MethodConfig(no_op=CallConfig.CALL) + else: + + def none_to_never(x: None | CallConfig): + return CallConfig.NEVER if x is None else x + + _no_op = none_to_never(no_op) + _opt_in = none_to_never(opt_in) + _close_out = none_to_never(close_out) + _clear_state = none_to_never(clear_state) + _update_app = none_to_never(update_application) + _delete_app = none_to_never(delete_application) + + call_configs = MethodConfig( + no_op=_no_op, + opt_in=_opt_in, + close_out=_close_out, + clear_state=_clear_state, + update_application=_update_app, + delete_application=_delete_app, + ) self.add_method_handler(wrapped_subroutine, name, call_configs) if not func: diff --git a/pyteal/compiler/compiler_test.py b/pyteal/compiler/compiler_test.py index 66364ad3f..e20b341a2 100644 --- a/pyteal/compiler/compiler_test.py +++ b/pyteal/compiler/compiler_test.py @@ -2286,7 +2286,9 @@ def empty_return_subroutine() -> pt.Expr: router.add_method_handler( empty_return_subroutine, method_config=pt.MethodConfig( - opt_in=pt.CallConfig.ALL, clear_state=pt.CallConfig.CALL + no_op=pt.CallConfig.CALL, + opt_in=pt.CallConfig.ALL, + clear_state=pt.CallConfig.CALL, ), ) @@ -2297,7 +2299,9 @@ def log_1(*, output: pt.abi.Uint64) -> pt.Expr: router.add_method_handler( log_1, method_config=pt.MethodConfig( - opt_in=pt.CallConfig.CALL, clear_state=pt.CallConfig.CALL + no_op=pt.CallConfig.CALL, + opt_in=pt.CallConfig.CALL, + clear_state=pt.CallConfig.CALL, ), ) From c56d837a5ede94c27343010bd3eb3732f5a75445 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Wed, 1 Jun 2022 17:14:51 -0400 Subject: [PATCH 187/188] remove arc4_compliant --- pyteal/ast/router.py | 11 ----------- pyteal/ast/router_test.py | 5 ----- 2 files changed, 16 deletions(-) diff --git a/pyteal/ast/router.py b/pyteal/ast/router.py index 485483dc8..71b8f35b3 100644 --- a/pyteal/ast/router.py +++ b/pyteal/ast/router.py @@ -83,17 +83,6 @@ class MethodConfig: def is_never(self) -> bool: return all(map(lambda cc: cc == CallConfig.NEVER, astuple(self))) - @classmethod - def arc4_compliant(cls): - return cls( - no_op=CallConfig.ALL, - opt_in=CallConfig.ALL, - close_out=CallConfig.ALL, - clear_state=CallConfig.ALL, - update_application=CallConfig.ALL, - delete_application=CallConfig.ALL, - ) - def approval_cond(self) -> Expr | int: config_oc_pairs: list[tuple[CallConfig, EnumInt]] = [ (self.no_op, OnComplete.NoOp), diff --git a/pyteal/ast/router_test.py b/pyteal/ast/router_test.py index 0ae151b26..84cd723ab 100644 --- a/pyteal/ast/router_test.py +++ b/pyteal/ast/router_test.py @@ -268,11 +268,6 @@ def test_method_config(): assert never_mc.approval_cond() == 0 assert never_mc.clear_state_cond() == 0 - all_mc = pt.MethodConfig.arc4_compliant() - assert not all_mc.is_never() - assert all_mc.approval_cond() == 1 - assert all_mc.clear_state_cond() == 1 - on_complete_pow_set = power_set(ON_COMPLETE_CASES) approval_check_names_n_ocs = [ (camel_to_snake(oc.name), oc) From c8ef39a0a2b8feae29a7033cd292361b59c0d723 Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Wed, 1 Jun 2022 14:28:31 -0700 Subject: [PATCH 188/188] Add manual test for 15+ args in abi router (#375) --- pyteal/ast/router_test.py | 57 ++++++++++++++++++++++++++++++++++++-- pyteal/ir/tealcomponent.py | 15 ++++++++++ 2 files changed, 69 insertions(+), 3 deletions(-) diff --git a/pyteal/ast/router_test.py b/pyteal/ast/router_test.py index 84cd723ab..d9b6a967a 100644 --- a/pyteal/ast/router_test.py +++ b/pyteal/ast/router_test.py @@ -413,7 +413,7 @@ def test_wrap_handler_method_call(): ) for abi_subroutine in ONLY_ABI_SUBROUTINE_CASES: wrapped: pt.Expr = ASTBuilder.wrap_handler(True, abi_subroutine) - assembled_wrapped: pt.TealBlock = assemble_helper(wrapped) + actual: pt.TealBlock = assemble_helper(wrapped) args: list[pt.abi.BaseType] = [ spec.new_instance() @@ -461,9 +461,60 @@ def test_wrap_handler_method_call(): else: evaluate = abi_subroutine(*args) - actual = assemble_helper(pt.Seq(*loading, evaluate, pt.Approve())) + expected = assemble_helper(pt.Seq(*loading, evaluate, pt.Approve())) with pt.TealComponent.Context.ignoreScratchSlotEquality(), pt.TealComponent.Context.ignoreExprEquality(): - assert actual == assembled_wrapped + assert actual == expected + + assert pt.TealBlock.MatchScratchSlotReferences( + pt.TealBlock.GetReferencedScratchSlots(actual), + pt.TealBlock.GetReferencedScratchSlots(expected), + ) + + +def test_wrap_handler_method_call_many_args(): + wrapped: pt.Expr = ASTBuilder.wrap_handler(True, many_args) + actual: pt.TealBlock = assemble_helper(wrapped) + + args = [pt.abi.Uint64() for _ in range(20)] + last_arg = pt.abi.TupleTypeSpec( + *[pt.abi.Uint64TypeSpec() for _ in range(6)] + ).new_instance() + + output_temp = pt.abi.Uint64() + expected_ast = pt.Seq( + args[0].decode(pt.Txn.application_args[1]), + args[1].decode(pt.Txn.application_args[2]), + args[2].decode(pt.Txn.application_args[3]), + args[3].decode(pt.Txn.application_args[4]), + args[4].decode(pt.Txn.application_args[5]), + args[5].decode(pt.Txn.application_args[6]), + args[6].decode(pt.Txn.application_args[7]), + args[7].decode(pt.Txn.application_args[8]), + args[8].decode(pt.Txn.application_args[9]), + args[9].decode(pt.Txn.application_args[10]), + args[10].decode(pt.Txn.application_args[11]), + args[11].decode(pt.Txn.application_args[12]), + args[12].decode(pt.Txn.application_args[13]), + args[13].decode(pt.Txn.application_args[14]), + last_arg.decode(pt.Txn.application_args[15]), + last_arg[0].store_into(args[14]), + last_arg[1].store_into(args[15]), + last_arg[2].store_into(args[16]), + last_arg[3].store_into(args[17]), + last_arg[4].store_into(args[18]), + last_arg[5].store_into(args[19]), + many_args(*args).store_into(output_temp), + pt.abi.MethodReturn(output_temp), + pt.Approve(), + ) + expected = assemble_helper(expected_ast) + with pt.TealComponent.Context.ignoreScratchSlotEquality(), pt.TealComponent.Context.ignoreExprEquality(): + assert actual == expected + + assert pt.TealBlock.MatchScratchSlotReferences( + pt.TealBlock.GetReferencedScratchSlots(actual), + pt.TealBlock.GetReferencedScratchSlots(expected), + ) def test_contract_json_obj(): diff --git a/pyteal/ir/tealcomponent.py b/pyteal/ir/tealcomponent.py index bb7755879..f723e20f6 100644 --- a/pyteal/ir/tealcomponent.py +++ b/pyteal/ir/tealcomponent.py @@ -67,6 +67,21 @@ def __exit__(self, *args): @classmethod def ignoreScratchSlotEquality(cls): + """When comparing TealOps, do not verify the equality of any ScratchSlot arguments. + + This is commonly used in testing to verify the that two control flow graphs contains the + same operations, but may use different ScratchSlots in them. In this case, you will most + likely want to also use use the following code after comparing with this option enabled: + + .. code-block:: python + TealBlock.MatchScratchSlotReferences( + TealBlock.GetReferencedScratchSlots(actual), + TealBlock.GetReferencedScratchSlots(expected), + ) + + This ensures that the ScratchSlot usages between the two control flow graphs is + equivalent. See :any:`TealBlock.MatchScratchSlotReferences` for more info. + """ return cls.ScratchSlotEqualityContext()