diff --git a/pyteal/__init__.py b/pyteal/__init__.py index ae30dd312..8087c15c3 100644 --- a/pyteal/__init__.py +++ b/pyteal/__init__.py @@ -17,7 +17,12 @@ TealInputError, TealCompileError, ) -from pyteal.config import MAX_GROUP_SIZE, NUM_SLOTS +from pyteal.config import ( + MAX_GROUP_SIZE, + NUM_SLOTS, + RETURN_HASH_PREFIX, + METHOD_ARG_NUM_CUTOFF, +) # begin __all__ __all__ = ( @@ -37,6 +42,8 @@ "TealCompileError", "MAX_GROUP_SIZE", "NUM_SLOTS", + "RETURN_HASH_PREFIX", + "METHOD_ARG_NUM_CUTOFF", ] ) # end __all__ diff --git a/pyteal/__init__.pyi b/pyteal/__init__.pyi index 4f1477a90..5e49e918e 100644 --- a/pyteal/__init__.pyi +++ b/pyteal/__init__.pyi @@ -20,7 +20,12 @@ from pyteal.errors import ( TealInputError, TealCompileError, ) -from pyteal.config import MAX_GROUP_SIZE, NUM_SLOTS +from pyteal.config import ( + MAX_GROUP_SIZE, + NUM_SLOTS, + RETURN_HASH_PREFIX, + METHOD_ARG_NUM_CUTOFF, +) __all__ = [ "ABIReturnSubroutine", @@ -38,6 +43,7 @@ __all__ = [ "AssetHolding", "AssetParam", "Balance", + "BareCallActions", "BinaryExpr", "BitLen", "BitwiseAnd", @@ -64,6 +70,7 @@ __all__ = [ "BytesSqrt", "BytesXor", "BytesZero", + "CallConfig", "CompileOptions", "Concat", "Cond", @@ -117,8 +124,10 @@ __all__ = [ "Lt", "MAX_GROUP_SIZE", "MAX_TEAL_VERSION", + "METHOD_ARG_NUM_CUTOFF", "MIN_TEAL_VERSION", "MaybeValue", + "MethodConfig", "MethodSignature", "MinBalance", "Minus", @@ -132,14 +141,17 @@ __all__ = [ "Nonce", "Not", "OnComplete", + "OnCompleteAction", "Op", "OpUp", "OpUpMode", "OptimizeOptions", "Or", "Pop", + "RETURN_HASH_PREFIX", "Reject", "Return", + "Router", "ScratchIndex", "ScratchLoad", "ScratchSlot", diff --git a/pyteal/ast/__init__.py b/pyteal/ast/__init__.py index 79f3beee5..68c6fd149 100644 --- a/pyteal/ast/__init__.py +++ b/pyteal/ast/__init__.py @@ -125,7 +125,6 @@ from pyteal.ast.break_ import Break from pyteal.ast.continue_ import Continue - # misc from pyteal.ast.scratch import ( ScratchIndex, @@ -139,6 +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, + CallConfig, + MethodConfig, + OnCompleteAction, + BareCallActions, +) # abi import pyteal.ast.abi as abi # noqa: I250 @@ -280,6 +286,11 @@ "For", "Break", "Continue", + "Router", + "CallConfig", + "MethodConfig", + "OnCompleteAction", + "BareCallActions", "abi", "EcdsaCurve", "EcdsaVerify", 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/method_return.py b/pyteal/ast/abi/method_return.py index 7d7af0ed7..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,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_HASH_PREFIX), self.arg.encode())).__teal__( + options + ) def __str__(self) -> str: return f"(MethodReturn {self.arg.type_spec()})" diff --git a/pyteal/ast/abi/string.py b/pyteal/ast/abi/string.py index b4215d65f..92b566d95 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()) @@ -70,12 +67,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()) 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/return_.py b/pyteal/ast/return_.py index 3d2b809d1..a19d894f0 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): diff --git a/pyteal/ast/router.py b/pyteal/ast/router.py new file mode 100644 index 000000000..71b8f35b3 --- /dev/null +++ b/pyteal/ast/router.py @@ -0,0 +1,643 @@ +from dataclasses import dataclass, field, fields, astuple +from typing import cast, Optional, Callable +from enum import IntFlag + +from algosdk import abi as sdk_abi +from algosdk import encoding + +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 +from pyteal.ir.ops import Mode + +from pyteal.ast import abi +from pyteal.ast.subroutine import ( + OutputKwArgInfo, + 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 +from pyteal.ast.int import Int +from pyteal.ast.seq import Seq +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 + + +class CallConfig(IntFlag): + """ + 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: + - CALL + - CREATE + - ALL + - NEVER + for a method call on one on_complete case. + """ + + NEVER = 0 + CALL = 1 + 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" + + +@dataclass(frozen=True) +class MethodConfig: + """ + MethodConfig keep track of one method's CallConfigs for all OnComplete cases. + + 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.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) + 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 all(map(lambda cc: cc == CallConfig.NEVER, astuple(self))) + + def approval_cond(self) -> Expr | int: + 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, oc in config_oc_pairs: + config_cond = config.condition_under_config() + match config_cond: + case Expr(): + cond_list.append(And(Txn.on_completion() == oc, config_cond)) + case 1: + cond_list.append(Txn.on_completion() == oc) + case 0: + continue + case _: + raise TealInternalError( + f"unexpected condition_under_config: {config_cond}" + ) + return Or(*cond_list) + + def clear_state_cond(self) -> Expr | int: + return self.clear_state.condition_under_config() + + +@dataclass(frozen=True) +class OnCompleteAction: + """ + OnComplete Action, registers bare calls to one single OnCompletion case. + """ + + 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 bool(self.call_config) ^ bool(self.action): + raise TealInputError( + f"action {self.action} and call_config {str(self.call_config)} contradicts" + ) + + @staticmethod + def never() -> "OnCompleteAction": + return OnCompleteAction() + + @staticmethod + def create_only( + f: Expr | SubroutineFnWrapper | ABIReturnSubroutine, + ) -> "OnCompleteAction": + return OnCompleteAction(action=f, call_config=CallConfig.CREATE) + + @staticmethod + def call_only( + f: Expr | SubroutineFnWrapper | ABIReturnSubroutine, + ) -> "OnCompleteAction": + return OnCompleteAction(action=f, call_config=CallConfig.CALL) + + @staticmethod + def always( + f: Expr | SubroutineFnWrapper | ABIReturnSubroutine, + ) -> "OnCompleteAction": + return OnCompleteAction(action=f, call_config=CallConfig.ALL) + + def is_empty(self) -> bool: + return not self.action and self.call_config == CallConfig.NEVER + + +OnCompleteAction.__module__ = "pyteal" + + +@dataclass(frozen=True) +class BareCallActions: + """ + BareCallActions keep track of bare-call registrations to all OnCompletion cases. + """ + + 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 is_empty(self) -> bool: + for action_field in fields(self): + action: OnCompleteAction = getattr(self, action_field.name) + if not action.is_empty(): + return False + return True + + def approval_construction(self) -> Optional[Expr]: + 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: + 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: {str(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 + + 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, + ) + case _: + raise TealInternalError( + f"Unexpected CallConfig: {str(self.clear_state.call_config)}" + ) + + +BareCallActions.__module__ = "pyteal" + + +@dataclass(frozen=True) +class CondNode: + condition: Expr + branch: Expr + + +CondNode.__module__ = "pyteal" + + +@dataclass +class ASTBuilder: + conditions_n_branches: list[CondNode] = field(default_factory=list) + + @staticmethod + 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. + + 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. + + 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. + 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_method_call: + match handler: + case Expr(): + if handler.type_of() != TealType.none: + raise TealInputError( + f"bare appcall handler should be TealType.none not {handler.type_of()}." + ) + 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 TealType.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 Expr, or Subroutine/ABIReturnSubroutine with none return and no arg" + ) + else: + if not isinstance(handler, ABIReturnSubroutine): + raise TealInputError( + f"method call should be only registering ABIReturnSubroutine, got {type(handler)}." + ) + if not handler.is_abi_routable(): + raise TealInputError( + f"method call ABIReturnSubroutine is not routable " + f"got {handler.subroutine.argument_count()} args with {len(handler.subroutine.abi_args)} ABI args." + ) + + arg_type_specs = cast( + list[abi.TypeSpec], handler.subroutine.expected_arg_types + ) + 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) + + arg_abi_vars: list[abi.BaseType] = [ + 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(len(arg_type_specs)) + ] + + 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_CUTOFF - 1 :], + ) + tuple_abi_args: list[abi.BaseType] = [ + t_arg_ts.new_instance() for t_arg_ts in tuple_arg_type_specs + ] + last_tuple_arg: abi.Tuple = cast(abi.Tuple, arg_abi_vars[-1]) + de_tuple_instructions: list[Expr] = [ + last_tuple_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 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( + walk_in_cond, + Seq(Assert(cond), self.wrap_handler(True, handler)), + ) + ) + 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( + 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), + ) + ) + + def add_method_handler( + self, + method_call: ABIReturnSubroutine, + overriding_name: str = None, + 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" + ) + 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") + 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 + + 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 + ) + self.clear_state_ast.add_method_to_ast( + method_signature, method_clear_state_cond, method_call + ) + + def method( + self, + func: Callable = None, + /, + *, + name: str = None, + 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, + 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. + """ + + # 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 + 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: + return wrap + return wrap(func) + + 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, + and constructs an `Contract` object. + + Returns: + contract: a dictified `Contract` object constructed from + approval program's method signatures and `self.name`. + """ + method_collections = [ + 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) + + 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. + + 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 ( + self.approval_ast.program_construction(), + self.clear_state_ast.program_construction(), + self.contract_construct(), + ) + + def compile_program( + self, + *, + version: int = DEFAULT_TEAL_VERSION, + assemble_constants: bool = False, + optimize: OptimizeOptions = None, + ) -> 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. + + 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=assemble_constants, + optimize=optimize, + ) + csp_compiled = compileTeal( + csp, + Mode.Application, + version=version, + assembleConstants=assemble_constants, + optimize=optimize, + ) + return ap_compiled, csp_compiled, contract + + +Router.__module__ = "pyteal" diff --git a/pyteal/ast/router_test.py b/pyteal/ast/router_test.py new file mode 100644 index 000000000..d9b6a967a --- /dev/null +++ b/pyteal/ast/router_test.py @@ -0,0 +1,535 @@ +import pyteal as pt +from pyteal.ast.router import ASTBuilder +import pytest +import typing +import algosdk.abi as sdk_abi + + +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 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(_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()) + ) + + +@pt.ABIReturnSubroutine +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) + + +@pt.ABIReturnSubroutine +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, + mul, + div, + mod, + qrem, + reverse, + concat_strings, + many_args, + safe_clear_state_delete, + dummy_doing_nothing, + eine_constant, + take_abi_and_log, +] + +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 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) + 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_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`, + 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 + 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 + + +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)) + + +def assemble_helper(what: pt.Expr) -> pt.TealBlock: + assembled, _ = what.__teal__(options) + assembled.addIncoming() + assembled = pt.TealBlock.NormalizeBlocks(assembled) + return assembled + + +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) + 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] + ordered_call_configs = full_ordered_combination_gen( + list(pt.CallConfig), len(on_complete_set) + ) + 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 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 + ] + 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(): + 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_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 = ASTBuilder.wrap_handler(False, bare_call) + expected: pt.Expr + match bare_call: + case pt.Expr(): + if bare_call.has_return(): + expected = bare_call + else: + expected = pt.Seq(bare_call, pt.Approve()) + case pt.SubroutineFnWrapper() | pt.ABIReturnSubroutine(): + 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 = [ + ( + pt.Int(1), + f"bare appcall handler should be TealType.none not {pt.TealType.uint64}.", + ), + ( + returning_u64, + f"subroutine call should be returning TealType.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()}.", + ), + ( + 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: + 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: + ASTBuilder.wrap_handler(True, not_registrable) + assert "method call ABIReturnSubroutine is not routable" in str(bug) + + with pytest.raises(pt.TealInputError) as bug: + 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 = ASTBuilder.wrap_handler(True, abi_subroutine) + actual: pt.TealBlock = assemble_helper(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] + + 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_CUTOFF - 1 :] + ] + ).new_instance() + loading = [ + arg.decode(pt.Txn.application_args[index + 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_CUTOFF]) + ) + for i in range(pt.METHOD_ARG_NUM_CUTOFF - 1, len(args)): + loading.append( + sdk_last_arg[i - pt.METHOD_ARG_NUM_CUTOFF + 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() + evaluate = pt.Seq( + abi_subroutine(*args).store_into(output_temp), + pt.abi.MethodReturn(output_temp), + ) + else: + evaluate = abi_subroutine(*args) + + expected = assemble_helper(pt.Seq(*loading, evaluate, pt.Approve())) + 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_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(): + abi_subroutines = list( + filter(lambda x: isinstance(x, pt.ABIReturnSubroutine), GOOD_SUBROUTINE_CASES) + ) + contract_name = "contract_name" + on_complete_actions = pt.BareCallActions( + clear_state=pt.OnCompleteAction.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.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 contract == sdk_contract diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index c7ef8135d..4a8dec550 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -589,6 +589,28 @@ def __call__( def name(self) -> str: return self.subroutine.name() + 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" + ) + + 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() + return f"{overriding_name}({','.join(args)}){self.type_of()}" + def type_of(self) -> str | abi.TypeSpec: return ( "void" @@ -596,7 +618,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 e01f2910a..a58df675f 100644 --- a/pyteal/ast/subroutine_test.py +++ b/pyteal/ast/subroutine_test.py @@ -1,11 +1,11 @@ from itertools import product -from typing import List, Literal +from typing import List, Literal, Optional, cast import pytest 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) @@ -86,6 +86,7 @@ class ABISubroutineTC: arg_instances: list[pt.Expr | pt.abi.BaseType] name: str ret_type: str | pt.abi.TypeSpec + signature: Optional[str] def test_abi_subroutine_definition(): @@ -139,13 +140,27 @@ def fn_w_tuple1arg( return output.set(pt.Int(1)) 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", + "fn_1arg_0ret(uint64)void", ), - 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_1ret, + [pt.abi.Uint64()], + "fn_1arg_1ret", + pt.abi.Uint64TypeSpec(), + "fn_1arg_1ret(uint64)uint64", ), ABISubroutineTC( fn_2arg_0ret, @@ -157,6 +172,7 @@ def fn_w_tuple1arg( ], "fn_2arg_0ret", "void", + "fn_2arg_0ret(uint64,byte[10])void", ), ABISubroutineTC( fn_2arg_1ret, @@ -168,6 +184,7 @@ def fn_w_tuple1arg( ], "fn_2arg_1ret", pt.abi.ByteTypeSpec(), + "fn_2arg_1ret(uint64,byte[10])byte", ), ABISubroutineTC( fn_2arg_1ret_with_expr, @@ -179,6 +196,7 @@ def fn_w_tuple1arg( ], "fn_2arg_1ret_with_expr", pt.abi.ByteTypeSpec(), + None, ), ABISubroutineTC( fn_w_tuple1arg, @@ -188,6 +206,7 @@ def fn_w_tuple1arg( ], "fn_w_tuple1arg", pt.abi.ByteTypeSpec(), + None, ), ) @@ -207,10 +226,47 @@ def fn_w_tuple1arg( 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_abi_routable(): + assert case.definition.method_signature() == cast(str, case.signature) + else: + with pytest.raises(pt.TealInputError): + 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(): """ diff --git a/pyteal/compiler/compiler_test.py b/pyteal/compiler/compiler_test.py index a89f075fc..e20b341a2 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 @@ -2193,3 +2193,1240 @@ 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(): + 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, + method_config=pt.MethodConfig( + no_op=pt.CallConfig.CALL, + opt_in=pt.CallConfig.ALL, + clear_state=pt.CallConfig.CALL, + ), + ) + + @pt.ABIReturnSubroutine + def log_1(*, output: pt.abi.Uint64) -> pt.Expr: + return output.set(1) + + router.add_method_handler( + log_1, + method_config=pt.MethodConfig( + no_op=pt.CallConfig.CALL, + 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, 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()), + ) + + _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 +== +|| +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 + +// 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 + +// 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_with_oc == actual_ap_with_oc_compiled + + expected_csp_with_oc = """#pragma version 6 +txn NumAppArgs +int 0 +== +bnz main_l8 +txna ApplicationArgs 0 +method "empty_return_subroutine()void" +== +bnz main_l7 +txna ApplicationArgs 0 +method "log_1()uint64" +== +bnz main_l6 +txna ApplicationArgs 0 +method "approve_if_odd(uint32)void" +== +bnz main_l5 +err +main_l5: +txn ApplicationID +int 0 +!= +assert +txna ApplicationArgs 1 +int 0 +extract_uint32 +store 2 +load 2 +callsub approveifodd_2 +int 1 +return +main_l6: +txn ApplicationID +int 0 +!= +assert +callsub log1_1 +store 1 +byte 0x151f7c75 +load 1 +itob +concat +log +int 1 +return +main_l7: +txn ApplicationID +int 0 +!= +assert +callsub emptyreturnsubroutine_0 +int 1 +return +main_l8: +txn ApplicationID +int 0 +!= +assert +int 1 +return + +// empty_return_subroutine +emptyreturnsubroutine_0: +byte "appear in both approval and clear state" +log +retsub + +// log_1 +log1_1: +int 1 +store 0 +load 0 +retsub + +// approve_if_odd +approveifodd_2: +store 3 +load 3 +int 2 +% +bnz approveifodd_2_l2 +int 0 +return +approveifodd_2_l2: +int 1 +return""".strip() + assert expected_csp_with_oc == actual_csp_with_oc_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_without_oc = """#pragma version 6 +txna ApplicationArgs 0 +method "add(uint64,uint64)uint64" +== +bnz main_l18 +txna ApplicationArgs 0 +method "sub(uint64,uint64)uint64" +== +bnz main_l17 +txna ApplicationArgs 0 +method "mul(uint64,uint64)uint64" +== +bnz main_l16 +txna ApplicationArgs 0 +method "div(uint64,uint64)uint64" +== +bnz main_l15 +txna ApplicationArgs 0 +method "mod(uint64,uint64)uint64" +== +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_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_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 +== +|| +assert +callsub emptyreturnsubroutine_6 +int 1 +return +main_l13: +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_l14: +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_l15: +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_l16: +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_l17: +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_l18: +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 + +// 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 + +// 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 actual_ap_without_oc_compiled == expected_ap_without_oc + + 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_l5 +txna ApplicationArgs 0 +method "approve_if_odd(uint32)void" +== +bnz main_l4 +err +main_l4: +txn ApplicationID +int 0 +!= +assert +txna ApplicationArgs 1 +int 0 +extract_uint32 +store 2 +load 2 +callsub approveifodd_2 +int 1 +return +main_l5: +txn ApplicationID +int 0 +!= +assert +callsub log1_1 +store 1 +byte 0x151f7c75 +load 1 +itob +concat +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_1: +int 1 +store 0 +load 0 +retsub + +// approve_if_odd +approveifodd_2: +store 3 +load 3 +int 2 +% +bnz approveifodd_2_l2 +int 0 +return +approveifodd_2_l2: +int 1 +return""".strip() + assert actual_csp_without_oc_compiled == expected_csp_without_oc 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 7bbffd9be..e32fda458 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,4 +8,7 @@ NUM_SLOTS = 256 # Method return selector in base16 -RETURN_METHOD_SELECTOR = "0x151F7C75" +RETURN_HASH_PREFIX = ABI_RETURN_HASH + +# Method argument number limit +METHOD_ARG_NUM_CUTOFF = 15 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() 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)