Repository: algorand/pyteal (language: python)

Issue:
Implement subroutine arguments and return values using frame pointers
## Problem

`ABIReturnSubroutine` method parameters are represented by scratch slots in generated TEAL source.  For larger programs, scratch slot usage is dominated by `ABIReturnSubroutine` and may prevent program compilation.

https://github.com/algorand/go-algorand/pull/4319 + https://github.com/algorand/go-algorand/issues/4304 offer a set of frame pointer opcodes that simplify stack-based management of subroutine arguments.  The story requests replacing scratch slot based subroutine parameters with stack-based analogs via frame pointer opcodes.

## Solution


## Dependencies

<!-- Does the solution have any team or design dependencies? -->

## Urgency

<!-- What is the urgency here and why? -->

Merge-master-fp-router
resolving conflicts with `master` branch


Discussion / hints:

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e83179d21..10ee27058 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,8 +10,11 @@
 
 ## Added
 
+* Clarify that `Approve` and `Reject` always exit in the documentation. ([#660](https://github.com/algorand/pyteal/pull/660))
 * Improved error handling for tuple type mismatch: added information on position and expected type. ([#655](https://github.com/algorand/pyteal/pull/655))
 * Added an `asdict()` method to `ast.router.BareCallActions`. ([#656](https://github.com/algorand/pyteal/pull/656))
+* Added frame pointer support for router. ([#600](https://github.com/algorand/pyteal/pull/600))
+ * NOTE: a backwards incompatable change was imposed in this PR: previous `build_program` method in `Router` was exported and public, now this method is hidden. Use `compile_program` only.
 
 ## Fixed
 
diff --git a/Makefile b/Makefile
index 6c5356507..690804429 100644
--- a/Makefile
+++ b/Makefile
@@ -52,10 +52,15 @@ lint: black flake8 mypy sdist-check
 # ---- 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 -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
+test-unit-async:
+	pytest -n $(NUM_PROCS) --durations=10 -sv pyteal tests/unit -m "not serial"
+
+test-unit-sync:
+	pytest --durations=10 -sv pyteal tests/unit -m serial
+
+test-unit: test-unit-async test-unit-sync
 
 lint-and-test: check-generate-init lint test-unit
 
@@ -64,9 +69,13 @@ lint-and-test: check-generate-init lint test-unit
 algod-start:
 	docker compose up -d algod --wait
 
+algod-version:
+	docker compose exec algod goal --version
+
+algod-start-report: algod-start algod-version
+
 algod-stop:
 	docker compose stop algod
-
 integration-run:
 	pytest -n $(NUM_PROCS) --durations=10 -sv tests/integration -m "not serial"
 	pytest --durations=10 -sv tests/integration -m serial
diff --git a/docs/control_structures.rst b/docs/control_structures.rst
index b8b0d26d3..7079abf3c 100644
--- a/docs/control_structures.rst
+++ b/docs/control_structures.rst
@@ -19,7 +19,8 @@ The :any:`Approve` and :any:`Reject` expressions cause the program to immediatel
 is used, then the execution is marked as successful, and if :code:`Reject` is used, then the execution
 is marked as unsuccessful.
 
-These expressions also work inside :ref:`subroutines <subroutine_expr>`.
+These expressions also work inside :ref:`subroutines <subroutine_expr>`. When used inside subroutines, they
+also cause the program to immediately exit, contrary to :code:`Return(...)` which just returns from the subroutine.
 
 .. _seq_expr:
 
diff --git a/pyteal/ast/frame.py b/pyteal/ast/frame.py
index 38199f7b3..19dcec12a 100644
--- a/pyteal/ast/frame.py
+++ b/pyteal/ast/frame.py
@@ -255,7 +255,7 @@ def __init__(
     ):
         super().__init__()
 
-        target_type = inferred_type if inferred_type is not None else TealType.anytype
+        target_type: TealType = inferred_type or TealType.anytype
         require_type(value, target_type)
 
         self.value = value
diff --git a/pyteal/ast/router.py b/pyteal/ast/router.py
index 3c472c27b..517f8fbf9 100644
--- a/pyteal/ast/router.py
+++ b/pyteal/ast/router.py
@@ -1,6 +1,7 @@
+from contextlib import contextmanager
 from dataclasses import dataclass, field, fields, astuple
-from typing import cast, Optional, Callable
 from enum import IntFlag
+from typing import cast, Optional, Callable
 
 from algosdk import abi as sdk_abi
 from algosdk import encoding
@@ -17,20 +18,26 @@
 from pyteal.ast import abi
 from pyteal.ast.subroutine import (
     OutputKwArgInfo,
+    Subroutine,
+    SubroutineCall,
     SubroutineFnWrapper,
     ABIReturnSubroutine,
 )
 from pyteal.ast.assert_ import Assert
 from pyteal.ast.cond import Cond
 from pyteal.ast.expr import Expr
+from pyteal.ast.frame import FrameVar, Proto, ProtoStackLayout
 from pyteal.ast.app import OnComplete
 from pyteal.ast.int import Int, EnumInt
+from pyteal.ast.scratch import ScratchSlot
 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, Reject
 
+ActionType = Expr | SubroutineFnWrapper | ABIReturnSubroutine
+
 
 class CallConfig(IntFlag):
     """
@@ -128,9 +135,6 @@ def approval_cond(self) -> Expr | int:
 MethodConfig.__module__ = "pyteal"
 
 
-ActionType = Expr | SubroutineFnWrapper | ABIReturnSubroutine
-
-
 @dataclass(frozen=True)
 class OnCompleteAction:
     """
@@ -256,12 +260,27 @@ def approval_construction(self) -> Optional[Expr]:
             )
         return Cond(*[[n.condition, n.branch] for n in conditions_n_branches])
 
+    def get_method_config(self) -> MethodConfig:
+        return MethodConfig(
+            no_op=self.no_op.call_config,
+            opt_in=self.opt_in.call_config,
+            close_out=self.close_out.call_config,
+            update_application=self.update_application.call_config,
+            delete_application=self.delete_application.call_config,
+        )
+
 
 BareCallActions.__module__ = "pyteal"
 
 
 @dataclass(frozen=True)
 class CondNode:
+    """CondNode is a node inside generated program AST.
+
+    `condition` is the logic condition that decides if the program should execute the branch.
+    `branch` is the expression containing most of executation.
+    """
+
     condition: Expr
     branch: Expr
 
@@ -269,13 +288,219 @@ class CondNode:
 CondNode.__module__ = "pyteal"
 
 
-@dataclass
+@dataclass(frozen=True)
+class CondWithMethod:
+    """CondWithMethod converts method_signature, condition, and method `ABIReturnSubroutine` into an AST node (`CondNode`).
+
+    `method_signature` and `condition` generates the condition expression in `CondNode`:
+    - `method_sig: str` is the method signature of `method: ABIReturnSubroutine`.
+    - `condition: Expr | int` is the condition representing the `OnComplete` allowance condition,
+       generated by `MethodConfig.approval_cond`:
+       - `Expr`: some of `OnComplete` options are allowed, e.g.,
+         `Or(Txn.on_completion() == OnComplete.NoOp, Txn.on_completion() == OnComplete.OptIn)`.
+       - 1: all of `OnComplete` options for approval program are allowed
+       - 0: no `OnComplete` option is allowed
+         NOTE: since this is generating a dead branch in TEAL generated code, we error here on encountering a 0 condition.
+
+    `method`, which is an `ABIReturnSubroutine`, is wrapped into an `Expr`:
+    - Router would generate the code that handles IO for the subroutine:
+      - Generated code parses `Txn.application_args`, converts them into ABI value instances, and passes them into method.
+      - Generated code logs method result, and exit program with `Approve()`.
+
+    Because of the introduction of `frame_pointer`, there are 2 flavors of generated code:
+    - pre-frame-pointer (scratch slot based)
+    - frame-pointer based.
+
+    For more details, refer to implementation of `ASTBuilder.wrap_handler`.
+    """
+
+    method_sig: str
+    condition: Expr | int
+    method: ABIReturnSubroutine
+
+    def to_cond_node(self, use_frame_pt: bool = False) -> CondNode:
+        walk_in_cond = Txn.application_args[0] == MethodSignature(self.method_sig)
+
+        if not (isinstance(self.condition, Expr) or self.condition == 1):
+            raise TealInputError("Invalid condition input for CondWithMethod")
+
+        res = ASTBuilder.wrap_handler(True, self.method, use_frame_pt=use_frame_pt)
+        if isinstance(self.condition, Expr):
+            res = Seq(Assert(self.condition), res)
+        return CondNode(walk_in_cond, res)
+
+
+CondWithMethod.__module__ = "pyteal"
+
+
 class ASTBuilder:
-    conditions_n_branches: list[CondNode] = field(default_factory=list)
+    def __init__(self):
+        self.methods_with_conds: list[CondWithMethod] = []
+        self.bare_calls: list[CondNode] = []
+
+    def _clean_bare_calls(self) -> None:
+        self.bare_calls = []
+
+    @staticmethod
+    def __filter_invalid_handlers_and_typecast(
+        subroutine: ABIReturnSubroutine | SubroutineFnWrapper | Expr,
+    ) -> ABIReturnSubroutine:
+        """This method filters out invalid handlers that might be normal subroutine, Expr, or unroutable ABIReturnSubroutine.
+
+        It accepts only routable ABIReturnSubroutine, and shrink the type to ABIReturnSubroutine from argument's union type.
+        """
+        if not isinstance(subroutine, ABIReturnSubroutine):
+            raise TealInputError(
+                f"method call should be only registering ABIReturnSubroutine, got {type(subroutine)}."
+            )
+        if not subroutine.is_abi_routable():
+            raise TealInputError(
+                f"method call ABIReturnSubroutine is not routable: "
+                f"got {subroutine.subroutine.argument_count()} args "
+                f"with {len(subroutine.subroutine.abi_args)} ABI args."
+            )
+        return subroutine
+
+    @staticmethod
+    def __subroutine_argument_instance_generate(
+        subroutine: ABIReturnSubroutine,
+    ) -> tuple[list[abi.BaseType], list[abi.BaseType], list[abi.Transaction]]:
+        # All subroutine args types
+        type_specs = cast(list[abi.TypeSpec], subroutine.subroutine.expected_arg_types)
+
+        # All subroutine arg values, initialize here and use below instead of
+        # creating new instances on the fly, so we don't have to think about splicing
+        # back in the transaction types
+        arg_vals = [typespec.new_instance() for typespec in type_specs]
+
+        # Only args that appear in app args
+        app_arg_vals: list[abi.BaseType] = [
+            ats for ats in arg_vals if not isinstance(ats, abi.Transaction)
+        ]
+
+        # only transaction args (these are omitted from app args)
+        txn_arg_vals: list[abi.Transaction] = [
+            ats for ats in arg_vals if isinstance(ats, abi.Transaction)
+        ]
+
+        for aav in app_arg_vals:
+            # If we're here we know the top level isn't a Transaction but a transaction may
+            # be included in some collection type like a Tuple or Array, raise error
+            # as these are not supported
+            if abi.contains_type_spec(aav.type_spec(), abi.TransactionTypeSpecs):
+                raise TealInputError(
+                    "A Transaction type may not be included in Tuples or Arrays"
+                )
+
+        return arg_vals, app_arg_vals, txn_arg_vals
+
+    @staticmethod
+    def __decode_constructions_and_args(
+        arg_vals: list[abi.BaseType],
+        app_arg_vals: list[abi.BaseType],
+        txn_arg_vals: list[abi.Transaction],
+        subroutine: ABIReturnSubroutine,
+        use_frame_pt: bool = False,
+    ) -> tuple[list[Expr], list[abi.BaseType], Optional[Proto]]:
+        """
+        Assumption: arg_vals = app_args_vals union with txn_arg_vals
+        """
+
+        # if subroutine has ABI output, then local variables start from 1
+        # otherwise local variables start from 0
+        index_start_from = 0 if subroutine.output_kwarg_info is None else 1
+
+        # prepare the local stack type list for local variable allocation
+        local_types: list[TealType] = [i._stored_value.storage_type() for i in arg_vals]
+
+        if subroutine.output_kwarg_info:
+            local_types = [
+                subroutine.output_kwarg_info.abi_type.storage_type()
+            ] + local_types
+
+        # assign to a var here since we modify app_arg_vals later
+        tuplify = len(app_arg_vals) > METHOD_ARG_NUM_CUTOFF
+
+        # Tuple-ify any app args after the limit
+        tupled_app_args: list[abi.BaseType] = []
+
+        if tuplify:
+            tupled_app_args = app_arg_vals[METHOD_ARG_NUM_CUTOFF - 1 :]
+            last_arg_specs_grouped: list[abi.TypeSpec] = [
+                t.type_spec() for t in tupled_app_args
+            ]
+            app_arg_vals = app_arg_vals[: METHOD_ARG_NUM_CUTOFF - 1]
+            app_args_tupled = abi.TupleTypeSpec(*last_arg_specs_grouped).new_instance()
+            local_types.append(app_args_tupled._stored_value.storage_type())
+            app_arg_vals.append(app_args_tupled)
+
+        proto: Optional[Proto] = None
+        if use_frame_pt:
+            proto = Proto(0, 0, mem_layout=ProtoStackLayout([], local_types, 0))
+            for i, arg_val in enumerate(arg_vals):
+                arg_val._stored_value = FrameVar(proto, i + index_start_from)
+            if tuplify:
+                app_arg_vals[-1]._stored_value = FrameVar(proto, len(local_types) - 1)
+
+        # decode app args
+        decode_instructions: list[Expr] = [
+            app_arg.decode(Txn.application_args[idx + 1])
+            for idx, app_arg in enumerate(app_arg_vals)
+        ]
+
+        # "decode" transaction types by setting the relative index
+        if len(txn_arg_vals) > 0:
+            txn_arg_len = len(txn_arg_vals)
+            # The transactions should appear in the group in the order they're specified in the method signature
+            # and should be relative to the current transaction.
+
+            # ex:
+            # doit(axfer,pay,appl)
+            # would be 4 transactions
+            #      current_idx-3 = axfer
+            #      current_idx-2 = pay
+            #      current_idx-1 = appl
+            #      current_idx-0 = the txn that triggered the current eval (not specified but here for completeness)
+
+            # since we're iterating in order of the txns appearance in the args we
+            # subtract the current index from the total length to get the offset.
+            # and subtract that from the current index to get the absolute position
+            # in the group
+
+            txn_decode_instructions: list[Expr] = []
+
+            for idx, arg_val in enumerate(txn_arg_vals):
+                txn_decode_instructions.append(
+                    arg_val._set_index(Txn.group_index() - Int(txn_arg_len - idx))
+                )
+                spec = arg_val.type_spec()
+                if type(spec) is not abi.TransactionTypeSpec:
+                    # this is a specific transaction type
+                    txn_decode_instructions.append(
+                        Assert(arg_val.get().type_enum() == spec.txn_type_enum())
+                    )
+
+            decode_instructions += txn_decode_instructions
+
+        # de-tuple into specific values using `store_into` on
+        # each element of the tuple'd arguments
+        if tuplify:
+            tupled_arg: abi.Tuple = cast(abi.Tuple, app_arg_vals[-1])
+            de_tuple_instructions: list[Expr] = [
+                tupled_arg[idx].store_into(arg_val)
+                for idx, arg_val in enumerate(tupled_app_args)
+            ]
+            decode_instructions += de_tuple_instructions
+
+        return decode_instructions, arg_vals, proto
 
     @staticmethod
     def wrap_handler(
-        is_method_call: bool, handler: ActionType, wrap_to_name: str | None = None
+        is_method_call: bool,
+        handler: ActionType,
+        *,
+        wrap_to_name: str | None = None,
+        use_frame_pt: bool = False,
     ) -> Expr:
         """This is a helper function that handles transaction arguments passing in bare-app-call/abi-method handlers.
 
@@ -290,6 +515,7 @@ def wrap_handler(
         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`.
+            use_frame_pt: a boolean value that specify if router is compiled to frame pointer based code.
         Returns:
             Expr:
                 - for bare-appcall it returns an expression that the handler takes no txn arg and Approve
@@ -297,7 +523,7 @@ def wrap_handler(
                   passed in ABIReturnSubroutine and logged, then approve.
         """
         if not is_method_call:
-            wrap_to_name = "bare appcall" if wrap_to_name is None else wrap_to_name
+            wrap_to_name = wrap_to_name or "bare appcall"
 
             match handler:
                 case Expr():
@@ -332,157 +558,211 @@ def wrap_handler(
                     raise TealInputError(
                         f"{wrap_to_name} can only accept: none type Expr, or Subroutine/ABIReturnSubroutine with none return and no arg"
                     )
-        else:
-            wrap_to_name = "method call" if wrap_to_name is None else wrap_to_name
-            if not isinstance(handler, ABIReturnSubroutine):
-                raise TealInputError(
-                    f"{wrap_to_name} should be only registering ABIReturnSubroutine, got {type(handler)}."
-                )
-            if not handler.is_abi_routable():
-                raise TealInputError(
-                    f"{wrap_to_name} ABIReturnSubroutine is not routable "
-                    f"got {handler.subroutine.argument_count()} args with {len(handler.subroutine.abi_args)} ABI args."
-                )
 
-            # All subroutine args types
-            arg_type_specs = cast(
-                list[abi.TypeSpec], handler.subroutine.expected_arg_types
+        # else: method case
+        wrap_to_name = wrap_to_name or "method call"
+        if not isinstance(handler, ABIReturnSubroutine):
+            raise TealInputError(
+                f"{wrap_to_name} should be only registering ABIReturnSubroutine, got {type(handler)}."
+            )
+        if not handler.is_abi_routable():
+            raise TealInputError(
+                f"{wrap_to_name} ABIReturnSubroutine is not routable "
+                f"got {handler.subroutine.argument_count()} args with {len(handler.subroutine.abi_args)} ABI args."
             )
+        if not use_frame_pt:
+            return ASTBuilder.__de_abify_subroutine_vanilla(handler)
+        else:
+            return ASTBuilder.__de_abify_subroutine_frame_pointers(handler)
 
-            # All subroutine arg values, initialize here and use below instead of
-            # creating new instances on the fly, so we don't have to think about splicing
-            # back in the transaction types
-            arg_vals = [typespec.new_instance() for typespec in arg_type_specs]
+    @staticmethod
+    def __de_abify_subroutine_vanilla(handler: ActionType) -> Expr:
+        """This private function retains the previous (pre-frame-pointer) logic of handling ABIReturnSubroutine method's IO.
+
+        This function can be roughly separated into following 4 parts:
+        - handler type check
+        - handler argument instance generate
+        - handler argument decode from Txn.application_args
+        - generating execution branch that calls handler and handles handler return.
+          NOTE: the very last step is handled differently from the frame pointer version; see the illustration below:
+
+        |
+        | main execution
+        |
+        `-> (assuming satisfying all preconditions: method selector match + OnComplete options match)
+            +-----------------------------------------------------------------------------------+
+            | We need to first allocate some scratch slots for the handler's ABI arguments,     |
+            | for we don't want the intermediate handler steps to destroy memory on arguments.  |
+            |                                                                                   |
+            | Decoding scheme relies on internal storage in ABI value (default scratch slot).   |
+            | NOTE: if the handler has more than 15 args, we need to de-tuple the last one.     |
+            |       The detupling process is also done over the scratch slots.                  |
+            |                                                                                   |
+            | This section represents the expressions in `decoding_steps`.                      |
+            +-----------------------------------------------------------------------------------+
+            | At this point, all of the handler arguments (ABI typed) are prepared.             |
+            | We call the handler with all these handler arguments.                             |
+            | If the handler has output, we dig the result to top of the stack and log it.      |
+            |                                                                                   |
+            | NOTE: if output exists inside of a subroutine, we alloc a scratch slot for output.|
+            |       Right before retsub, use `deferred_expr` to place output encoding on stack. |
+            +-----------------------------------------------------------------------------------+
+            | Now that the handler return (if exists) is handled, we `Approve()` and exit prog. |
+            +-----------------------------------------------------------------------------------+
+        """
+        handler = ASTBuilder.__filter_invalid_handlers_and_typecast(handler)
+        (
+            arg_vals,
+            app_arg_vals,
+            txn_arg_vals,
+        ) = ASTBuilder.__subroutine_argument_instance_generate(handler)
+
+        (
+            decode_instructions,
+            arg_vals,
+            _,
+        ) = ASTBuilder.__decode_constructions_and_args(
+            arg_vals, app_arg_vals, txn_arg_vals, handler
+        )
 
-            # Only args that appear in app args
-            app_arg_vals: list[abi.BaseType] = [
-                ats for ats in arg_vals if not isinstance(ats, abi.Transaction)
-            ]
+        if handler.type_of() == sdk_abi.Returns.VOID:
+            return Seq(
+                *decode_instructions,
+                cast(SubroutineCall, handler(*arg_vals)),
+                Approve(),
+            )
+        else:
+            output_temp: abi.BaseType = cast(
+                OutputKwArgInfo, handler.output_kwarg_info
+            ).abi_type.new_instance()
+            returned_val: abi.ReturnedValue = cast(
+                abi.ReturnedValue, handler(*arg_vals)
+            )
+            return Seq(
+                *decode_instructions,
+                returned_val.store_into(output_temp),
+                abi.MethodReturn(output_temp),
+                Approve(),
+            )
 
-            for aav in app_arg_vals:
-                # If we're here we know the top level isnt a Transaction but a transaction may
-                # be included in some collection type like a Tuple or Array, raise error
-                # as these are not supported
-                if abi.contains_type_spec(aav.type_spec(), abi.TransactionTypeSpecs):
-                    raise TealInputError(
-                        "A Transaction type may not be included in Tuples or Arrays"
-                    )
+    @staticmethod
+    def __de_abify_subroutine_frame_pointers(handler: ActionType) -> Expr:
+        """This private function implements the frame-pointer-based logic of handling ABIReturnSubroutine method's IO.
+
+        This function can be roughly separated into following 4 parts:
+        - handler type check
+        - handler argument instance generate
+        - handler argument decode from Txn.application_args (with use_frame_pt=True option on)
+        - generating execution branch that calls the handler and handles handler return.
+          NOTE: the very last step is handled differently from the frame pointer version; see the illustration below:
+
+        |
+        | main execution
+        |
+        `-> (assuming satisfying all preconditions: method selector match + OnComplete options match)
+            +-----------------------------------------------------------------------------------+
+            | We wrap the following section up in an intermediate function:                     |
+            +-----------------------------------------------------------------------------------+
+            |                                                                                   |
+            |  We construct an intermediate subroutine with 0 args and 0 returns as follows:    |
+            |  +--------------------------------------------------------------------------------+
+            |  | Thus we use `proto 0 0` to clean up stack once handler computation completes.  |
+            |  +--------------------------------------------------------------------------------+
+            |  |We need to allocate some stack space to handle memory for the following 2 cases:|
+            |  | - If handler has an ABI output returning, need to store in a grid on stack.    |
+            |  | - All of the other handler arguments from `Txn.application_args` should be     |
+            |  |   decoded into ABI values and placed on stack.                                 |
+            |  |   NOTE: if the handler has more than 15 args, we need to de-tuple the last one.|
+            |  |         The detupling process is also done over the stack with frame pointers. |
+            |  |                                                                                |
+            |  | To keep track of each arg's memory location,                                   |
+            |  | we use FrameVar to keep track of relative dist against stack height at proto.  |
+            |  |                                                                                |
+            |  | Also notice that all of the decoding operations are done over local vars,      |
+            |  | proto 0 0 can come in and clean all the stack variables away.                  |
+            |  |                                                                                |
+            |  | This section represents the expressions in `decoding_steps`.                   |
+            |  +--------------------------------------------------------------------------------+
+            |  | At this point, all of the handler arguments (ABI typed) are prepared.          |
+            |  | We call the handler with all of these handler arguments.                       |
+            |  | If the handler has output, we dig the result to the top of stack and log it.   |
+            |  |                                                                                |
+            |  | This section represents the expressions in `returning_steps`.                  |
+            +  +--------------------------------------------------------------------------------+
+            |                                                                                   |
+            | Now that handler is done computing in the intermediate function,                  |
+            | we `Approve()` and exit prog.                                                     |
+            +-----------------------------------------------------------------------------------+
+        """
+        handler = ASTBuilder.__filter_invalid_handlers_and_typecast(handler)
+        (
+            arg_vals,
+            app_arg_vals,
+            txn_arg_vals,
+        ) = ASTBuilder.__subroutine_argument_instance_generate(handler)
+
+        (
+            decode_instructions,
+            arg_vals,
+            proto,
+        ) = ASTBuilder.__decode_constructions_and_args(
+            arg_vals,
+            app_arg_vals,
+            txn_arg_vals,
+            handler,
+            use_frame_pt=True,
+        )
 
-            # assign to a var here since we modify app_arg_vals later
-            tuplify = len(app_arg_vals) > METHOD_ARG_NUM_CUTOFF
+        subroutine_caster = Subroutine(TealType.none, f"{handler.name()}_caster")
 
-            # only transaction args (these are omitted from app args)
-            txn_arg_vals: list[abi.Transaction] = [
-                ats for ats in arg_vals if isinstance(ats, abi.Transaction)
-            ]
+        proto = cast(Proto, proto)
+        proto.mem_layout = cast(ProtoStackLayout, proto.mem_layout)
 
-            # Tuple-ify any app args after the limit
-            if tuplify:
-                tupled_app_args = app_arg_vals[METHOD_ARG_NUM_CUTOFF - 1 :]
-                last_arg_specs_grouped: list[abi.TypeSpec] = [
-                    t.type_spec() for t in tupled_app_args
-                ]
-                app_arg_vals = app_arg_vals[: METHOD_ARG_NUM_CUTOFF - 1]
-                app_arg_vals.append(
-                    abi.TupleTypeSpec(*last_arg_specs_grouped).new_instance()
-                )
+        decoding_steps: list[Expr] = [
+            *proto.mem_layout._succinct_repr(),
+            *decode_instructions,
+        ]
+        returning_steps: list[Expr]
 
-            # decode app args
-            decode_instructions: list[Expr] = [
-                app_arg.decode(Txn.application_args[idx + 1])
-                for idx, app_arg in enumerate(app_arg_vals)
+        if handler.type_of() == sdk_abi.Returns.VOID:
+            returning_steps = [cast(SubroutineCall, handler(*arg_vals))]
+        else:
+            output_temp: abi.BaseType = cast(
+                OutputKwArgInfo, handler.output_kwarg_info
+            ).abi_type.new_instance()
+            output_temp._stored_value = FrameVar(proto, 0)
+            returned_val: abi.ReturnedValue = cast(
+                abi.ReturnedValue, handler(*arg_vals)
+            )
+            returning_steps = [
+                returned_val.store_into(output_temp),
+                abi.MethodReturn(output_temp),
             ]
 
-            # "decode" transaction types by setting the relative index
-            if len(txn_arg_vals) > 0:
-                txn_arg_len = len(txn_arg_vals)
-                # The transactions should appear in the group in the order they're specified in the method signature
-                # and should be relative to the current transaction.
-
-                # ex:
-                # doit(axfer,pay,appl)
-                # would be 4 transactions
-                #      current_idx-3 = axfer
-                #      current_idx-2 = pay
-                #      current_idx-1 = appl
-                #      current_idx-0 = the txn that triggered the current eval (not specified but here for completeness)
-
-                # since we're iterating in order of the txns appearance in the args we
-                # subtract the current index from the total length to get the offset.
-                # and subtract that from the current index to get the absolute position
-                # in the group
-
-                txn_decode_instructions: list[Expr] = []
-
-                for idx, arg_val in enumerate(txn_arg_vals):
-                    txn_decode_instructions.append(
-                        arg_val._set_index(Txn.group_index() - Int(txn_arg_len - idx))
-                    )
-                    spec = arg_val.type_spec()
-                    if type(spec) is not abi.TransactionTypeSpec:
-                        # this is a specific transaction type
-                        txn_decode_instructions.append(
-                            Assert(arg_val.get().type_enum() == spec.txn_type_enum())
-                        )
-
-                decode_instructions += txn_decode_instructions
+        def declaration():
+            return Seq(*decoding_steps, *returning_steps)
 
-            # de-tuple into specific values using `store_into` on
-            # each element of the tuple'd arguments
-            if tuplify:
-                tupled_arg: abi.Tuple = cast(abi.Tuple, app_arg_vals[-1])
-                de_tuple_instructions: list[Expr] = [
-                    tupled_arg[idx].store_into(arg_val)
-                    for idx, arg_val in enumerate(tupled_app_args)
-                ]
-                decode_instructions += de_tuple_instructions
-
-            # NOTE: does not have to have return, can be void method
-            if handler.type_of() == "void":
-                return Seq(
-                    *decode_instructions,
-                    cast(Expr, handler(*arg_vals)),
-                    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_vals)
-                )
-                return Seq(
-                    *decode_instructions,
-                    subroutine_call.store_into(output_temp),
-                    abi.MethodReturn(output_temp),
-                    Approve(),
-                )
+        return Seq(subroutine_caster(declaration)(), 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")
+        if isinstance(cond, int) and cond == 0:
+            return
+        self.methods_with_conds.append(CondWithMethod(method_signature, cond, handler))
+
+    def program_construction(self, use_frame_pt: bool = False) -> Expr:
+        conditions_n_branches: list[CondNode] = self.bare_calls + [
+            method_with_cond.to_cond_node(use_frame_pt=use_frame_pt)
+            for method_with_cond in self.methods_with_conds
+        ]
 
-    def program_construction(self) -> Expr:
-        if not self.conditions_n_branches:
+        if not conditions_n_branches:
             return Reject()
-        return Cond(*[[n.condition, n.branch] for n in self.conditions_n_branches])
+        return Cond(*[[n.condition, n.branch] for n in conditions_n_branches])
+
+
+ASTBuilder.__module__ = "pyteal"
 
 
 class Router:
@@ -526,22 +806,25 @@ def __init__(
         self.clear_state: Expr = (
             Reject()
             if clear_state is None
-            else ASTBuilder.wrap_handler(False, clear_state, "clear state call")
+            else ASTBuilder.wrap_handler(
+                False, clear_state, wrap_to_name="clear state call"
+            )
         )
 
         self.methods: list[sdk_abi.Method] = []
         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),
-                    )
-                )
+        self.bare_call_actions: BareCallActions = bare_calls or BareCallActions()
+
+        # maps method signature (or None for bare call) to MethodConfig:
+        self.method_configs: dict[str | None, MethodConfig] = dict()
+
+        if not self.bare_call_actions.is_empty():
+            self.method_configs[None] = self.bare_call_actions.get_method_config()
+
+    def _clean(self) -> None:
+        self.approval_ast._clean_bare_calls()
 
     def add_method_handler(
         self,
@@ -565,7 +848,7 @@ def add_method_handler(
         """
         if not isinstance(method_call, ABIReturnSubroutine):
             raise TealInputError(
-                "for adding method handler, must be ABIReturnSubroutine"
+                f"for adding method handler, must be ABIReturnSubroutine but method_call is {type(method_call)}"
             )
         method_signature = method_call.method_signature(overriding_name)
         if method_config is None:
@@ -596,6 +879,7 @@ def add_method_handler(
         self.approval_ast.add_method_to_ast(
             method_signature, method_approval_cond, method_call
         )
+        self.method_configs[method_signature] = method_config
         return method_call
 
     def method(
@@ -662,15 +946,11 @@ def wrap(_func) -> ABIReturnSubroutine:
             ):
                 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)
-                _update_app = none_to_never(update_application)
-                _delete_app = none_to_never(delete_application)
+                _no_op = no_op or CallConfig.NEVER
+                _opt_in = opt_in or CallConfig.NEVER
+                _close_out = close_out or CallConfig.NEVER
+                _update_app = update_application or CallConfig.NEVER
+                _delete_app = delete_application or CallConfig.NEVER
 
                 call_configs = MethodConfig(
                     no_op=_no_op,
@@ -699,7 +979,12 @@ def contract_construct(self) -> sdk_abi.Contract:
 
         return sdk_abi.Contract(self.name, self.methods, self.descr)
 
-    def build_program(self) -> tuple[Expr, Expr, sdk_abi.Contract]:
+    def _build_program(
+        self,
+        *,
+        version: int = DEFAULT_TEAL_VERSION,
+        optimize: OptimizeOptions | None = None,
+    ) -> tuple[Expr, Expr, sdk_abi.Contract]:
         """
         Constructs ASTs for approval and clear-state programs from the registered methods and bare
         app calls in the router, and also generates a Contract object to allow client read and call
@@ -715,12 +1000,33 @@ def build_program(self) -> tuple[Expr, Expr, sdk_abi.Contract]:
             * clear_state_program: an AST for clear-state program
             * contract: a Python SDK Contract object to allow clients to make off-chain calls
         """
+        if not self.bare_call_actions.is_empty():
+            bare_call_approval = self.bare_call_actions.approval_construction()
+            if bare_call_approval:
+                self.approval_ast.bare_calls = [
+                    CondNode(
+                        Txn.application_args.length() == Int(0),
+                        cast(Expr, bare_call_approval),
+                    )
+                ]
+
+        optimize = optimize or OptimizeOptions()
+        use_frame_pt = optimize.use_frame_pointers(version)
         return (
-            self.approval_ast.program_construction(),
+            self.approval_ast.program_construction(use_frame_pt=use_frame_pt),
             self.clear_state,
             self.contract_construct(),
         )
 
+    @contextmanager
+    def _cleaning_context(self):
+        starting_slot_id = ScratchSlot.nextSlotId
+        try:
+            yield
+        finally:
+            self._clean()
+            ScratchSlot.reset_slot_numbering(starting_slot_id)
+
     def compile_program(
         self,
         *,
@@ -733,7 +1039,7 @@ def compile_program(
         bare app calls in the router, and also generates a Contract object to allow client read and call
         the methods easily.
 
-        This method combines :any:`Router.build_program` and :any:`compileTeal`.
+        This method combines `Router._build_program` and :any:`compileTeal`.
 
         Note that if no methods or bare app call actions have been registered to either the approval
         or clear state programs, then that program will reject all transactions.
@@ -745,21 +1051,22 @@ def compile_program(
             * clear_state_program: compiled clear-state program string
             * contract: a Python SDK Contract object to allow clients to make off-chain calls
         """
-        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,
-        )
+        with self._cleaning_context():
+            ap, csp, contract = self._build_program(version=version, optimize=optimize)
+            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
 
 
diff --git a/pyteal/ast/scratch.py b/pyteal/ast/scratch.py
index abf8821fe..6b2cf4702 100644
--- a/pyteal/ast/scratch.py
+++ b/pyteal/ast/scratch.py
@@ -15,7 +15,11 @@ class ScratchSlot:
     # Unique identifier for the compiler to automatically assign slots
     # The id field is used by the compiler to map to an actual slot in the source code
     # Slot ids under 256 are manually reserved slots
-    nextSlotId = NUM_SLOTS
+    nextSlotId: int = NUM_SLOTS
+
+    @classmethod
+    def reset_slot_numbering(cls, start_index: int = NUM_SLOTS) -> None:
+        cls.nextSlotId = start_index
 
     def __init__(self, requestedSlotId: int | None = None):
         """Initializes a scratch slot with a particular id
diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py
index ff6203805..623905513 100644
--- a/pyteal/ast/subroutine.py
+++ b/pyteal/ast/subroutine.py
@@ -53,13 +53,13 @@ def get_declaration_by_option(
         return cast(SubroutineDeclaration, self.option_map[fp_option])
 
     def __probe_info(self, fp_option: bool) -> tuple[bool, TealType]:
-        current_slot_id = ScratchSlot.nextSlotId
+        starting_slot_id = ScratchSlot.nextSlotId
         is_pre_existing = self.option_map[fp_option] is not None
         decl = self.get_declaration_by_option(fp_option)
         has_return, type_of = decl.has_return(), decl.type_of()
         if not is_pre_existing:
             self.option_map[fp_option] = None
-        ScratchSlot.nextSlotId = current_slot_id
+        ScratchSlot.reset_slot_numbering(starting_slot_id)
         return has_return, type_of
 
     def __info_prepare(self) -> None:
@@ -542,7 +542,9 @@ def __init__(
             name_str=name,
         )
 
-    def __call__(self, *args: Expr | ScratchVar | abi.BaseType, **kwargs: Any) -> Expr:
+    def __call__(
+        self, *args: Expr | ScratchVar | abi.BaseType, **kwargs: Any
+    ) -> SubroutineCall:
         if len(kwargs) != 0:
             raise TealInputError(
                 f"Subroutine cannot be called with keyword arguments. "
@@ -662,7 +664,7 @@ def _get_output_kwarg_info(
 
     def __call__(
         self, *args: Expr | ScratchVar | abi.BaseType, **kwargs
-    ) -> abi.ReturnedValue | Expr:
+    ) -> abi.ReturnedValue | SubroutineCall:
         if len(kwargs) != 0:
             raise TealInputError(
                 f"Subroutine cannot be called with keyword arguments. "
diff --git a/pyteal/compiler/compiler.py b/pyteal/compiler/compiler.py
index a85325ab9..8186822cd 100644
--- a/pyteal/compiler/compiler.py
+++ b/pyteal/compiler/compiler.py
@@ -2,17 +2,7 @@
 
 from pyteal.compiler.optimizer import OptimizeOptions, apply_global_optimizations
 
-from pyteal.types import TealType
-from pyteal.ast import (
-    Expr,
-    Return,
-    Seq,
-    SubroutineDefinition,
-    SubroutineDeclaration,
-)
-from pyteal.ir import Mode, Op, TealComponent, TealOp, TealBlock, TealSimpleBlock
-from pyteal.errors import TealInputError, TealInternalError
-
+from pyteal.ast import Expr, Return, Seq, SubroutineDefinition, SubroutineDeclaration
 from pyteal.compiler.sort import sortBlocks
 from pyteal.compiler.flatten import flattenBlocks, flattenSubroutines
 from pyteal.compiler.scratchslots import (
@@ -24,6 +14,10 @@
     resolveSubroutines,
 )
 from pyteal.compiler.constants import createConstantBlocks
+from pyteal.errors import TealInputError, TealInternalError
+from pyteal.ir import Mode, Op, TealComponent, TealOp, TealBlock, TealSimpleBlock
+from pyteal.types import TealType
+
 
 MAX_PROGRAM_VERSION = 8
 FRAME_POINTERS_VERSION = 8
diff --git a/requirements.txt b/requirements.txt
index ba07a0959..5811b9870 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,7 +1,7 @@
 black==22.3.0
 flake8==5.0.4
 flake8-tidy-imports==4.6.0
-graviton@git+https://github.com/algorand/graviton@v0.8.0
+graviton@git+https://github.com/algorand/graviton@v0.9.0
 mypy==0.991
 pytest==7.2.0
 pytest-cov==3.0.0


Test patch:
diff --git a/pyteal/ast/router_test.py b/pyteal/ast/router_test.py
index a50bc5996..2eb1fb937 100644
--- a/pyteal/ast/router_test.py
+++ b/pyteal/ast/router_test.py
@@ -1,10 +1,12 @@
-import pyteal as pt
-import secrets
-from pyteal.ast.router import ASTBuilder
 import pytest
+import secrets
 import typing
+
 import algosdk.abi as sdk_abi
 
+import pyteal as pt
+from pyteal.ast.router import ASTBuilder
+
 
 options = pt.CompileOptions(version=5)
 
@@ -378,7 +380,7 @@ def test_bare_call_config_clear_state_failure():
     assert "Attempt to construct clear state program from bare app call" in str(tie)
 
 
-def test_bare_call_actions_asdict():
+def test_BareCallActions_asdict():
     no_action = pt.OnCompleteAction()
     del_action = pt.OnCompleteAction(action=pt.Int(1), call_config=pt.CallConfig.ALL)
     close_action = pt.OnCompleteAction(action=pt.Int(2), call_config=pt.CallConfig.CALL)
@@ -407,6 +409,35 @@ def test_bare_call_actions_asdict():
     }
 
 
+def test_BareCallActions_get_method_config():
+    from pyteal.ast.router import MethodConfig, CallConfig
+
+    cc_all, cc_call, cc_create = (
+        pt.CallConfig.ALL,
+        pt.CallConfig.CALL,
+        pt.CallConfig.CREATE,
+    )
+    optin_action = pt.OnCompleteAction(action=pt.Int(1), call_config=cc_all)
+    noop_action = pt.OnCompleteAction(action=pt.Int(2), call_config=cc_call)
+    update_action = pt.OnCompleteAction(action=pt.Int(3), call_config=cc_create)
+
+    bca = pt.BareCallActions(
+        update_application=update_action,
+        opt_in=optin_action,
+        no_op=noop_action,
+    )
+
+    mc = bca.get_method_config()
+    assert mc == MethodConfig(
+        close_out=CallConfig.NEVER,
+        delete_application=CallConfig.NEVER,
+        no_op=cc_call,
+        opt_in=cc_all,
+        update_application=cc_create,
+        clear_state=CallConfig.NEVER,
+    )
+
+
 def test_router_register_method_clear_state_failure():
     router = pt.Router("doomedToFail")
 
@@ -766,7 +797,7 @@ def test_contract_json_obj():
 
         # Manually replace it since the override is applied in the method handler
         # not attached to the ABIReturnSubroutine itself
-        ms.desc = desc if desc is not None else ms.desc
+        ms.desc = desc or ms.desc
 
         sig_method = sdk_abi.Method.from_signature(subroutine.method_signature())
 
@@ -785,7 +816,7 @@ def test_contract_json_obj():
 def test_build_program_all_empty():
     router = pt.Router("test")
 
-    approval, clear_state, contract = router.build_program()
+    approval, clear_state, contract = router._build_program()
 
     expected_empty_program = pt.TealSimpleBlock(
         [
@@ -805,7 +836,7 @@ def test_build_program_all_empty():
 def test_build_program_approval_empty():
     router = pt.Router("test", clear_state=pt.Approve())
 
-    approval, clear_state, contract = router.build_program()
+    approval, clear_state, contract = router._build_program()
 
     expected_empty_program = pt.TealSimpleBlock(
         [
@@ -827,7 +858,7 @@ def test_build_program_clear_state_empty():
         "test", pt.BareCallActions(no_op=pt.OnCompleteAction.always(pt.Approve()))
     )
 
-    approval, clear_state, contract = router.build_program()
+    approval, clear_state, contract = router._build_program()
 
     expected_empty_program = pt.TealSimpleBlock(
         [
@@ -844,6 +875,42 @@ def test_build_program_clear_state_empty():
     assert contract == expected_contract
 
 
+def test_build_program_clear_state_invalid_config():
+    for config in (pt.CallConfig.CREATE, pt.CallConfig.ALL):
+        with pytest.raises(
+            pt.TealInputError,
+            match=r"^Attempt to construct clear state program from bare app call",
+        ):
+            pt.BareCallActions(
+                clear_state=pt.OnCompleteAction(action=pt.Approve(), call_config=config)
+            )
+
+        router = pt.Router("test")  # once without a clear_state
+        router = pt.Router("test", clear_state=pt.Approve())  # and for the rest, with
+
+        with pytest.raises(
+            pt.TealInputError,
+            match=r"^Attempt to register ABI method for clear state program",
+        ):
+
+            @router.method(clear_state=pt.Int(1))
+            def clear_state_method_fails():
+                return pt.Approve()
+
+        @pt.ABIReturnSubroutine
+        def clear_state_method_succeeds():
+            return pt.Approve()
+
+        with pytest.raises(
+            pt.TealInputError,
+            match=r"^Attempt to construct clear state program from MethodConfig",
+        ):
+            router.add_method_handler(
+                clear_state_method_succeeds,
+                method_config=pt.MethodConfig(clear_state=config),
+            )
+
+
 def test_build_program_clear_state_valid_config():
     action = pt.If(pt.Txn.fee() == pt.Int(4)).Then(pt.Approve()).Else(pt.Reject())
 
@@ -851,7 +918,7 @@ def test_build_program_clear_state_valid_config():
         "test",
         clear_state=action,
     )
-    _, actual_clear_state_with_bare_call, _ = router_with_bare_call.build_program()
+    _, actual_clear_state_with_bare_call, _ = router_with_bare_call._build_program()
 
     expected_clear_state_with_bare_call = assemble_helper(action)
 
@@ -897,3 +964,42 @@ def handle(deposit: pt.abi.PaymentTransaction):  # noqa: F811
     ap2, cs2, c2 = r2.compile_program(version=pt.compiler.MAX_PROGRAM_VERSION)
 
     assert (ap1, cs1, c1) == (ap2, cs2, c2)
+
+
+def test_router_compile_program_idempotence():
+    on_completion_actions = pt.BareCallActions(
+        opt_in=pt.OnCompleteAction.call_only(pt.Log(pt.Bytes("optin call"))),
+    )
+    router = pt.Router("questionable", on_completion_actions, clear_state=pt.Approve())
+
+    approval1, clear1, contract1 = router.compile_program(version=6)
+    approval2, clear2, contract2 = router.compile_program(version=6)
+
+    assert contract1.dictify() == contract2.dictify()
+    assert clear1 == clear2
+    assert approval1 == approval2
+
+    @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())
+
+    meth = router.add_method_handler(add)
+    assert meth.method_signature() == "add(uint64,uint64)uint64"
+
+    # formerly nextSlotId: 256 --> 262:
+    approval1, clear1, contract1 = router.compile_program(version=6)
+    # formerly nextSlotId: 262 --> 265:
+    approval2, clear2, contract2 = router.compile_program(version=6)
+    # formerly nextSlotId: 265 --> 268:
+    approval3, clear3, contract3 = router.compile_program(version=6)
+
+    assert contract2.dictify() == contract3.dictify()
+    assert clear3 == clear2
+    assert approval3 == approval3
+
+    assert contract2.dictify() == contract1.dictify()
+    assert clear2 == clear1
+    assert (
+        approval2 == approval1
+    ), f"""{approval1=}
+{approval2=}"""
diff --git a/pyteal/compiler/compiler_test.py b/pyteal/compiler/compiler_test.py
index ae7c9117b..5e855944f 100644
--- a/pyteal/compiler/compiler_test.py
+++ b/pyteal/compiler/compiler_test.py
@@ -1,7 +1,11 @@
+from pathlib import Path
+
 import pytest
 
 import pyteal as pt
 
+ROUTER_FIXTURES = Path.cwd() / "tests" / "teal" / "router"
+
 
 def test_compile_single():
     expr = pt.Int(1)
@@ -2195,1079 +2199,258 @@ def access_b4_store(magic_num: pt.abi.Uint64, *, output: pt.abi.Uint64):
         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())
-
-        meth = router.add_method_handler(add)
-        assert meth.method_signature() == "add(uint64,uint64)uint64"
-
-        @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())
-
-        meth = router.add_method_handler(sub)
-        assert meth.method_signature() == "sub(uint64,uint64)uint64"
-
-        @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())
-
-        meth = router.add_method_handler(mul)
-        assert meth.method_signature() == "mul(uint64,uint64)uint64"
-
-        @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())
-
-        meth = router.add_method_handler(div)
-        assert meth.method_signature() == "div(uint64,uint64)uint64"
-
-        @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())
-
-        meth = router.add_method_handler(mod)
-        assert meth.method_signature() == "mod(uint64,uint64)uint64"
-
-        @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()
-            )
+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())
 
-        meth = router.add_method_handler(all_laid_to_args)
-        assert (
-            meth.method_signature()
-            == "all_laid_to_args(uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64)uint64"
-        )
+    meth = router.add_method_handler(add)
+    assert meth.method_signature() == "add(uint64,uint64)uint64"
 
-        @pt.ABIReturnSubroutine
-        def empty_return_subroutine() -> pt.Expr:
-            return pt.Log(pt.Bytes("appear in both approval and clear state"))
+    @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())
 
-        meth = router.add_method_handler(
-            empty_return_subroutine,
-            method_config=pt.MethodConfig(
-                no_op=pt.CallConfig.CALL,
-                opt_in=pt.CallConfig.ALL,
-            ),
-        )
-        assert meth.method_signature() == "empty_return_subroutine()void"
+    meth = router.add_method_handler(sub)
+    assert meth.method_signature() == "sub(uint64,uint64)uint64"
 
-        @pt.ABIReturnSubroutine
-        def log_1(*, output: pt.abi.Uint64) -> pt.Expr:
-            return output.set(1)
+    @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())
 
-        meth = router.add_method_handler(
-            log_1,
-            method_config=pt.MethodConfig(
-                no_op=pt.CallConfig.CALL,
-                opt_in=pt.CallConfig.CALL,
-            ),
-        )
+    meth = router.add_method_handler(mul)
+    assert meth.method_signature() == "mul(uint64,uint64)uint64"
 
-        assert meth.method_signature() == "log_1()uint64"
+    @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 log_creation(*, output: pt.abi.String) -> pt.Expr:
-            return output.set("logging creation")
+    meth = router.add_method_handler(div)
+    assert meth.method_signature() == "div(uint64,uint64)uint64"
 
-        meth = router.add_method_handler(
-            log_creation, method_config=pt.MethodConfig(no_op=pt.CallConfig.CREATE)
-        )
-        assert meth.method_signature() == "log_creation()string"
+    @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())
 
-    on_completion_actions = pt.BareCallActions(
-        opt_in=pt.OnCompleteAction.call_only(pt.Log(pt.Bytes("optin call"))),
-    )
+    meth = router.add_method_handler(mod)
+    assert meth.method_signature() == "mod(uint64,uint64)uint64"
 
-    with pytest.raises(pt.TealInputError) as e:
-        pt.Router("will-error", on_completion_actions).compile_program(
-            version=6, optimize=pt.OptimizeOptions(frame_pointers=True)
+    @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()
         )
 
-    assert "Frame pointers aren't available" in str(e.value)
+    meth = router.add_method_handler(all_laid_to_args)
+    assert (
+        meth.method_signature()
+        == "all_laid_to_args(uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64)uint64"
+    )
 
-    _router_with_oc = pt.Router(
-        "ASimpleQuestionablyRobustContract",
-        on_completion_actions,
-        clear_state=pt.Approve(),
+    @pt.ABIReturnSubroutine
+    def empty_return_subroutine() -> pt.Expr:
+        return pt.Log(pt.Bytes("appear in both approval and clear state"))
+
+    meth = router.add_method_handler(
+        empty_return_subroutine,
+        method_config=pt.MethodConfig(
+            no_op=pt.CallConfig.CALL,
+            opt_in=pt.CallConfig.ALL,
+        ),
     )
-    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 46
-load 46
-int 0
-extract_uint64
-store 44
-load 46
-int 8
-extract_uint64
-store 45
-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 44
-load 45
-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
+    assert meth.method_signature() == "empty_return_subroutine()void"
 
-// add
-add_0:
-store 4
-store 3
-load 3
-load 4
-+
-store 5
-load 5
-retsub
+    @pt.ABIReturnSubroutine
+    def log_1(*, output: pt.abi.Uint64) -> pt.Expr:
+        return output.set(1)
+
+    meth = router.add_method_handler(
+        log_1,
+        method_config=pt.MethodConfig(
+            no_op=pt.CallConfig.CALL,
+            opt_in=pt.CallConfig.CALL,
+        ),
+    )
 
-// sub
-sub_1:
-store 10
-store 9
-load 9
-load 10
--
-store 11
-load 11
-retsub
+    assert meth.method_signature() == "log_1()uint64"
 
-// mul
-mul_2:
-store 16
-store 15
-load 15
-load 16
-*
-store 17
-load 17
-retsub
+    @pt.ABIReturnSubroutine
+    def log_creation(*, output: pt.abi.String) -> pt.Expr:
+        return output.set("logging creation")
 
-// div
-div_3:
-store 22
-store 21
-load 21
-load 22
-/
-store 23
-load 23
-retsub
+    meth = router.add_method_handler(
+        log_creation, method_config=pt.MethodConfig(no_op=pt.CallConfig.CREATE)
+    )
+    assert meth.method_signature() == "log_creation()string"
 
-// 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
+ON_COMPLETION_ACTIONS = pt.BareCallActions(
+    opt_in=pt.OnCompleteAction.call_only(pt.Log(pt.Bytes("optin call"))),
+)
 
-// empty_return_subroutine
-emptyreturnsubroutine_6:
-byte "appear in both approval and clear state"
-log
-retsub
+FIRST_ROUTER = pt.Router(
+    "ASimpleQuestionablyRobustContract",
+    ON_COMPLETION_ACTIONS,
+    clear_state=pt.Approve(),
+)
+add_methods_to_router(FIRST_ROUTER)
 
-// log_1
-log1_7:
-int 1
-store 66
-load 66
-retsub
 
-// log_creation
-logcreation_8:
-byte 0x00106c6f6767696e67206372656174696f6e
-store 68
-load 68
-retsub""".strip()
+def router_app_tester() -> tuple[list[pt.Router], dict[str, str]]:
+    routers = []
+    sources = {}
 
-    assert expected_ap_with_oc == actual_ap_with_oc_compiled
+    def append_router_info(rinfo, programs):
+        assert len(rinfo) == 3
+        assert len(programs) == 2
+        routers.append(rinfo)
+        sources[rinfo[:2]] = programs
 
-    expected_csp_with_oc = """#pragma version 6
-int 1
-return""".strip()
-    assert expected_csp_with_oc == actual_csp_with_oc_compiled
+    # V6 not ready for Frame Pointers:
+    with pytest.raises(pt.TealInputError) as e:
+        pt.Router("will-error", ON_COMPLETION_ACTIONS).compile_program(
+            version=6, optimize=pt.OptimizeOptions(frame_pointers=True)
+        )
+    assert "Frame pointers aren't available" in str(e.value)
+
+    def router_compilarison(router, version, fixture_ap, fixture_csp, key):
+        actual_ap, actual_csp, _ = router.compile_program(version=version)
+
+        with open(ROUTER_FIXTURES / fixture_ap) as f:
+            expected_ap_with_oc = f.read()
+        with open(ROUTER_FIXTURES / fixture_csp) as f:
+            expected_csp_with_oc = f.read()
+        assert expected_ap_with_oc == actual_ap
+        assert expected_csp_with_oc == actual_csp
+        append_router_info(
+            (
+                key,
+                version,
+                router,
+            ),
+            (
+                actual_ap,
+                actual_csp,
+            ),
+        )
 
+    # QUESTIONABLE V6:
+    _router_with_oc = FIRST_ROUTER
+    router_compilarison(
+        _router_with_oc,
+        6,
+        "questionable_approval_v6.teal",
+        "questionable_clear_v6.teal",
+        "questionable",
+    )
+
+    # YACC V6:
     _router_without_oc = pt.Router(
         "yetAnotherContractConstructedFromRouter", clear_state=pt.Approve()
     )
     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 46
-load 46
-int 0
-extract_uint64
-store 44
-load 46
-int 8
-extract_uint64
-store 45
-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 44
-load 45
-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
+    router_compilarison(
+        _router_without_oc, 6, "yacc_approval_v6.teal", "yacc_clear_v6.teal", "yacc"
+    )
 
-// div
-div_3:
-store 22
-store 21
-load 21
-load 22
-/
-store 23
-load 23
-retsub
+    # QUESTIONABLE FP V8:
+    _router_with_oc = pt.Router(
+        "QuestionableRouterGenerateCodeWithFramePointer",
+        ON_COMPLETION_ACTIONS,
+        clear_state=pt.Approve(),
+    )
+    add_methods_to_router(_router_with_oc)
+    router_compilarison(
+        _router_with_oc,
+        8,
+        "questionableFP_approval_v8.teal",
+        "questionableFP_clear_v8.teal",
+        "questionable",
+    )
 
-// mod
-mod_4:
-store 28
-store 27
-load 27
-load 28
-%
-store 29
-load 29
-retsub
+    # YACC FP V8:
+    _router_without_oc = pt.Router(
+        "yetAnotherContractConstructedFromRouterWithFramePointer",
+        clear_state=pt.Approve(),
+    )
+    add_methods_to_router(_router_without_oc)
+    router_compilarison(
+        _router_without_oc,
+        8,
+        "yaccFP_approval_v8.teal",
+        "yaccFP_clear_v8.teal",
+        "yacc",
+    )
 
-// 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
+    non_trivial = pt.Seq(
+        pt.Cond(
+            [pt.Txn.application_args.length() < pt.Int(2), pt.Approve()],
+            [pt.Txn.application_args[0] != pt.Bytes("CLEANUP"), pt.Approve()],
+            [pt.Txn.application_args[1] != pt.Bytes("ABORTING"), pt.Approve()],
+            [pt.Int(1), pt.Reject()],
+        ),
+    )
+    _router_with_nontriv_clear = pt.Router(
+        "QuestionableRouterWithNontrivialClear",
+        bare_calls=ON_COMPLETION_ACTIONS,
+        clear_state=non_trivial,
+    )
+    add_methods_to_router(_router_with_nontriv_clear)
+
+    router_compilarison(
+        _router_with_nontriv_clear,
+        6,
+        "nontriv_clear_approval_v6.teal",
+        "nontriv_clear_clear_v6.teal",
+        "nontriv_clear",
+    )
 
-// empty_return_subroutine
-emptyreturnsubroutine_6:
-byte "appear in both approval and clear state"
-log
-retsub
+    router_compilarison(
+        _router_with_nontriv_clear,
+        8,
+        "nontriv_clear_approval_v8.teal",
+        "nontriv_clear_clear_v8.teal",
+        "nontriv_clear",
+    )
 
-// log_1
-log1_7:
-int 1
-store 66
-load 66
-retsub
+    return routers, sources
 
-// log_creation
-logcreation_8:
-byte 0x00106c6f6767696e67206372656174696f6e
-store 68
-load 68
-retsub""".strip()
-    assert actual_ap_without_oc_compiled == expected_ap_without_oc
 
-    expected_csp_without_oc = """#pragma version 6
-int 1
-return""".strip()
-    assert actual_csp_without_oc_compiled == expected_csp_without_oc
+def test_router_app():
+    # TODO: this test is redundant as router_app_tester is imported and run by
+    # tests/integration/abi_router_test.py's setup
+    router_app_tester()
diff --git a/tests/blackbox.py b/tests/blackbox.py
index 00e85e9ef..0b0aafc9d 100644
--- a/tests/blackbox.py
+++ b/tests/blackbox.py
@@ -1,16 +1,30 @@
-from typing import Any, Callable, Sequence, cast
+from collections import defaultdict
+from copy import deepcopy
+from dataclasses import dataclass, asdict
+import json
+from typing import Any, Callable, Final, Literal, Sequence, Type, cast
 
-import algosdk.abi
-from algosdk.v2client import algod
+import algosdk.abi as sdk_abi
+from algosdk.transaction import OnComplete
+from algosdk import v2client
 
 from graviton import blackbox
+from graviton.abi_strategy import (
+    ABIArgsMod,
+    ABICallStrategy,
+    ABIStrategy,
+    RandomArgLengthCallStrategy,
+)
 from graviton.blackbox import (
     DryRunInspector,
     DryRunExecutor,
     DryRunTransactionParams as TxParams,
 )
-from graviton.models import PyTypes
+from graviton.inspector import DryRunProperty as DRProp
+from graviton.models import ExecutionMode, PyTypes
+from graviton.sim import InputStrategy, Simulation, SimulationResults
 
+from pyteal.compiler.compiler import OptimizeOptions
 from pyteal.ast.subroutine import OutputKwArgInfo
 
 from pyteal import (
@@ -18,14 +32,17 @@
     Arg,
     Btoi,
     Bytes,
+    CallConfig,
     compileTeal,
     Expr,
     Int,
     Itob,
     Len,
     Log,
+    MethodConfig,
     Mode,
     Pop,
+    Router,
     ScratchVar,
     Seq,
     SubroutineFnWrapper,
@@ -35,6 +52,26 @@
 
 from pyteal.ast.subroutine import ABIReturnSubroutine
 
+# ---- Types ---- #
+
+Predicates = dict[DRProp, Any]  # same as in graviton
+
+
+CLEAR_STATE_CALL: Final[str] = "ClearStateCall"
+ClearStateCallType = Literal["ClearStateCall"]
+
+# str for methods, None for bare app calls:
+CallType = str | None
+
+# CallType for app calls, CLEAR_STATE_CALL for clear state calls:
+RouterCallType = CallType | ClearStateCallType  # type: ignore
+
+ABICallConfigs = dict[CallType, MethodConfig]
+
+# key `method_config == None` indicates that the MethodConfig's
+# should be picked up from the router
+CallPredicates = dict[RouterCallType, Predicates]
+
 # ---- Clients ---- #
 
 
@@ -46,9 +83,9 @@ def algod_with_assertion():
 
 def _algod_client(
     algod_address="http://localhost:4001", algod_token="a" * 64
-) -> algod.AlgodClient:
+) -> v2client.algod.AlgodClient:
     """Instantiate and return Algod client object."""
-    return algod.AlgodClient(algod_token, algod_address)
+    return v2client.algod.AlgodClient(algod_token, algod_address)
 
 
 # ---- Decorator ---- #
@@ -205,7 +242,7 @@ def abi_method_signature(self) -> None | str:
 
         return None
 
-    def abi_argument_types(self) -> None | list[algosdk.abi.ABIType]:
+    def abi_argument_types(self) -> None | list[sdk_abi.ABIType]:
         if not (self.input_types or self.is_abi()):
             return None
 
@@ -216,7 +253,7 @@ def handle_arg(arg):
 
         return [handle_arg(arg) for arg in self.input_types]
 
-    def abi_return_type(self) -> None | algosdk.abi.ABIType:
+    def abi_return_type(self) -> None | sdk_abi.ABIType:
         if not self.is_abi():
             return None
 
@@ -440,3 +477,440 @@ def dryrun_one(
         return self.executor(compiler_version).run_one(
             args, txn_params=txn_params, verbose=verbose
         )
+
+
+def as_on_complete(oc_str: str) -> OnComplete:
+    match oc_str:
+        case "no_op":
+            return OnComplete.NoOpOC
+        case "opt_in":
+            return OnComplete.OptInOC
+        case "close_out":
+            return OnComplete.CloseOutOC
+        case "clear_state":
+            return OnComplete.ClearStateOC
+        case "update_application":
+            return OnComplete.UpdateApplicationOC
+        case "delete_application":
+            return OnComplete.DeleteApplicationOC
+
+    raise ValueError(f"unrecognized {oc_str=}")
+
+
+def negate_cc(cc: CallConfig) -> CallConfig:
+    return CallConfig(3 - cc)
+
+
+@dataclass(frozen=True)
+class RouterSimulationResults:
+    stats: dict[str, Any]
+    results: dict
+    approval_simulator: Simulation | None
+    clear_simulator: Simulation | None
+
+
+class RouterSimulation:
+    """
+    Lifecycle of a RouterSimulation
+
+    1. Creation (__init__ method):
+        * router: Router (no version or other options specified)
+        * predicates: CallPredicates - the Router ought satisfy. Type has shape:
+            * method --> <property -> predicate ...> ...
+        * model_router: Router (optional) - in the case when the predicates provided
+            are of type PredicateKind.IdenticalPair, this parameter needs to be
+            provided for comparison.
+            NOTE: model_router may in fact be the same as router, and in this case
+            it is expected that something else such as version or optimization option
+            would differ between model_router and router during the simulation
+        * algod (optional) - if missing, just get one
+
+    Artifacts from Step 1 are stored in self.results: _SimConfig
+
+    2. Simulation (simulate_and_assert method): - using self.results artifacts Step 1, also takes params:
+        * approval_arg_strat_type: Type[ABICallStrategy]
+            - strategy type to use for approval program's arg generation
+        * clear_arg_strat_type_or_inputs: Type[ABICallStrategy] | Iterable[Sequence[PyTypes]] | None
+            - strategy type to use for clear program's arg generation
+        * approval_abi_args_mod: ABIArgsMod (default None)
+            - used to specify any arg mutation
+        # TODO: currently there aren't any clear_abi_args_mod, but we might need these for testing non-trivial clear programs
+        * version: int - for compiling self.router
+        * method_configs: ABICallConfigs - these drive all the test cases
+        * assemble_constants: bool (optional) - for compiling self.router
+        * optimize: OptimizeOptions (optional) - for compiling self.router
+        * num_dryruns: int (default 1)
+            - the number of input runs to generate per method X config combination
+        * txn_params: TxParams (optional)
+            - other TxParams to append in addition to the (is_app_create, OnComplete) information
+        * model_version: int - for compiling self.model_router
+        * model_assemble_constants: bool (optional) - for compiling self.model_router
+        * model_optimize: OptimizeOptions (optional) - for compiling self.model_router
+        * msg: string (optional) - message to report when an assertion is violated
+        * omit_approval_call: bool (default False) - allow purely testing the clear program
+        * omit_clear_call: bool (default False) - allow purely testing the approval program
+        NOTE: one of omit_approval_call or omit_clear_call must remain False
+        * executor_validation (default True) - when False, skip the DryRunExecutor's validation
+        * skip_validation (default False) - when False, skip the Router's validation
+    """
+
+    def __init__(
+        self,
+        router: Router,
+        predicates: CallPredicates,
+        *,
+        model_router: Router | None = None,
+        algod: v2client.algod.AlgodClient | None = None,
+    ):
+        self.router: Router = self._validate_router(router)
+
+        self.predicates: CallPredicates = self._validate_predicates(predicates)
+
+        self.model_router: Router | None = None
+        if model_router:
+            self.model_router = self._validate_router(model_router, kind="Model")
+
+        self.algod: v2client.algod.AlgodClient = algod or algod_with_assertion()
+
+        self.results: dict[
+            str | None, dict[tuple[bool, OnComplete], SimulationResults]
+        ] = {}
+
+    # ---- Validation ---- #
+
+    @classmethod
+    def _validate_router(cls, router: Router, kind: str = "Base") -> Router:
+        assert isinstance(
+            router, Router
+        ), f"Wrong type for {kind} Router: {type(router)}"
+        cls._validate_method_configs(router.method_configs)
+
+        return router
+
+    @classmethod
+    def _validate_method_configs(cls, method_configs):
+        assert isinstance(
+            method_configs, dict
+        ), f"method_configs '{method_configs}' has type {type(method_configs)} but only 'dict' and 'NoneType' are allowed."
+
+        assert (
+            method_configs
+        ), "make sure to give at least one key/value pair in method_configs"
+
+        for call, meth_config in method_configs.items():
+            assert isinstance(  # type: ignore
+                call, CallType
+            ), f"method_configs dict key '{call}' has type {type(call)} but only str and NoneType are allowed."
+            cls._validate_single_method_config(call, meth_config)
+
+    @classmethod
+    def _validate_single_method_config(cls, call, meth_config):
+        assert isinstance(
+            meth_config, MethodConfig
+        ), f"method_configs['{call}'] = has type {type(meth_config)} but only MethodConfig is allowed."
+        assert (
+            not meth_config.is_never()
+        ), f"method_configs['{call}'] specifies NEVER to be called; for driving the test, each configured method should ACTUALLY be tested."
+        assert (
+            meth_config.clear_state is CallConfig.NEVER
+        ), "unexpected value for method_config's clear_state"
+
+    @classmethod
+    def _validate_predicates(cls, predicates):
+        assert isinstance(predicates, dict), (
+            f"Wrong type for predicates: {type(predicates)}. Please provide: "
+            f"dict[str | None, dict[graviton.DryRunProporty, Any]."
+        )
+
+        assert (
+            len(predicates) > 0
+        ), "Please provide at least one method to call and assert against."
+
+        for method, preds in predicates.items():
+            assert isinstance(method, (str, type(None), type(ClearStateCallType))), (
+                f"Predicates method '{method}' has type {type(method)} but only "
+                "'str' and 'NoneType' and Literal['ClearStateCall'] (== ClearStateCall)"
+                " are allowed."
+            )
+            if isinstance(method, type(ClearStateCallType)):
+                assert method == ClearStateCallType, (
+                    f"Predicates method '{method}' is not allowed. "
+                    "Only Literal['ClearStateCall'] (== ClearStateCall) "
+                    "is allowed for a Literal."
+                )
+            assert (
+                preds
+            ), f"Every method must provide at least one predicate for assertion but method '{method}' is missing predicates."
+            assert isinstance(
+                preds, dict
+            ), f"Method '{method}' is expected to have dict[graviton.DryRunProperty, Any] for its predicates value but the type is {type(preds)}."
+            for prop in preds:
+                assert isinstance(
+                    prop, DRProp
+                ), f"Method '{method}' is expected to have dict[graviton.DryRunProperty, Any] for its predicates value but predicates['{method}'] has key '{prop}' of {type(prop)}."
+
+        return predicates
+
+    def _validate_simulation(
+        self,
+        approval_args_strat_type,
+        clear_args_strat_type,
+        approval_abi_args_mod,
+        num_dryruns,
+        txn_params,
+        model_version,
+        method_configs,
+        contract,
+        model_contract,
+        omit_clear_call,
+    ):
+        assert isinstance(approval_args_strat_type, type) and issubclass(
+            approval_args_strat_type, ABIStrategy
+        ), f"approval_args_strat_type should _BE_ a subtype of ABIStrategy but we have {approval_args_strat_type} (its type is {type(approval_args_strat_type)})."
+        if not omit_clear_call:
+            assert isinstance(clear_args_strat_type, type) and issubclass(
+                clear_args_strat_type, ABIStrategy
+            ), f"clear_args_strat_type should _BE_ a subtype of ABIStrategy but we have {clear_args_strat_type} (its type is {type(clear_args_strat_type)})."
+
+        assert isinstance(
+            approval_abi_args_mod, (ABIArgsMod, type(None))
+        ), f"approval_abi_args_mod '{approval_abi_args_mod}' has type {type(approval_abi_args_mod)} but only 'ABIArgsMod' and 'NoneType' are allowed."
+
+        self._validate_method_configs(method_configs)
+
+        self._validate_meths_in_contract(method_configs, contract)
+
+        if model_contract:
+            self._validate_meths_in_contract(
+                method_configs, model_contract, router_prefix="model"
+            )
+
+        assert (
+            isinstance(num_dryruns, int) and num_dryruns >= 1
+        ), f"num_dryruns must be a positive int but is {num_dryruns}."
+
+        assert isinstance(
+            txn_params, (TxParams, type(None))
+        ), f"txn_params must have type DryRunTransactionParams or NoneType but has type {type(txn_params)}."
+
+        if not self.model_router:
+            assert (
+                model_version is None
+            ), f"model_version '{model_version}' was provided which is nonsensical because model_router was never provided for."
+
+    def _validate_meths_in_contract(
+        self, method_configs, contract, router_prefix="base"
+    ):
+        for meth in method_configs:
+            if meth is None:
+                continue
+            try:
+                contract.get_method_by_name(meth)
+            except KeyError:
+                raise ValueError(
+                    f"method_configs has a method '{meth}' missing from {router_prefix}-Router's contract."
+                )
+
+    def simulate_and_assert(
+        self,
+        approval_args_strat_type: Type[ABIStrategy],
+        clear_args_strat_type_or_inputs: Type[ABIStrategy]
+        | list[Sequence[PyTypes]]
+        | None,
+        approval_abi_args_mod: ABIArgsMod | None,
+        version: int,
+        method_configs: ABICallConfigs,
+        *,
+        assemble_constants: bool = False,
+        optimize: OptimizeOptions | None = None,
+        num_dryruns: int = 1,
+        txn_params: TxParams | None = None,
+        model_version: int | None = None,
+        model_assemble_constants: bool = False,
+        model_optimize: OptimizeOptions | None = None,
+        msg: str = "",
+        omit_approval_call: bool = False,
+        omit_clear_call: bool = False,
+        executor_validation: bool = True,
+        skip_validation: bool = False,
+    ) -> RouterSimulationResults:
+        assert not (
+            omit_approval_call and omit_clear_call
+        ), "Aborting and failing as all tests are being omitted"
+
+        # --- setup local functions including reporter and stats. Also declare closure vars --- #
+
+        # for purposes of clarity, declare all the variables for closures before each function:
+        approve_sim: Simulation | None  # required for return RouterResults
+
+        # msg4simulate:
+
+        # msg - cf. parameters
+        approval_strat: ABICallStrategy | None
+        meth_name: str | None  # simulate_approval's closure as well
+        call_cfg: CallConfig | None
+        is_app_create: bool
+        stats: dict[str, int | str] = defaultdict(int)
+
+        def msg4simulate() -> str:
+            return f"""user provide message={msg}
+call_strat={type(approval_strat)}
+{meth_name=}
+{oc=}
+{call_cfg=}
+{is_app_create=}
+{len(self.predicates[meth_name])=}
+{stats["method_combo_count"]=}
+{stats["dryrun_count"]=}
+{stats["assertions_count"]=}
+"""
+
+        # update_stats:
+        # num_dryruns - cf. parameters
+        # stats - cf. above
+        def update_stats(meth, num_preds):
+            stats[str(meth)] += num_dryruns
+            stats["method_combo_count"] += 1
+            stats["dryrun_count"] += num_dryruns
+            stats["assertions_count"] += num_dryruns * num_preds
+
+        # simulate_approval:
+        # txn_params - cf. parameters
+        oc: OnComplete
+        # approval_strat - cf. above
+
+        def simulate_approval(on_create):
+            tp: TxParams = deepcopy(txn_params)
+            tp.update_fields(TxParams.for_app(is_app_create=on_create, on_complete=oc))
+            sim_results = approve_sim.run_and_assert(
+                approval_strat, txn_params=tp, msg=msg4simulate()
+            )
+            assert sim_results.succeeded
+            if meth_name not in self.results:
+                self.results[meth_name] = {}
+            self.results[meth_name][(on_create, oc)] = sim_results
+            update_stats(meth_name, len(self.predicates[meth_name]))
+
+        # --- Compile Programs --- #
+        approval_teal, clear_teal, contract = self.router.compile_program(
+            version=version, assemble_constants=assemble_constants, optimize=optimize
+        )
+
+        model_approval_teal: str | None = None
+        model_clear_teal: str | None = None
+        model_contract: sdk_abi.Contract | None = None
+        if self.model_router:
+            (
+                model_approval_teal,
+                model_clear_teal,
+                model_contract,
+            ) = self.model_router.compile_program(
+                version=cast(int, model_version),
+                assemble_constants=model_assemble_constants,
+                optimize=model_optimize,
+            )
+
+        if not skip_validation:
+            self._validate_simulation(
+                approval_args_strat_type,
+                clear_args_strat_type_or_inputs,
+                approval_abi_args_mod,
+                num_dryruns,
+                txn_params,
+                model_version,
+                method_configs,
+                contract,
+                model_contract,
+                omit_clear_call,
+            )
+
+        if not txn_params:
+            txn_params = TxParams()
+
+        stats["name"] = self.router.name
+
+        # ---- APPROVAL PROGRAM SIMULATION ---- #
+        if not omit_approval_call:
+            approval_strat = ABICallStrategy(
+                json.dumps(contract.dictify()),
+                approval_args_strat_type,
+                num_dryruns=num_dryruns,
+                abi_args_mod=approval_abi_args_mod,
+            )
+            double_check_at_least_one_method = False
+            for meth_name, meth_cfg in method_configs.items():
+                sig = approval_strat.method_signature(meth_name)
+                approve_sim = Simulation(
+                    self.algod,
+                    ExecutionMode.Application,
+                    approval_teal,
+                    self.predicates[meth_name],
+                    abi_method_signature=sig,
+                    identities_teal=model_approval_teal,
+                    validation=executor_validation,
+                )
+
+                for oc_str, call_cfg in asdict(meth_cfg).items():
+                    oc = as_on_complete(oc_str)
+
+                    # weird walrus is_app_create := ... to fill closure of msg4simulate()
+                    if cast(CallConfig, call_cfg) & CallConfig.CALL:
+                        double_check_at_least_one_method = True
+                        simulate_approval(is_app_create := False)
+
+                    if cast(CallConfig, call_cfg) & CallConfig.CREATE:
+                        double_check_at_least_one_method = True
+                        simulate_approval(is_app_create := True)
+            assert double_check_at_least_one_method, "no method was simulated"
+
+        # ---- CLEAR PROGRAM SIMULATION ---- #
+        approval_strat = None
+        call_cfg = None
+        approve_sim = None
+        clear_strat_or_inputs: InputStrategy  # CallStrategy | Iterable[Sequence[PyTypes]]
+        clear_sim: Simulation | None = None
+        if not omit_clear_call:
+            assert clear_args_strat_type_or_inputs  # therefore Type[ABIStrategy] | list[Sequence[PyTypes]]
+            if isinstance(clear_args_strat_type_or_inputs, list):
+                clear_strat_or_inputs = cast(
+                    list[Sequence[PyTypes]], clear_args_strat_type_or_inputs
+                )
+                # for the closure of local update_stats():
+                num_dryruns = len(clear_strat_or_inputs)
+            else:
+                clear_strat_or_inputs = RandomArgLengthCallStrategy(
+                    cast(Type[ABIStrategy], clear_args_strat_type_or_inputs),
+                    max_args=2,
+                    num_dryruns=num_dryruns,
+                    min_args=0,
+                    type_for_args=sdk_abi.ABIType.from_string("byte[8]"),
+                )
+
+            meth_name = CLEAR_STATE_CALL
+            is_app_create = False
+            oc = OnComplete.ClearStateOC
+            clear_sim = Simulation(
+                self.algod,
+                ExecutionMode.Application,
+                clear_teal,
+                self.predicates[meth_name],
+                identities_teal=model_clear_teal,
+                validation=executor_validation,
+            )
+
+            sim_results = clear_sim.run_and_assert(
+                clear_strat_or_inputs, msg=msg4simulate()
+            )
+            assert sim_results.succeeded
+            if meth_name not in self.results:
+                self.results[meth_name] = {}
+            self.results[meth_name][(is_app_create, oc)] = sim_results
+            update_stats(meth_name, len(self.predicates[meth_name]))
+
+        # ---- Summary Statistics ---- #
+        return RouterSimulationResults(
+            stats=stats,
+            results=self.results,
+            approval_simulator=approve_sim,
+            clear_simulator=clear_sim,
+        )
diff --git a/tests/integration/abi_router_test.py b/tests/integration/abi_router_test.py
new file mode 100644
index 000000000..8f53db916
--- /dev/null
+++ b/tests/integration/abi_router_test.py
@@ -0,0 +1,540 @@
+import json
+import re
+from collections import defaultdict
+from dataclasses import asdict
+from pathlib import Path
+
+import pytest
+from graviton.abi_strategy import (
+    ABIArgsMod,
+    RandomABIStrategy,
+    RandomABIStrategyHalfSized,
+)
+from graviton.blackbox import DryRunEncoder
+from graviton.invariant import DryRunProperty as DRProp
+from graviton.invariant import PredicateKind
+
+import pyteal as pt
+from pyteal.compiler.compiler_test import router_app_tester
+from tests.blackbox import (
+    CLEAR_STATE_CALL,
+    ABICallConfigs,
+    Predicates,
+    RouterCallType,
+    RouterSimulation,
+    negate_cc,
+)
+
+NUM_ROUTER_DRYRUNS = 7
+FIXTURES = Path.cwd() / "tests" / "teal" / "router"
+
+
+ALL_ROUTER_CASES, ROUTER_SOURCES = router_app_tester()
+
+ROUTER_CASES, NONTRIV_CLEAR_ROUTER_CASES = ALL_ROUTER_CASES[:-2], ALL_ROUTER_CASES[-2:]
+
+TYPICAL_IAC_OC = pt.MethodConfig(no_op=pt.CallConfig.CALL)
+
+# TEST DRIVERS LEGEND - combines method_configs + predicates
+# * @0 - method: RouterCallType
+#   method == None indicates bare app call
+#   method == CLEAR_STATE_CALL indicates clear state app call
+#
+# * @1 - method_config: MethodConfig
+#   defines how to call the method
+#
+# * @2 - predicates: Predicates ~ dict[DRProp, Any]
+#   these are being asserted after being processed into Invariant's
+#
+# NOTE: the "yacc" routers will simply ignore the case with method `None`
+# as they do not have any bare-app-calls
+QUESTIONABLE_DRIVER: list[tuple[RouterCallType, pt.MethodConfig, Predicates]] = [
+    (
+        "add",
+        TYPICAL_IAC_OC,
+        {DRProp.passed: True, DRProp.lastLog: lambda args: args[1] + args[2]},
+    ),
+    (
+        "sub",
+        TYPICAL_IAC_OC,
+        {
+            DRProp.passed: lambda args: args[1] >= args[2],
+            DRProp.lastLog: (
+                lambda args, actual: True
+                if args[1] < args[2]
+                else actual == args[1] - args[2]
+            ),
+        },
+    ),
+    (
+        "mul",
+        TYPICAL_IAC_OC,
+        {DRProp.passed: True, DRProp.lastLog: lambda args: args[1] * args[2]},
+    ),
+    (
+        "div",
+        TYPICAL_IAC_OC,
+        {DRProp.passed: True, DRProp.lastLog: lambda args: args[1] // args[2]},
+    ),
+    (
+        "mod",
+        TYPICAL_IAC_OC,
+        {DRProp.passed: True, DRProp.lastLog: lambda args: args[1] % args[2]},
+    ),
+    (
+        "all_laid_to_args",
+        TYPICAL_IAC_OC,
+        {DRProp.passed: True, DRProp.lastLog: lambda args: sum(args[1:])},
+    ),
+    (
+        "empty_return_subroutine",
+        pt.MethodConfig(
+            no_op=pt.CallConfig.CALL,
+            opt_in=pt.CallConfig.ALL,
+        ),
+        {
+            DRProp.passed: True,
+            DRProp.lastLog: DryRunEncoder.hex(
+                "appear in both approval and clear state"
+            ),
+        },
+    ),
+    (
+        "log_1",
+        pt.MethodConfig(
+            no_op=pt.CallConfig.CALL,
+            opt_in=pt.CallConfig.CALL,
+            # clear_state=pt.CallConfig.CALL,
+        ),
+        {DRProp.passed: True, DRProp.lastLog: 1},
+    ),
+    (
+        "log_creation",
+        pt.MethodConfig(no_op=pt.CallConfig.CREATE),
+        {DRProp.passed: True, DRProp.lastLog: "logging creation"},
+    ),
+    (
+        None,
+        pt.MethodConfig(
+            opt_in=pt.CallConfig.CALL,
+        ),
+        {
+            DRProp.passed: True,
+            DRProp.lastLog: lambda _, actual: actual
+            in (None, DryRunEncoder.hex("optin call")),
+        },
+    ),
+    (
+        CLEAR_STATE_CALL,
+        pt.MethodConfig(),  # ignored in this case
+        {
+            DRProp.passed: True,
+            DRProp.cost: 2,
+        },
+    ),
+]
+
+YACC_DRIVER = [case for case in QUESTIONABLE_DRIVER if case[0]]
+
+DRIVERS = {
+    "questionable": QUESTIONABLE_DRIVER,
+    "yacc": YACC_DRIVER,
+    "nontriv_clear": QUESTIONABLE_DRIVER,
+}
+
+
+def split_driver2predicates_methconfigs(driver) -> tuple[Predicates, ABICallConfigs]:
+    predicates = {}
+    methconfigs = {}
+    for meth, meth_config, predicate in driver:
+        predicates[meth] = predicate
+        if meth != CLEAR_STATE_CALL:
+            methconfigs[meth] = meth_config
+
+    return predicates, methconfigs
+
+
+def assert_full_method_coverage(router, methconfigs):
+    assert len(methconfigs) == len(rmc := router.method_configs)
+    for meth_sig, meth_config in rmc.items():
+        k = meth_sig
+        if k:
+            k = meth_sig.split("(")[0]
+        assert k in methconfigs, f"{k=} (derived from {meth_sig=} not in methconfigs"
+        assert meth_config == methconfigs[k]
+
+
+@pytest.mark.parametrize("case, version, router", ROUTER_CASES)
+def test_abi_router_positive(case, version, router):
+    """
+    Test the _positive_ version of a case. In other words, ensure that for each
+    router encountered and its driver, iterate through the driver as follows:
+        * consider each method or bare call
+        * consider each (OnComplete, CallConfig) combination
+        * assert that all predicates hold for this call
+    """
+    driver = DRIVERS[case]
+    predicates, methconfigs = split_driver2predicates_methconfigs(driver)
+
+    assert_full_method_coverage(router, methconfigs)
+
+    rsim = RouterSimulation(router, predicates)
+
+    def msg():
+        return f"""test_abi_router_positive()
+{case=}
+{version=}
+{router.name=}"""
+
+    results = rsim.simulate_and_assert(
+        approval_args_strat_type=RandomABIStrategyHalfSized,
+        clear_args_strat_type_or_inputs=RandomABIStrategy,
+        approval_abi_args_mod=None,
+        version=version,
+        method_configs=methconfigs,
+        num_dryruns=NUM_ROUTER_DRYRUNS,
+        msg=msg(),
+    )
+    # won't even get here if there was an error, but some extra sanity checks:
+    assert (sim_results := results.results) and all(
+        sim.succeeded for meth in sim_results.values() for sim in meth.values()
+    )
+
+    print("\nstats:", json.dumps(stats := results.stats, indent=2))
+    assert stats and all(stats.values())
+
+    # TODO: add these assertions after the flakiness of issue #199 is fixed for good
+    # These fail because of differing scratch slot assignments:
+    # pregen_approval, pregen_clear = ROUTER_SOURCES[(case, version)]
+    # assert pregen_clear == results.clear_simulator.simulate_dre.program
+    # assert pregen_approval == results.approval_simulator.simulate_dre.program
+
+
+# cf. https://death.andgravity.com/f-re for an explanation of verbose regex'es
+EXPECTED_ERR_PATTERN = r"""
+    err\ opcode                                 # pyteal generated err's ok
+|   assert\ failed\ pc=                         # pyteal generated assert's ok
+|   invalid\ ApplicationArgs\ index             # failing because an app arg wasn't provided
+|   extraction\ end\ 16\ is\ beyond\ length     # failing because couldn't extract when omitted final arg or jammed in tuple
+"""
+
+
+APPROVAL_NEGATIVE_PREDS = {
+    DRProp.rejected: True,
+    DRProp.error: True,
+    DRProp.errorMessage: lambda _, actual: (
+        bool(re.search(EXPECTED_ERR_PATTERN, actual, re.VERBOSE))
+    ),
+}
+
+CLEAR_NEGATIVE_INVARIANTS_MUST_APPROVE = [
+    inv for m, _, inv in QUESTIONABLE_DRIVER if m == CLEAR_STATE_CALL
+][0]
+
+
+def scenario_assert_stats(scenario, results, totals):
+    part_a = f"""
+SCENARIO: {scenario} 
+"""
+    if results:
+        part_b = json.dumps(stats := results.stats, indent=2)
+        assert stats and all(stats.values())
+        for k, v in stats.items():
+            if isinstance(v, int):
+                totals[k] += v
+    else:
+        part_b = "SKIPPED"
+    print(f"{part_a}stats:", part_b)
+
+
+@pytest.mark.parametrize("case, version, router", ROUTER_CASES)
+def test_abi_router_negative(case, version, router):
+    totals = defaultdict(int)
+
+    contract = router.contract_construct()
+
+    driver = DRIVERS[case]
+    pos_predicates, pos_mconfigs = split_driver2predicates_methconfigs(driver)
+
+    # assert FULL coverage (before modifying the dict):
+    assert_full_method_coverage(router, pos_mconfigs)
+
+    if None not in pos_mconfigs:
+        pos_mconfigs[None] = pt.MethodConfig()
+        pos_predicates[None] = APPROVAL_NEGATIVE_PREDS
+
+    pure_meth_mconfigs = {
+        meth: methconfig
+        for meth, methconfig in pos_mconfigs.items()
+        if meth is not None
+    }
+
+    neg_predicates = {
+        meth: (
+            APPROVAL_NEGATIVE_PREDS
+            if meth != CLEAR_STATE_CALL
+            else CLEAR_NEGATIVE_INVARIANTS_MUST_APPROVE
+        )
+        for meth in pos_predicates
+    }
+
+    rsim = RouterSimulation(router, neg_predicates)
+
+    def msg():
+        return f"""test_abi_router_negative()
+{scenario=}
+{case=}
+{version=}
+{router.name=}"""
+
+    scenario = "I. explore all UNEXPECTED (is_app_create, on_complete) combos"
+
+    # NOTE: We're NOT including clear_state calls for the approval program
+    # as they would never be applied.
+    # Also, we're ONLY including clear_state for the clear program.
+    neg_mconfigs = {
+        meth: pt.MethodConfig(
+            **{k: negate_cc(v) for k, v in asdict(mc).items() if k != "clear_state"}
+        )
+        for meth, mc in pos_mconfigs.items()
+    }
+
+    results = rsim.simulate_and_assert(
+        approval_args_strat_type=RandomABIStrategyHalfSized,
+        clear_args_strat_type_or_inputs=RandomABIStrategy,
+        approval_abi_args_mod=None,
+        version=version,
+        method_configs=neg_mconfigs,
+        num_dryruns=NUM_ROUTER_DRYRUNS,
+        executor_validation=False,
+        msg=msg(),
+    )
+    # won't even get here if there was an error, but some extra sanity checks:
+    assert (sim_results := results.results) and all(
+        sim.succeeded for meth in sim_results.values() for sim in meth.values()
+    )
+    scenario_assert_stats(scenario, results, totals)
+
+    # II. the case of bare-app-calls
+    scenario = "II. adding an argument to a bare app call"
+    if None in pos_mconfigs and not pos_mconfigs[None].is_never():
+        bare_only_methconfigs = {None: pos_mconfigs[None]}
+        results = rsim.simulate_and_assert(
+            approval_args_strat_type=RandomABIStrategyHalfSized,
+            clear_args_strat_type_or_inputs=None,
+            approval_abi_args_mod=ABIArgsMod.parameter_append,
+            version=version,
+            method_configs=bare_only_methconfigs,
+            omit_clear_call=True,
+            num_dryruns=NUM_ROUTER_DRYRUNS,
+            executor_validation=False,
+            msg=msg(),
+        )
+        assert (sim_results := results.results) and all(
+            sim.succeeded for meth in sim_results.values() for sim in meth.values()
+        )
+        scenario_assert_stats(scenario, results, totals)
+    else:
+        scenario_assert_stats(scenario, None, totals)
+
+    # For the rest, we may assume method calls (i.e. non bare-app calls)
+    # III. explore changing method selector arg[0] by edit distance 1
+
+    # NOTE: We don't test the case of adding an argument to method calls
+    # because the SDK's will guard against this case.
+    # However, we should re-think this assumption.
+    # Cf. https://github.com/algorand/go-algorand-internal/issues/2772
+    # Cf. https://github.com/algorand/algorand-sdk-testing/issues/190
+
+    scenario = "III(a). inserting an extra random byte into method selector"
+    results = rsim.simulate_and_assert(
+        approval_args_strat_type=RandomABIStrategyHalfSized,
+        clear_args_strat_type_or_inputs=None,
+        approval_abi_args_mod=ABIArgsMod.selector_byte_insert,
+        version=version,
+        method_configs=pure_meth_mconfigs,
+        omit_clear_call=True,
+        num_dryruns=NUM_ROUTER_DRYRUNS,
+        executor_validation=False,
+        msg=msg(),
+    )
+    assert (sim_results := results.results) and all(
+        sim.succeeded for meth in sim_results.values() for sim in meth.values()
+    )
+    scenario_assert_stats(scenario, results, totals)
+
+    scenario = "III(b). removing a random byte from method selector"
+    results = rsim.simulate_and_assert(
+        approval_args_strat_type=RandomABIStrategyHalfSized,
+        clear_args_strat_type_or_inputs=None,
+        approval_abi_args_mod=ABIArgsMod.selector_byte_delete,
+        version=version,
+        method_configs=pure_meth_mconfigs,
+        omit_clear_call=True,
+        num_dryruns=NUM_ROUTER_DRYRUNS,
+        executor_validation=False,
+        msg=msg(),
+    )
+    assert (sim_results := results.results) and all(
+        sim.succeeded for meth in sim_results.values() for sim in meth.values()
+    )
+    scenario_assert_stats(scenario, results, totals)
+
+    scenario = "III(c). replacing a random byte in method selector"
+    results = rsim.simulate_and_assert(
+        approval_args_strat_type=RandomABIStrategyHalfSized,
+        clear_args_strat_type_or_inputs=None,
+        approval_abi_args_mod=ABIArgsMod.selector_byte_replace,
+        version=version,
+        method_configs=pure_meth_mconfigs,
+        omit_clear_call=True,
+        num_dryruns=NUM_ROUTER_DRYRUNS,
+        executor_validation=False,
+        msg=msg(),
+    )
+    assert (sim_results := results.results) and all(
+        sim.succeeded for meth in sim_results.values() for sim in meth.values()
+    )
+    scenario_assert_stats(scenario, results, totals)
+
+    # IV. explore changing the number of args over the 'good' call_types
+    # NOTE: We don't test the case of adding an argument to method calls
+    # We also remove methods with 0 arguments, as these degenerate to the
+    # already tested bare-app call case.
+    scenario = "IV. removing the final argument"
+    atleast_one_param_mconfigs = {
+        meth: mconfig
+        for meth, mconfig in pure_meth_mconfigs.items()
+        if len(contract.get_method_by_name(meth).args) > 0
+    }
+    results = rsim.simulate_and_assert(
+        approval_args_strat_type=RandomABIStrategyHalfSized,
+        clear_args_strat_type_or_inputs=None,
+        approval_abi_args_mod=ABIArgsMod.parameter_delete,
+        version=version,
+        method_configs=atleast_one_param_mconfigs,
+        omit_clear_call=True,
+        num_dryruns=NUM_ROUTER_DRYRUNS,
+        executor_validation=False,
+        msg=msg(),
+    )
+    assert (sim_results := results.results) and all(
+        sim.succeeded for meth in sim_results.values() for sim in meth.values()
+    )
+    scenario_assert_stats(scenario, results, totals)
+
+    print("SUMMARY STATS: ", json.dumps(totals, indent=2))
+
+
+IDENTITY_PREDICATES = {
+    DRProp.lastLog: PredicateKind.IdenticalPair,
+    DRProp.status: PredicateKind.IdenticalPair,
+    DRProp.error: PredicateKind.IdenticalPair,
+}
+
+
+def test_nontriv_clear():
+    totals = defaultdict(int)
+
+    questionable = [
+        r for name, ver, r in ALL_ROUTER_CASES if name == "questionable" and ver == 6
+    ][0]
+    nontriv_clear = [
+        r for name, ver, r in ALL_ROUTER_CASES if name == "nontriv_clear" and ver == 6
+    ][0]
+
+    _, mconfigs = split_driver2predicates_methconfigs(DRIVERS["nontriv_clear"])
+    predicates = {meth: IDENTITY_PREDICATES for meth in mconfigs}
+
+    rsim_nt_vs_q = RouterSimulation(
+        nontriv_clear, predicates, model_router=questionable
+    )
+
+    # Sanity check the approval programs (_POSITIVE_ cases only):
+    msg = "APPROVAL nontriv@v6 vs. questionable@v8"
+    version = 6
+    results = rsim_nt_vs_q.simulate_and_assert(
+        approval_args_strat_type=RandomABIStrategyHalfSized,
+        clear_args_strat_type_or_inputs=None,
+        approval_abi_args_mod=None,
+        version=version,
+        method_configs=mconfigs,
+        num_dryruns=NUM_ROUTER_DRYRUNS,
+        omit_clear_call=True,
+        model_version=8,
+        msg=msg,
+    )
+    assert (sim_results := results.results) and all(
+        sim.succeeded for meth in sim_results.values() for sim in meth.values()
+    )
+    scenario_assert_stats(msg, results, totals)
+
+    msg = "APPROVAL nontriv@v8 vs. questionable@v8"
+    version = 8
+    results = rsim_nt_vs_q.simulate_and_assert(
+        approval_args_strat_type=RandomABIStrategyHalfSized,
+        clear_args_strat_type_or_inputs=None,
+        approval_abi_args_mod=None,
+        version=version,
+        method_configs=mconfigs,
+        num_dryruns=NUM_ROUTER_DRYRUNS,
+        omit_clear_call=True,
+        model_version=8,
+        msg=msg,
+    )
+    assert (sim_results := results.results) and all(
+        sim.succeeded for meth in sim_results.values() for sim in meth.values()
+    )
+    scenario_assert_stats(msg, results, totals)
+
+    print("PARTIAL SUMMARY STATS: ", json.dumps(totals, indent=2))
+
+    # Finally, a bespoke test for the non-trivial clear program:
+    bespoke = {
+        DRProp.passed: {
+            (): True,
+            (b"random bytes",): True,
+            (b"CLEANUP",): True,
+            (b"CLEANUP", b"random bytes"): True,
+            (b"CLEANUP", b"ABORTING"): False,
+            (b"CLEANUP", b"ABORTING", b"random bytes"): False,
+        }
+    }
+    inputs = list(bespoke[DRProp.passed].keys())
+    clear_preds = {CLEAR_STATE_CALL: bespoke}
+
+    msg = "CLEAR nontriv@v6"
+    version = 6
+    clear_rsim = RouterSimulation(nontriv_clear, clear_preds)
+    results = clear_rsim.simulate_and_assert(
+        approval_args_strat_type=None,
+        clear_args_strat_type_or_inputs=inputs,
+        approval_abi_args_mod=None,
+        version=version,
+        method_configs=None,
+        msg=msg,
+        omit_approval_call=True,
+        skip_validation=True,
+    )
+    assert (sim_results := results.results) and all(
+        sim.succeeded for meth in sim_results.values() for sim in meth.values()
+    )
+    scenario_assert_stats(msg, results, totals)
+
+    msg = "CLEAR nontriv@v8"
+    version = 8
+    clear_rsim = RouterSimulation(nontriv_clear, clear_preds)
+    results = clear_rsim.simulate_and_assert(
+        approval_args_strat_type=None,
+        clear_args_strat_type_or_inputs=inputs,
+        approval_abi_args_mod=None,
+        version=version,
+        method_configs=None,
+        msg=msg,
+        omit_approval_call=True,
+        skip_validation=True,
+    )
+    assert (sim_results := results.results) and all(
+        sim.succeeded for meth in sim_results.values() for sim in meth.values()
+    )
+    scenario_assert_stats(msg, results, totals)
diff --git a/tests/teal/router/nontriv_clear_approval_v6.teal b/tests/teal/router/nontriv_clear_approval_v6.teal
new file mode 100644
index 000000000..51455774a
--- /dev/null
+++ b/tests/teal/router/nontriv_clear_approval_v6.teal
@@ -0,0 +1,460 @@
+#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 46
+load 46
+int 0
+extract_uint64
+store 44
+load 46
+int 8
+extract_uint64
+store 45
+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 44
+load 45
+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 0x00106c6f6767696e67206372656174696f6e
+store 68
+load 68
+retsub
\ No newline at end of file
diff --git a/tests/teal/router/nontriv_clear_approval_v8.teal b/tests/teal/router/nontriv_clear_approval_v8.teal
new file mode 100644
index 000000000..00a013082
--- /dev/null
+++ b/tests/teal/router/nontriv_clear_approval_v8.teal
@@ -0,0 +1,512 @@
+#pragma version 8
+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 logcreationcaster_17
+int 1
+return
+main_l12:
+txn OnCompletion
+int NoOp
+==
+txn ApplicationID
+int 0
+!=
+&&
+txn OnCompletion
+int OptIn
+==
+txn ApplicationID
+int 0
+!=
+&&
+||
+assert
+callsub log1caster_16
+int 1
+return
+main_l13:
+txn OnCompletion
+int NoOp
+==
+txn ApplicationID
+int 0
+!=
+&&
+txn OnCompletion
+int OptIn
+==
+||
+assert
+callsub emptyreturnsubroutinecaster_15
+int 1
+return
+main_l14:
+txn OnCompletion
+int NoOp
+==
+txn ApplicationID
+int 0
+!=
+&&
+assert
+callsub alllaidtoargscaster_14
+int 1
+return
+main_l15:
+txn OnCompletion
+int NoOp
+==
+txn ApplicationID
+int 0
+!=
+&&
+assert
+callsub modcaster_13
+int 1
+return
+main_l16:
+txn OnCompletion
+int NoOp
+==
+txn ApplicationID
+int 0
+!=
+&&
+assert
+callsub divcaster_12
+int 1
+return
+main_l17:
+txn OnCompletion
+int NoOp
+==
+txn ApplicationID
+int 0
+!=
+&&
+assert
+callsub mulcaster_11
+int 1
+return
+main_l18:
+txn OnCompletion
+int NoOp
+==
+txn ApplicationID
+int 0
+!=
+&&
+assert
+callsub subcaster_10
+int 1
+return
+main_l19:
+txn OnCompletion
+int NoOp
+==
+txn ApplicationID
+int 0
+!=
+&&
+assert
+callsub addcaster_9
+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:
+proto 2 1
+int 0
+frame_dig -2
+frame_dig -1
++
+frame_bury 0
+retsub
+
+// sub
+sub_1:
+proto 2 1
+int 0
+frame_dig -2
+frame_dig -1
+-
+frame_bury 0
+retsub
+
+// mul
+mul_2:
+proto 2 1
+int 0
+frame_dig -2
+frame_dig -1
+*
+frame_bury 0
+retsub
+
+// div
+div_3:
+proto 2 1
+int 0
+frame_dig -2
+frame_dig -1
+/
+frame_bury 0
+retsub
+
+// mod
+mod_4:
+proto 2 1
+int 0
+frame_dig -2
+frame_dig -1
+%
+frame_bury 0
+retsub
+
+// all_laid_to_args
+alllaidtoargs_5:
+proto 16 1
+int 0
+frame_dig -16
+frame_dig -15
++
+frame_dig -14
++
+frame_dig -13
++
+frame_dig -12
++
+frame_dig -11
++
+frame_dig -10
++
+frame_dig -9
++
+frame_dig -8
++
+frame_dig -7
++
+frame_dig -6
++
+frame_dig -5
++
+frame_dig -4
++
+frame_dig -3
++
+frame_dig -2
++
+frame_dig -1
++
+frame_bury 0
+retsub
+
+// empty_return_subroutine
+emptyreturnsubroutine_6:
+proto 0 0
+byte "appear in both approval and clear state"
+log
+retsub
+
+// log_1
+log1_7:
+proto 0 1
+int 0
+int 1
+frame_bury 0
+retsub
+
+// log_creation
+logcreation_8:
+proto 0 1
+byte ""
+byte 0x00106c6f6767696e67206372656174696f6e
+frame_bury 0
+retsub
+
+// add_caster
+addcaster_9:
+proto 0 0
+int 0
+dupn 2
+txna ApplicationArgs 1
+btoi
+frame_bury 1
+txna ApplicationArgs 2
+btoi
+frame_bury 2
+frame_dig 1
+frame_dig 2
+callsub add_0
+frame_bury 0
+byte 0x151f7c75
+frame_dig 0
+itob
+concat
+log
+retsub
+
+// sub_caster
+subcaster_10:
+proto 0 0
+int 0
+dupn 2
+txna ApplicationArgs 1
+btoi
+frame_bury 1
+txna ApplicationArgs 2
+btoi
+frame_bury 2
+frame_dig 1
+frame_dig 2
+callsub sub_1
+frame_bury 0
+byte 0x151f7c75
+frame_dig 0
+itob
+concat
+log
+retsub
+
+// mul_caster
+mulcaster_11:
+proto 0 0
+int 0
+dupn 2
+txna ApplicationArgs 1
+btoi
+frame_bury 1
+txna ApplicationArgs 2
+btoi
+frame_bury 2
+frame_dig 1
+frame_dig 2
+callsub mul_2
+frame_bury 0
+byte 0x151f7c75
+frame_dig 0
+itob
+concat
+log
+retsub
+
+// div_caster
+divcaster_12:
+proto 0 0
+int 0
+dupn 2
+txna ApplicationArgs 1
+btoi
+frame_bury 1
+txna ApplicationArgs 2
+btoi
+frame_bury 2
+frame_dig 1
+frame_dig 2
+callsub div_3
+frame_bury 0
+byte 0x151f7c75
+frame_dig 0
+itob
+concat
+log
+retsub
+
+// mod_caster
+modcaster_13:
+proto 0 0
+int 0
+dupn 2
+txna ApplicationArgs 1
+btoi
+frame_bury 1
+txna ApplicationArgs 2
+btoi
+frame_bury 2
+frame_dig 1
+frame_dig 2
+callsub mod_4
+frame_bury 0
+byte 0x151f7c75
+frame_dig 0
+itob
+concat
+log
+retsub
+
+// all_laid_to_args_caster
+alllaidtoargscaster_14:
+proto 0 0
+int 0
+dupn 16
+byte ""
+txna ApplicationArgs 1
+btoi
+frame_bury 1
+txna ApplicationArgs 2
+btoi
+frame_bury 2
+txna ApplicationArgs 3
+btoi
+frame_bury 3
+txna ApplicationArgs 4
+btoi
+frame_bury 4
+txna ApplicationArgs 5
+btoi
+frame_bury 5
+txna ApplicationArgs 6
+btoi
+frame_bury 6
+txna ApplicationArgs 7
+btoi
+frame_bury 7
+txna ApplicationArgs 8
+btoi
+frame_bury 8
+txna ApplicationArgs 9
+btoi
+frame_bury 9
+txna ApplicationArgs 10
+btoi
+frame_bury 10
+txna ApplicationArgs 11
+btoi
+frame_bury 11
+txna ApplicationArgs 12
+btoi
+frame_bury 12
+txna ApplicationArgs 13
+btoi
+frame_bury 13
+txna ApplicationArgs 14
+btoi
+frame_bury 14
+txna ApplicationArgs 15
+frame_bury 17
+frame_dig 17
+int 0
+extract_uint64
+frame_bury 15
+frame_dig 17
+int 8
+extract_uint64
+frame_bury 16
+frame_dig 1
+frame_dig 2
+frame_dig 3
+frame_dig 4
+frame_dig 5
+frame_dig 6
+frame_dig 7
+frame_dig 8
+frame_dig 9
+frame_dig 10
+frame_dig 11
+frame_dig 12
+frame_dig 13
+frame_dig 14
+frame_dig 15
+frame_dig 16
+callsub alllaidtoargs_5
+frame_bury 0
+byte 0x151f7c75
+frame_dig 0
+itob
+concat
+log
+retsub
+
+// empty_return_subroutine_caster
+emptyreturnsubroutinecaster_15:
+proto 0 0
+callsub emptyreturnsubroutine_6
+retsub
+
+// log_1_caster
+log1caster_16:
+proto 0 0
+int 0
+callsub log1_7
+frame_bury 0
+byte 0x151f7c75
+frame_dig 0
+itob
+concat
+log
+retsub
+
+// log_creation_caster
+logcreationcaster_17:
+proto 0 0
+byte ""
+callsub logcreation_8
+frame_bury 0
+byte 0x151f7c75
+frame_dig 0
+concat
+log
+retsub
\ No newline at end of file
diff --git a/tests/teal/router/nontriv_clear_clear_v6.teal b/tests/teal/router/nontriv_clear_clear_v6.teal
new file mode 100644
index 000000000..22f69de3e
--- /dev/null
+++ b/tests/teal/router/nontriv_clear_clear_v6.teal
@@ -0,0 +1,28 @@
+#pragma version 6
+txn NumAppArgs
+int 2
+<
+bnz main_l8
+txna ApplicationArgs 0
+byte "CLEANUP"
+!=
+bnz main_l7
+txna ApplicationArgs 1
+byte "ABORTING"
+!=
+bnz main_l6
+int 1
+bnz main_l5
+err
+main_l5:
+int 0
+return
+main_l6:
+int 1
+return
+main_l7:
+int 1
+return
+main_l8:
+int 1
+return
\ No newline at end of file
diff --git a/tests/teal/router/nontriv_clear_clear_v8.teal b/tests/teal/router/nontriv_clear_clear_v8.teal
new file mode 100644
index 000000000..f9a349e0b
--- /dev/null
+++ b/tests/teal/router/nontriv_clear_clear_v8.teal
@@ -0,0 +1,28 @@
+#pragma version 8
+txn NumAppArgs
+int 2
+<
+bnz main_l8
+txna ApplicationArgs 0
+byte "CLEANUP"
+!=
+bnz main_l7
+txna ApplicationArgs 1
+byte "ABORTING"
+!=
+bnz main_l6
+int 1
+bnz main_l5
+err
+main_l5:
+int 0
+return
+main_l6:
+int 1
+return
+main_l7:
+int 1
+return
+main_l8:
+int 1
+return
\ No newline at end of file
diff --git a/tests/teal/router/questionableFP_approval_v8.teal b/tests/teal/router/questionableFP_approval_v8.teal
new file mode 100644
index 000000000..00a013082
--- /dev/null
+++ b/tests/teal/router/questionableFP_approval_v8.teal
@@ -0,0 +1,512 @@
+#pragma version 8
+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 logcreationcaster_17
+int 1
+return
+main_l12:
+txn OnCompletion
+int NoOp
+==
+txn ApplicationID
+int 0
+!=
+&&
+txn OnCompletion
+int OptIn
+==
+txn ApplicationID
+int 0
+!=
+&&
+||
+assert
+callsub log1caster_16
+int 1
+return
+main_l13:
+txn OnCompletion
+int NoOp
+==
+txn ApplicationID
+int 0
+!=
+&&
+txn OnCompletion
+int OptIn
+==
+||
+assert
+callsub emptyreturnsubroutinecaster_15
+int 1
+return
+main_l14:
+txn OnCompletion
+int NoOp
+==
+txn ApplicationID
+int 0
+!=
+&&
+assert
+callsub alllaidtoargscaster_14
+int 1
+return
+main_l15:
+txn OnCompletion
+int NoOp
+==
+txn ApplicationID
+int 0
+!=
+&&
+assert
+callsub modcaster_13
+int 1
+return
+main_l16:
+txn OnCompletion
+int NoOp
+==
+txn ApplicationID
+int 0
+!=
+&&
+assert
+callsub divcaster_12
+int 1
+return
+main_l17:
+txn OnCompletion
+int NoOp
+==
+txn ApplicationID
+int 0
+!=
+&&
+assert
+callsub mulcaster_11
+int 1
+return
+main_l18:
+txn OnCompletion
+int NoOp
+==
+txn ApplicationID
+int 0
+!=
+&&
+assert
+callsub subcaster_10
+int 1
+return
+main_l19:
+txn OnCompletion
+int NoOp
+==
+txn ApplicationID
+int 0
+!=
+&&
+assert
+callsub addcaster_9
+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:
+proto 2 1
+int 0
+frame_dig -2
+frame_dig -1
++
+frame_bury 0
+retsub
+
+// sub
+sub_1:
+proto 2 1
+int 0
+frame_dig -2
+frame_dig -1
+-
+frame_bury 0
+retsub
+
+// mul
+mul_2:
+proto 2 1
+int 0
+frame_dig -2
+frame_dig -1
+*
+frame_bury 0
+retsub
+
+// div
+div_3:
+proto 2 1
+int 0
+frame_dig -2
+frame_dig -1
+/
+frame_bury 0
+retsub
+
+// mod
+mod_4:
+proto 2 1
+int 0
+frame_dig -2
+frame_dig -1
+%
+frame_bury 0
+retsub
+
+// all_laid_to_args
+alllaidtoargs_5:
+proto 16 1
+int 0
+frame_dig -16
+frame_dig -15
++
+frame_dig -14
++
+frame_dig -13
++
+frame_dig -12
++
+frame_dig -11
++
+frame_dig -10
++
+frame_dig -9
++
+frame_dig -8
++
+frame_dig -7
++
+frame_dig -6
++
+frame_dig -5
++
+frame_dig -4
++
+frame_dig -3
++
+frame_dig -2
++
+frame_dig -1
++
+frame_bury 0
+retsub
+
+// empty_return_subroutine
+emptyreturnsubroutine_6:
+proto 0 0
+byte "appear in both approval and clear state"
+log
+retsub
+
+// log_1
+log1_7:
+proto 0 1
+int 0
+int 1
+frame_bury 0
+retsub
+
+// log_creation
+logcreation_8:
+proto 0 1
+byte ""
+byte 0x00106c6f6767696e67206372656174696f6e
+frame_bury 0
+retsub
+
+// add_caster
+addcaster_9:
+proto 0 0
+int 0
+dupn 2
+txna ApplicationArgs 1
+btoi
+frame_bury 1
+txna ApplicationArgs 2
+btoi
+frame_bury 2
+frame_dig 1
+frame_dig 2
+callsub add_0
+frame_bury 0
+byte 0x151f7c75
+frame_dig 0
+itob
+concat
+log
+retsub
+
+// sub_caster
+subcaster_10:
+proto 0 0
+int 0
+dupn 2
+txna ApplicationArgs 1
+btoi
+frame_bury 1
+txna ApplicationArgs 2
+btoi
+frame_bury 2
+frame_dig 1
+frame_dig 2
+callsub sub_1
+frame_bury 0
+byte 0x151f7c75
+frame_dig 0
+itob
+concat
+log
+retsub
+
+// mul_caster
+mulcaster_11:
+proto 0 0
+int 0
+dupn 2
+txna ApplicationArgs 1
+btoi
+frame_bury 1
+txna ApplicationArgs 2
+btoi
+frame_bury 2
+frame_dig 1
+frame_dig 2
+callsub mul_2
+frame_bury 0
+byte 0x151f7c75
+frame_dig 0
+itob
+concat
+log
+retsub
+
+// div_caster
+divcaster_12:
+proto 0 0
+int 0
+dupn 2
+txna ApplicationArgs 1
+btoi
+frame_bury 1
+txna ApplicationArgs 2
+btoi
+frame_bury 2
+frame_dig 1
+frame_dig 2
+callsub div_3
+frame_bury 0
+byte 0x151f7c75
+frame_dig 0
+itob
+concat
+log
+retsub
+
+// mod_caster
+modcaster_13:
+proto 0 0
+int 0
+dupn 2
+txna ApplicationArgs 1
+btoi
+frame_bury 1
+txna ApplicationArgs 2
+btoi
+frame_bury 2
+frame_dig 1
+frame_dig 2
+callsub mod_4
+frame_bury 0
+byte 0x151f7c75
+frame_dig 0
+itob
+concat
+log
+retsub
+
+// all_laid_to_args_caster
+alllaidtoargscaster_14:
+proto 0 0
+int 0
+dupn 16
+byte ""
+txna ApplicationArgs 1
+btoi
+frame_bury 1
+txna ApplicationArgs 2
+btoi
+frame_bury 2
+txna ApplicationArgs 3
+btoi
+frame_bury 3
+txna ApplicationArgs 4
+btoi
+frame_bury 4
+txna ApplicationArgs 5
+btoi
+frame_bury 5
+txna ApplicationArgs 6
+btoi
+frame_bury 6
+txna ApplicationArgs 7
+btoi
+frame_bury 7
+txna ApplicationArgs 8
+btoi
+frame_bury 8
+txna ApplicationArgs 9
+btoi
+frame_bury 9
+txna ApplicationArgs 10
+btoi
+frame_bury 10
+txna ApplicationArgs 11
+btoi
+frame_bury 11
+txna ApplicationArgs 12
+btoi
+frame_bury 12
+txna ApplicationArgs 13
+btoi
+frame_bury 13
+txna ApplicationArgs 14
+btoi
+frame_bury 14
+txna ApplicationArgs 15
+frame_bury 17
+frame_dig 17
+int 0
+extract_uint64
+frame_bury 15
+frame_dig 17
+int 8
+extract_uint64
+frame_bury 16
+frame_dig 1
+frame_dig 2
+frame_dig 3
+frame_dig 4
+frame_dig 5
+frame_dig 6
+frame_dig 7
+frame_dig 8
+frame_dig 9
+frame_dig 10
+frame_dig 11
+frame_dig 12
+frame_dig 13
+frame_dig 14
+frame_dig 15
+frame_dig 16
+callsub alllaidtoargs_5
+frame_bury 0
+byte 0x151f7c75
+frame_dig 0
+itob
+concat
+log
+retsub
+
+// empty_return_subroutine_caster
+emptyreturnsubroutinecaster_15:
+proto 0 0
+callsub emptyreturnsubroutine_6
+retsub
+
+// log_1_caster
+log1caster_16:
+proto 0 0
+int 0
+callsub log1_7
+frame_bury 0
+byte 0x151f7c75
+frame_dig 0
+itob
+concat
+log
+retsub
+
+// log_creation_caster
+logcreationcaster_17:
+proto 0 0
+byte ""
+callsub logcreation_8
+frame_bury 0
+byte 0x151f7c75
+frame_dig 0
+concat
+log
+retsub
\ No newline at end of file
diff --git a/tests/teal/router/questionableFP_clear_v8.teal b/tests/teal/router/questionableFP_clear_v8.teal
new file mode 100644
index 000000000..31588a8ec
--- /dev/null
+++ b/tests/teal/router/questionableFP_clear_v8.teal
@@ -0,0 +1,3 @@
+#pragma version 8
+int 1
+return
\ No newline at end of file
diff --git a/tests/teal/router/questionable_approval_v6.teal b/tests/teal/router/questionable_approval_v6.teal
new file mode 100644
index 000000000..51455774a
--- /dev/null
+++ b/tests/teal/router/questionable_approval_v6.teal
@@ -0,0 +1,460 @@
+#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 46
+load 46
+int 0
+extract_uint64
+store 44
+load 46
+int 8
+extract_uint64
+store 45
+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 44
+load 45
+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 0x00106c6f6767696e67206372656174696f6e
+store 68
+load 68
+retsub
\ No newline at end of file
diff --git a/tests/teal/router/questionable_clear_v6.teal b/tests/teal/router/questionable_clear_v6.teal
new file mode 100644
index 000000000..c7de51891
--- /dev/null
+++ b/tests/teal/router/questionable_clear_v6.teal
@@ -0,0 +1,3 @@
+#pragma version 6
+int 1
+return
\ No newline at end of file
diff --git a/tests/teal/router/yaccFP_approval_v8.teal b/tests/teal/router/yaccFP_approval_v8.teal
new file mode 100644
index 000000000..5c5bda6d0
--- /dev/null
+++ b/tests/teal/router/yaccFP_approval_v8.teal
@@ -0,0 +1,493 @@
+#pragma version 8
+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 logcreationcaster_17
+int 1
+return
+main_l11:
+txn OnCompletion
+int NoOp
+==
+txn ApplicationID
+int 0
+!=
+&&
+txn OnCompletion
+int OptIn
+==
+txn ApplicationID
+int 0
+!=
+&&
+||
+assert
+callsub log1caster_16
+int 1
+return
+main_l12:
+txn OnCompletion
+int NoOp
+==
+txn ApplicationID
+int 0
+!=
+&&
+txn OnCompletion
+int OptIn
+==
+||
+assert
+callsub emptyreturnsubroutinecaster_15
+int 1
+return
+main_l13:
+txn OnCompletion
+int NoOp
+==
+txn ApplicationID
+int 0
+!=
+&&
+assert
+callsub alllaidtoargscaster_14
+int 1
+return
+main_l14:
+txn OnCompletion
+int NoOp
+==
+txn ApplicationID
+int 0
+!=
+&&
+assert
+callsub modcaster_13
+int 1
+return
+main_l15:
+txn OnCompletion
+int NoOp
+==
+txn ApplicationID
+int 0
+!=
+&&
+assert
+callsub divcaster_12
+int 1
+return
+main_l16:
+txn OnCompletion
+int NoOp
+==
+txn ApplicationID
+int 0
+!=
+&&
+assert
+callsub mulcaster_11
+int 1
+return
+main_l17:
+txn OnCompletion
+int NoOp
+==
+txn ApplicationID
+int 0
+!=
+&&
+assert
+callsub subcaster_10
+int 1
+return
+main_l18:
+txn OnCompletion
+int NoOp
+==
+txn ApplicationID
+int 0
+!=
+&&
+assert
+callsub addcaster_9
+int 1
+return
+
+// add
+add_0:
+proto 2 1
+int 0
+frame_dig -2
+frame_dig -1
++
+frame_bury 0
+retsub
+
+// sub
+sub_1:
+proto 2 1
+int 0
+frame_dig -2
+frame_dig -1
+-
+frame_bury 0
+retsub
+
+// mul
+mul_2:
+proto 2 1
+int 0
+frame_dig -2
+frame_dig -1
+*
+frame_bury 0
+retsub
+
+// div
+div_3:
+proto 2 1
+int 0
+frame_dig -2
+frame_dig -1
+/
+frame_bury 0
+retsub
+
+// mod
+mod_4:
+proto 2 1
+int 0
+frame_dig -2
+frame_dig -1
+%
+frame_bury 0
+retsub
+
+// all_laid_to_args
+alllaidtoargs_5:
+proto 16 1
+int 0
+frame_dig -16
+frame_dig -15
++
+frame_dig -14
++
+frame_dig -13
++
+frame_dig -12
++
+frame_dig -11
++
+frame_dig -10
++
+frame_dig -9
++
+frame_dig -8
++
+frame_dig -7
++
+frame_dig -6
++
+frame_dig -5
++
+frame_dig -4
++
+frame_dig -3
++
+frame_dig -2
++
+frame_dig -1
++
+frame_bury 0
+retsub
+
+// empty_return_subroutine
+emptyreturnsubroutine_6:
+proto 0 0
+byte "appear in both approval and clear state"
+log
+retsub
+
+// log_1
+log1_7:
+proto 0 1
+int 0
+int 1
+frame_bury 0
+retsub
+
+// log_creation
+logcreation_8:
+proto 0 1
+byte ""
+byte 0x00106c6f6767696e67206372656174696f6e
+frame_bury 0
+retsub
+
+// add_caster
+addcaster_9:
+proto 0 0
+int 0
+dupn 2
+txna ApplicationArgs 1
+btoi
+frame_bury 1
+txna ApplicationArgs 2
+btoi
+frame_bury 2
+frame_dig 1
+frame_dig 2
+callsub add_0
+frame_bury 0
+byte 0x151f7c75
+frame_dig 0
+itob
+concat
+log
+retsub
+
+// sub_caster
+subcaster_10:
+proto 0 0
+int 0
+dupn 2
+txna ApplicationArgs 1
+btoi
+frame_bury 1
+txna ApplicationArgs 2
+btoi
+frame_bury 2
+frame_dig 1
+frame_dig 2
+callsub sub_1
+frame_bury 0
+byte 0x151f7c75
+frame_dig 0
+itob
+concat
+log
+retsub
+
+// mul_caster
+mulcaster_11:
+proto 0 0
+int 0
+dupn 2
+txna ApplicationArgs 1
+btoi
+frame_bury 1
+txna ApplicationArgs 2
+btoi
+frame_bury 2
+frame_dig 1
+frame_dig 2
+callsub mul_2
+frame_bury 0
+byte 0x151f7c75
+frame_dig 0
+itob
+concat
+log
+retsub
+
+// div_caster
+divcaster_12:
+proto 0 0
+int 0
+dupn 2
+txna ApplicationArgs 1
+btoi
+frame_bury 1
+txna ApplicationArgs 2
+btoi
+frame_bury 2
+frame_dig 1
+frame_dig 2
+callsub div_3
+frame_bury 0
+byte 0x151f7c75
+frame_dig 0
+itob
+concat
+log
+retsub
+
+// mod_caster
+modcaster_13:
+proto 0 0
+int 0
+dupn 2
+txna ApplicationArgs 1
+btoi
+frame_bury 1
+txna ApplicationArgs 2
+btoi
+frame_bury 2
+frame_dig 1
+frame_dig 2
+callsub mod_4
+frame_bury 0
+byte 0x151f7c75
+frame_dig 0
+itob
+concat
+log
+retsub
+
+// all_laid_to_args_caster
+alllaidtoargscaster_14:
+proto 0 0
+int 0
+dupn 16
+byte ""
+txna ApplicationArgs 1
+btoi
+frame_bury 1
+txna ApplicationArgs 2
+btoi
+frame_bury 2
+txna ApplicationArgs 3
+btoi
+frame_bury 3
+txna ApplicationArgs 4
+btoi
+frame_bury 4
+txna ApplicationArgs 5
+btoi
+frame_bury 5
+txna ApplicationArgs 6
+btoi
+frame_bury 6
+txna ApplicationArgs 7
+btoi
+frame_bury 7
+txna ApplicationArgs 8
+btoi
+frame_bury 8
+txna ApplicationArgs 9
+btoi
+frame_bury 9
+txna ApplicationArgs 10
+btoi
+frame_bury 10
+txna ApplicationArgs 11
+btoi
+frame_bury 11
+txna ApplicationArgs 12
+btoi
+frame_bury 12
+txna ApplicationArgs 13
+btoi
+frame_bury 13
+txna ApplicationArgs 14
+btoi
+frame_bury 14
+txna ApplicationArgs 15
+frame_bury 17
+frame_dig 17
+int 0
+extract_uint64
+frame_bury 15
+frame_dig 17
+int 8
+extract_uint64
+frame_bury 16
+frame_dig 1
+frame_dig 2
+frame_dig 3
+frame_dig 4
+frame_dig 5
+frame_dig 6
+frame_dig 7
+frame_dig 8
+frame_dig 9
+frame_dig 10
+frame_dig 11
+frame_dig 12
+frame_dig 13
+frame_dig 14
+frame_dig 15
+frame_dig 16
+callsub alllaidtoargs_5
+frame_bury 0
+byte 0x151f7c75
+frame_dig 0
+itob
+concat
+log
+retsub
+
+// empty_return_subroutine_caster
+emptyreturnsubroutinecaster_15:
+proto 0 0
+callsub emptyreturnsubroutine_6
+retsub
+
+// log_1_caster
+log1caster_16:
+proto 0 0
+int 0
+callsub log1_7
+frame_bury 0
+byte 0x151f7c75
+frame_dig 0
+itob
+concat
+log
+retsub
+
+// log_creation_caster
+logcreationcaster_17:
+proto 0 0
+byte ""
+callsub logcreation_8
+frame_bury 0
+byte 0x151f7c75
+frame_dig 0
+concat
+log
+retsub
\ No newline at end of file
diff --git a/tests/teal/router/yaccFP_clear_v8.teal b/tests/teal/router/yaccFP_clear_v8.teal
new file mode 100644
index 000000000..31588a8ec
--- /dev/null
+++ b/tests/teal/router/yaccFP_clear_v8.teal
@@ -0,0 +1,3 @@
+#pragma version 8
+int 1
+return
\ No newline at end of file
diff --git a/tests/teal/router/yacc_approval_v6.teal b/tests/teal/router/yacc_approval_v6.teal
new file mode 100644
index 000000000..b36bc397e
--- /dev/null
+++ b/tests/teal/router/yacc_approval_v6.teal
@@ -0,0 +1,441 @@
+#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 46
+load 46
+int 0
+extract_uint64
+store 44
+load 46
+int 8
+extract_uint64
+store 45
+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 44
+load 45
+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 0x00106c6f6767696e67206372656174696f6e
+store 68
+load 68
+retsub
\ No newline at end of file
diff --git a/tests/teal/router/yacc_clear_v6.teal b/tests/teal/router/yacc_clear_v6.teal
new file mode 100644
index 000000000..c7de51891
--- /dev/null
+++ b/tests/teal/router/yacc_clear_v6.teal
@@ -0,0 +1,3 @@
+#pragma version 6
+int 1
+return
\ No newline at end of file
diff --git a/tests/unit/blackbox_test.py b/tests/unit/blackbox_test.py
index 69ee59187..b12b63e5f 100644
--- a/tests/unit/blackbox_test.py
+++ b/tests/unit/blackbox_test.py
@@ -1,12 +1,19 @@
 from itertools import product
 from pathlib import Path
-import pytest
 from typing import Literal, Optional, Tuple
+from unittest.mock import MagicMock
 
-import pyteal as pt
-
-from tests.blackbox import Blackbox, BlackboxWrapper, PyTealDryRunExecutor
+import pytest
+from algosdk.v2client.algod import AlgodClient
+from graviton.inspector import DryRunProperty as DRProp
 
+import pyteal as pt
+from tests.blackbox import (
+    Blackbox,
+    BlackboxWrapper,
+    PyTealDryRunExecutor,
+    RouterSimulation,
+)
 from tests.compile_asserts import assert_teal_as_expected
 
 PATH = Path.cwd() / "tests" / "unit"
@@ -151,6 +158,7 @@ def fn_2mixed_arg_1ret(
 
 
 @pytest.mark.parametrize("subr, mode", product(UNITS, pt.Mode))
+@pytest.mark.serial  # Serial due to scratch generation
 def test_blackbox_pyteal(subr: BlackboxWrapper, mode: pt.Mode):
     is_app = mode == pt.Mode.Application
     name = f"{'app' if is_app else 'lsig'}_{subr.name()}"
@@ -166,6 +174,7 @@ def test_blackbox_pyteal(subr: BlackboxWrapper, mode: pt.Mode):
 
 
 @pytest.mark.parametrize("subr_abi, mode", product(ABI_UNITS, pt.Mode))
+@pytest.mark.serial  # Serial due to scratch generation
 def test_abi_blackbox_pyteal(
     subr_abi: Tuple[BlackboxWrapper, Optional[pt.ast.abi.BaseType]], mode: pt.Mode
 ):
@@ -254,3 +263,91 @@ def test_PyTealBlackboxExecutor_abi_return_type(
         assert PyTealDryRunExecutor(fn, mode).abi_return_type() is not None
     else:
         assert PyTealDryRunExecutor(fn, mode).abi_return_type() is None
+
+
+def successful_RouterSimulation(router, model_router, predicates, algod):
+    rsim = RouterSimulation(
+        router,
+        predicates,
+        model_router=model_router,
+        algod=algod,
+    )
+    assert rsim.router == router
+    assert rsim.predicates == predicates
+    assert rsim.model_router == model_router
+    assert rsim.algod == algod
+
+    return rsim
+
+
+def failing_RouterSimulation(router, model_router, predicates, algod, err_msg):
+    with pytest.raises(AssertionError) as ae:
+        RouterSimulation(
+            router,
+            predicates,
+            model_router=model_router,
+            algod=algod,
+        )
+    assert err_msg == str(ae.value)
+
+
+def test_RouterSimulation_init():
+    router = "not a router"
+    model_router = "not a router either"
+    predicates = "totally unchecked at init"
+    algod = MagicMock(spec=AlgodClient)
+
+    # many paths to misery:
+    err_msg = "Wrong type for Base Router: <class 'str'>"
+    failing_RouterSimulation(router, model_router, predicates, algod, err_msg)
+
+    router = pt.Router("test_router")
+    err_msg = "make sure to give at least one key/value pair in method_configs"
+    failing_RouterSimulation(router, model_router, predicates, algod, err_msg)
+
+    router.add_method_handler(
+        pt.ABIReturnSubroutine(lambda: pt.Int(1)), overriding_name="foo"
+    )
+    err_msg = "Wrong type for predicates: <class 'str'>. Please provide: dict[str | None, dict[graviton.DryRunProporty, Any]."
+    failing_RouterSimulation(router, model_router, predicates, algod, err_msg)
+
+    predicates = {}
+    err_msg = "Please provide at least one method to call and assert against."
+    failing_RouterSimulation(router, model_router, predicates, algod, err_msg)
+
+    predicates = {3: "blah"}
+    err_msg = "Predicates method '3' has type <class 'int'> but only 'str' and 'NoneType' and Literal['ClearStateCall'] (== ClearStateCall) are allowed."
+    failing_RouterSimulation(router, model_router, predicates, algod, err_msg)
+
+    predicates = {Literal[42]: "blah"}
+    err_msg = "Predicates method 'typing.Literal[42]' is not allowed. Only Literal['ClearStateCall'] (== ClearStateCall) is allowed for a Literal."
+    failing_RouterSimulation(router, model_router, predicates, algod, err_msg)
+
+    predicates = {"bar": {DRProp.passed: True}, "foo": {}}
+    err_msg = "Every method must provide at least one predicate for assertion but method 'foo' is missing predicates."
+    failing_RouterSimulation(router, model_router, predicates, algod, err_msg)
+
+    predicates = {"bar": {DRProp.passed: True}, "foo": 42}
+    err_msg = "Method 'foo' is expected to have dict[graviton.DryRunProperty, Any] for its predicates value but the type is <class 'int'>."
+    failing_RouterSimulation(router, model_router, predicates, algod, err_msg)
+
+    predicates = {"bar": {DRProp.passed: True}, "foo": {"blah": 45}}
+    err_msg = "Method 'foo' is expected to have dict[graviton.DryRunProperty, Any] for its predicates value but predicates['foo'] has key 'blah' of <class 'str'>."
+    failing_RouterSimulation(router, model_router, predicates, algod, err_msg)
+
+    predicates = {"bar": {DRProp.passed: True}, "foo": {DRProp.budgetAdded: 45}}
+    err_msg = "Wrong type for Model Router: <class 'str'>"
+    failing_RouterSimulation(router, model_router, predicates, algod, err_msg)
+
+    model_router = pt.Router("test_router")
+    err_msg = "make sure to give at least one key/value pair in method_configs"
+    failing_RouterSimulation(router, model_router, predicates, algod, err_msg)
+
+    # Finally, two happy paths
+    model_router.add_method_handler(
+        pt.ABIReturnSubroutine(lambda: pt.Int(1)), overriding_name="foo"
+    )
+    successful_RouterSimulation(router, model_router, predicates, algod)
+
+    model_router = None
+    successful_RouterSimulation(router, model_router, predicates, algod)
diff --git a/tests/unit/user_guide_test.py b/tests/unit/user_guide_test.py
index da5461fc6..e7b59b3c6 100644
--- a/tests/unit/user_guide_test.py
+++ b/tests/unit/user_guide_test.py
@@ -23,6 +23,7 @@ def user_guide_snippet_dynamic_scratch_var() -> pt.Expr:
 
 
 @pytest.mark.parametrize("snippet", [user_guide_snippet_dynamic_scratch_var])
+@pytest.mark.serial  # Serial due to scratch generation
 def test_user_guide_snippets(snippet):
     assert_new_v_old(snippet, 6, "user_guide")
 
@@ -110,6 +111,7 @@ def abi_sum(to_sum: abi.DynamicArray[abi.Uint64], *, output: abi.Uint64) -> Expr
 
 
 @pytest.mark.parametrize("snippet", USER_GUIDE_SNIPPETS_COPACETIC)
+@pytest.mark.serial  # Serial due to scratch generation
 def test_user_guide_snippets_good(snippet):
     assert_new_v_old(snippet, 6, "user_guide")
 
@@ -123,6 +125,7 @@ def test_user_guide_snippets_good(snippet):
 
 
 @pytest.mark.parametrize("snippet_etype_e", USER_GUIDE_SNIPPETS_ERRORING.items())
+@pytest.mark.serial  # Serial due to scratch generation
 def test_user_guide_snippets_bad(snippet_etype_e):
     snippet, etype_e = snippet_etype_e
     etype, e = etype_e