Repository: algorand/pyteal (language: python)

Issue:
Source Mapper: Poor Mapping of Subroutines
### Upcoming Source Mapper Feature: not mapping subroutines correctly

When source mapping is enabled and annotated teal generated, *Subroutine/AbiSubroutine/router.method* definitions and calls are not mapped correctly (or at least inconsistently). 

For example, @bbroder-algo generated a source map for code residing in [this repo](https://github.com/gr8h/project_rps) (this is the "Rock Paper Scissors" example from a [recent bootcamp](https://medium.com/encode-club/encode-x-algorand-bootcamp-december-2022-summary-d7daf118b8b6)), and obtained this snippet:

<img width="898" alt="image" src="https://user-images.githubusercontent.com/291133/216179188-c4dcdb30-9956-420e-8cab-ccbaff4c7f3c.png">

We can see that there were two subroutines `reset` (starting line 85) and `is_account_empty` (starting line 114). However:
* the beginning of the definitions (`// reset` and `// is_account_empty`) were mapped to `Compilation(...`
* the ending of the definitions (`retsub` on lines 110 and 152 -not shown-) were also mapped to `Compilation(...`

Note that a mapping to `Compilation(...` basically indicates a source-mapper _MISS_. 

### Expected behaviour

The beginning and end of each `@Subroutine` and `@ABIReturnSubroutine` and `@router.method` definition should display
the python signature of the function that was decorated.  So in the case of `reset` above, it should have looked like:

```python
// reset                          //                        19      def reset(account: Expr):
reset_0:                          //
store 0                           //
load 0                            //
byte "opponent"                   //                        9       Bytes('opponent')

...

app_local_put                     //                                App.localPut(account, local_play, Bytes(''))
retsub                            //                        19      def reset(account: Expr):
```

### Actual behaviour

Unfortunately, it looks more like the following currently:

```python
// reset                          //                        249     Compilation(approval_program(), Mode.App...
reset_0:                          //
store 0                           //
load 0                            //
byte "opponent"                   //                        9       Bytes('opponent')

...

app_local_put                     //                                App.localPut(account, local_play, Bytes(''))
retsub                            //                        249     Compilation(approval_program(), Mode.App...
```

DO NOT MERGE: RPS fixture test to assert no regressions
# DO NOT MERGE!!!

Adding a fixture and a unit test that sets the ground truth for how the compiler should behave. I anticipate incorporating this into #650.
Merge-master-fp-router
resolving conflicts with `master` branch
Source Mapper: Poor Mapping of Subroutines
### Upcoming Source Mapper Feature: not mapping subroutines correctly

When source mapping is enabled and annotated teal generated, *Subroutine/AbiSubroutine/router.method* definitions and calls are not mapped correctly (or at least inconsistently). 

For example, @bbroder-algo generated a source map for code residing in [this repo](https://github.com/gr8h/project_rps) (this is the "Rock Paper Scissors" example from a [recent bootcamp](https://medium.com/encode-club/encode-x-algorand-bootcamp-december-2022-summary-d7daf118b8b6)), and obtained this snippet:

<img width="898" alt="image" src="https://user-images.githubusercontent.com/291133/216179188-c4dcdb30-9956-420e-8cab-ccbaff4c7f3c.png">

We can see that there were two subroutines `reset` (starting line 85) and `is_account_empty` (starting line 114). However:
* the beginning of the definitions (`// reset` and `// is_account_empty`) were mapped to `Compilation(...`
* the ending of the definitions (`retsub` on lines 110 and 152 -not shown-) were also mapped to `Compilation(...`

Note that a mapping to `Compilation(...` basically indicates a source-mapper _MISS_. 

### Expected behaviour

The beginning and end of each `@Subroutine` and `@ABIReturnSubroutine` and `@router.method` definition should display
the python signature of the function that was decorated.  So in the case of `reset` above, it should have looked like:

```python
// reset                          //                        19      def reset(account: Expr):
reset_0:                          //
store 0                           //
load 0                            //
byte "opponent"                   //                        9       Bytes('opponent')

...

app_local_put                     //                                App.localPut(account, local_play, Bytes(''))
retsub                            //                        19      def reset(account: Expr):
```

### Actual behaviour

Unfortunately, it looks more like the following currently:

```python
// reset                          //                        249     Compilation(approval_program(), Mode.App...
reset_0:                          //
store 0                           //
load 0                            //
byte "opponent"                   //                        9       Bytes('opponent')

...

app_local_put                     //                                App.localPut(account, local_play, Bytes(''))
retsub                            //                        249     Compilation(approval_program(), Mode.App...
```

Source Mapper: Poor Mapping of Subroutines
### Upcoming Source Mapper Feature: not mapping subroutines correctly

When source mapping is enabled and annotated teal generated, *Subroutine/AbiSubroutine/router.method* definitions and calls are not mapped correctly (or at least inconsistently). 

For example, @bbroder-algo generated a source map for code residing in [this repo](https://github.com/gr8h/project_rps) (this is the "Rock Paper Scissors" example from a [recent bootcamp](https://medium.com/encode-club/encode-x-algorand-bootcamp-december-2022-summary-d7daf118b8b6)), and obtained this snippet:

<img width="898" alt="image" src="https://user-images.githubusercontent.com/291133/216179188-c4dcdb30-9956-420e-8cab-ccbaff4c7f3c.png">

We can see that there were two subroutines `reset` (starting line 85) and `is_account_empty` (starting line 114). However:
* the beginning of the definitions (`// reset` and `// is_account_empty`) were mapped to `Compilation(...`
* the ending of the definitions (`retsub` on lines 110 and 152 -not shown-) were also mapped to `Compilation(...`

Note that a mapping to `Compilation(...` basically indicates a source-mapper _MISS_. 

### Expected behaviour

The beginning and end of each `@Subroutine` and `@ABIReturnSubroutine` and `@router.method` definition should display
the python signature of the function that was decorated.  So in the case of `reset` above, it should have looked like:

```python
// reset                          //                        19      def reset(account: Expr):
reset_0:                          //
store 0                           //
load 0                            //
byte "opponent"                   //                        9       Bytes('opponent')

...

app_local_put                     //                                App.localPut(account, local_play, Bytes(''))
retsub                            //                        19      def reset(account: Expr):
```

### Actual behaviour

Unfortunately, it looks more like the following currently:

```python
// reset                          //                        249     Compilation(approval_program(), Mode.App...
reset_0:                          //
store 0                           //
load 0                            //
byte "opponent"                   //                        9       Bytes('opponent')

...

app_local_put                     //                                App.localPut(account, local_play, Bytes(''))
retsub                            //                        249     Compilation(approval_program(), Mode.App...
```



Discussion / hints:

Fix patch:
diff --git a/.flake8 b/.flake8
index d1f5e3ba7..3b523ff59 100644
--- a/.flake8
+++ b/.flake8
@@ -23,8 +23,14 @@ per-file-ignores =
     examples/signature/recurring_swap.py:           F403, F405
     examples/signature/split.py:                    F403, F405
     pyteal/__init__.py:                             F401, F403
+    pyteal/compiler/flatten.py:                     F821
     pyteal/compiler/optimizer/__init__.py:          F401
     pyteal/ir/ops.py:                               E221
+    pyteal/ir/tealconditionalblock.py:              F821
+    pyteal/ir/tealsimpleblock.py:                   F821
+    pyteal/stack_frame.py:                          F821
+    tests/teal/rps_helpers/program.py:              F401, F403, F405
+    tests/teal/rps.py:                              F403, F405, I252
     tests/unit/module_test.py:                      F401, F403
 
 # from flake8-tidy-imports
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 5783fa850..29cbcc407 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -1,4 +1,4 @@
-name: "Build workflow"
+name: "On Commit Workflow"
 on:
   pull_request:
   push:
@@ -49,7 +49,7 @@ jobs:
     runs-on: ubuntu-20.04
     strategy:
       matrix:
-        python: [ "3.10" ]
+        python: ["3.10"]
     steps:
       - name: Check out code
         uses: actions/checkout@v2
diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml
index 449b552f2..c8f9be018 100644
--- a/.github/workflows/nightly.yml
+++ b/.github/workflows/nightly.yml
@@ -1,7 +1,7 @@
 name: "Nightly Build"
 on:
   schedule:
-    - cron: "10 17 * * *"
+    - cron: "10 1 * * *"
 
 jobs:
   nightly-slow-unit-tests:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 70b053300..fcdf235ac 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,7 +4,8 @@
 
 * Clarify that `Approve` and `Reject` always exit in the documentation. ([#660](https://github.com/algorand/pyteal/pull/660))
 * 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.
+  * 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.
+* Experimental source mapping capability. ([#650](https://github.com/algorand/pyteal/pull/650))
 
 ## Fixed
 
diff --git a/Makefile b/Makefile
index 690804429..dd4c25e5f 100644
--- a/Makefile
+++ b/Makefile
@@ -21,6 +21,8 @@ bdist-wheel:
 
 bundle-docs-clean:
 	rm -rf docs/pyteal.docset
+	rm -rf docs/_build
+	rm -f docs/pyteal.docset.tar.gz
 
 bundle-docs: bundle-docs-clean
 	cd docs && \
@@ -51,14 +53,19 @@ 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
+# Slow test which are fast enough on python 3.11+
+test-unit-slow: 
+	pytest tests/unit/sourcemap_constructs311_test.py -m serial
+
+test-unit-very-slow: 
+	pytest tests/unit/sourcemap_constructs_allpy_test.py -m serial
 
-NUM_PROCS = auto
 test-unit-async:
-	pytest -n $(NUM_PROCS) --durations=10 -sv pyteal tests/unit -m "not serial"
+	pytest -n auto --durations=10 pyteal tests/unit -m "not slow" -m "not serial"
 
-test-unit-sync:
-	pytest --durations=10 -sv pyteal tests/unit -m serial
+# Run tests w/ @pytest.mark.serial under ~/tests/unit each in its own proc:
+test-unit-sync: test-unit-slow
+	find tests/unit -name '*_test.py' | sort | xargs -t -I {} pytest --suppress-no-test-exit-code --dist=no --durations=10 {} -m serial -m "not slow"
 
 test-unit: test-unit-async test-unit-sync
 
@@ -76,13 +83,19 @@ 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
 
-test-integration: integration-run
+test-integ-async:
+	pytest -n auto --durations=10 -sv tests/integration -m "not serial"
+
+# Run tests w/ @pytest.mark.serial under ~/tests/integration each in its own proc:
+test-integ-sync:
+	find tests/integration -name '*_test.py' | sort | xargs -t -I {} pytest --suppress-no-test-exit-code --dist=no --durations=10 {} -m serial
+
+test-integration: test-integ-async test-integ-sync
 
-all-tests: lint-and-test test-integration
+all-sync: test-unit-sync test-integ-sync
+
+all-lint-unit-integ: lint-and-test test-integration
 
 # ---- Github Actions 1-Liners ---- #
 
@@ -94,8 +107,7 @@ check-code-changes:
 	git config --global --add safe.directory /__w/pyteal/pyteal
 	[ -n "$$(git log --since='24 hours ago')" ] && (echo "should_run=true" >> $(GITHUB_ENV)) || (echo "should_run=false" >> $(GITHUB_ENV))
 
-nightly-slow:
-	echo "TODO - this is a stub for a very slow test"
+nightly-slow: test-unit-very-slow
 
 # ---- Local Github Actions Simulation via `act` ---- #
 # assumes act is installed, e.g. via `brew install act`
@@ -111,3 +123,6 @@ local-gh-simulate:
 
 coverage:
 	pytest --cov-report html --cov=pyteal
+
+sourcemap-coverage:
+	pytest --cov-report html --cov=pyteal.stack_frame --cov=pyteal.compiler.sourcemap --cov=pyteal.compiler.compiler --dist=no tests/unit/sourcemap_monkey_unit_test.py -m serial
diff --git a/docs/requirements.txt b/docs/requirements.txt
index 94121dd46..117f5b9d5 100644
--- a/docs/requirements.txt
+++ b/docs/requirements.txt
@@ -1,6 +1,8 @@
 sphinx==5.1.1
 sphinx-rtd-theme==1.0.0
 # dependencies from setup.py
+docstring-parser==0.14.1
+executing==1.2.0
 py-algorand-sdk>=2.0.0,<3.0.0
 semantic-version>=2.9.0,<3.0.0
-docstring-parser==0.14.1
+tabulate>=0.9.0,<0.10.0
diff --git a/examples/application/abi/algobank.py b/examples/application/abi/algobank.py
index 34303ffe3..c8af8859d 100644
--- a/examples/application/abi/algobank.py
+++ b/examples/application/abi/algobank.py
@@ -112,11 +112,11 @@ def withdraw(amount: abi.Uint64, recipient: abi.Account) -> Expr:
     )
 
 
-approval_program, clear_state_program, contract = router.compile_program(
-    version=6, optimize=OptimizeOptions(scratch_slots=True)
-)
-
 if __name__ == "__main__":
+    approval_program, clear_state_program, contract = router.compile_program(
+        version=6, optimize=OptimizeOptions(scratch_slots=True)
+    )
+
     with open("algobank_approval.teal", "w") as f:
         f.write(approval_program)
 
diff --git a/pyteal.ini b/pyteal.ini
new file mode 100644
index 000000000..c525da425
--- /dev/null
+++ b/pyteal.ini
@@ -0,0 +1,5 @@
+[pyteal]
+
+[pyteal-source-mapper]
+enabled = False
+debug = False
diff --git a/pyteal/__init__.py b/pyteal/__init__.py
index bef7d0fdd..47eead933 100644
--- a/pyteal/__init__.py
+++ b/pyteal/__init__.py
@@ -11,11 +11,15 @@
     MIN_PROGRAM_VERSION,
     DEFAULT_PROGRAM_VERSION,
     CompileOptions,
+    Compilation,
     compileTeal,
     OptimizeOptions,
+    PyTealSourceMap,
 )
 from pyteal.types import TealType
 from pyteal.errors import (
+    AlgodClientError,
+    SourceMapDisabledError,
     TealInternalError,
     TealTypeError,
     TealSeqError,
@@ -43,9 +47,13 @@
         "DEFAULT_PROGRAM_VERSION",
         "CompileOptions",
         "pragma",
+        "Compilation",
         "compileTeal",
         "OptimizeOptions",
+        "PyTealSourceMap",
         "TealType",
+        "AlgodClientError",
+        "SourceMapDisabledError",
         "TealInternalError",
         "TealTypeError",
         "TealSeqError",
diff --git a/pyteal/__init__.pyi b/pyteal/__init__.pyi
index d1c6b10a4..b99ed5441 100644
--- a/pyteal/__init__.pyi
+++ b/pyteal/__init__.pyi
@@ -14,11 +14,15 @@ from pyteal.compiler import (
     MIN_PROGRAM_VERSION,
     DEFAULT_PROGRAM_VERSION,
     CompileOptions,
+    Compilation,
     compileTeal,
     OptimizeOptions,
+    PyTealSourceMap,
 )
 from pyteal.types import TealType
 from pyteal.errors import (
+    AlgodClientError,
+    SourceMapDisabledError,
     TealInternalError,
     TealTypeError,
     TealSeqError,
@@ -39,6 +43,7 @@ __all__ = [
     "AccountParamObject",
     "Add",
     "Addr",
+    "AlgodClientError",
     "And",
     "App",
     "AppField",
@@ -91,6 +96,7 @@ __all__ = [
     "BytesZero",
     "CallConfig",
     "Comment",
+    "Compilation",
     "CompileOptions",
     "Concat",
     "Cond",
@@ -175,6 +181,7 @@ __all__ = [
     "Or",
     "Pop",
     "Pragma",
+    "PyTealSourceMap",
     "RETURN_HASH_PREFIX",
     "Reject",
     "Replace",
@@ -194,6 +201,7 @@ __all__ = [
     "Sha512_256",
     "ShiftLeft",
     "ShiftRight",
+    "SourceMapDisabledError",
     "Sqrt",
     "Subroutine",
     "SubroutineCall",
@@ -210,6 +218,7 @@ __all__ = [
     "TealInternalError",
     "TealLabel",
     "TealOp",
+    "TealPragma",
     "TealPragmaError",
     "TealSeqError",
     "TealSimpleBlock",
diff --git a/pyteal/ast/abi/method_return.py b/pyteal/ast/abi/method_return.py
index 24566b5bd..3a36abb95 100644
--- a/pyteal/ast/abi/method_return.py
+++ b/pyteal/ast/abi/method_return.py
@@ -6,8 +6,9 @@
 from pyteal.ast.unaryexpr import Log
 from pyteal.ast.naryexpr import Concat
 from pyteal.ast.bytes import Bytes
-from pyteal.ir import TealBlock, TealSimpleBlock, Op
 from pyteal.config import RETURN_HASH_PREFIX
+from pyteal.ir import TealBlock, TealSimpleBlock, Op
+from pyteal.stack_frame import NatalStackFrame
 
 if TYPE_CHECKING:
     from pyteal.compiler import CompileOptions
@@ -19,15 +20,18 @@ def __init__(self, arg: BaseType):
         if not isinstance(arg, BaseType):
             raise TealInputError(f"Expecting an ABI type argument but get {arg}")
         self.arg = arg
+        self._sframes_container: Expr | None = None
 
     def __teal__(self, options: "CompileOptions") -> Tuple[TealBlock, TealSimpleBlock]:
         if options.version < Op.log.min_version:
             raise TealInputError(
                 f"current version {options.version} is lower than log's min version {Op.log.min_version}"
             )
-        return Log(Concat(Bytes(RETURN_HASH_PREFIX), self.arg.encode())).__teal__(
+        start, end = Log(Concat(Bytes(RETURN_HASH_PREFIX), self.arg.encode())).__teal__(
             options
         )
+        NatalStackFrame.reframe_ops_in_blocks(self._sframes_container or self, start)
+        return start, end
 
     def __str__(self) -> str:
         return f"(MethodReturn {self.arg.type_spec()})"
diff --git a/pyteal/ast/app.py b/pyteal/ast/app.py
index e6f9d0de9..1e01e6abc 100644
--- a/pyteal/ast/app.py
+++ b/pyteal/ast/app.py
@@ -25,12 +25,12 @@
 class OnComplete:
     """An enum of values that :any:`TxnObject.on_completion()` may return."""
 
-    NoOp = EnumInt("NoOp")
-    OptIn = EnumInt("OptIn")
-    CloseOut = EnumInt("CloseOut")
-    ClearState = EnumInt("ClearState")
-    UpdateApplication = EnumInt("UpdateApplication")
-    DeleteApplication = EnumInt("DeleteApplication")
+    NoOp = EnumInt("NoOp")  # T2PT8
+    OptIn = EnumInt("OptIn")  # T2PT8
+    CloseOut = EnumInt("CloseOut")  # T2PT8
+    ClearState = EnumInt("ClearState")  # T2PT8
+    UpdateApplication = EnumInt("UpdateApplication")  # T2PT8
+    DeleteApplication = EnumInt("DeleteApplication")  # T2PT8
 
 
 OnComplete.__module__ = "pyteal"
diff --git a/pyteal/ast/assert_.py b/pyteal/ast/assert_.py
index 9444e9262..5e0074919 100644
--- a/pyteal/ast/assert_.py
+++ b/pyteal/ast/assert_.py
@@ -30,6 +30,7 @@ def __init__(
 
         self.comment = comment
         self.cond = [cond] + list(additional_conds)
+        self._sframes_container: Expr | None = None
 
     def __teal__(self, options: "CompileOptions"):
         if len(self.cond) > 1:
@@ -37,6 +38,7 @@ def __teal__(self, options: "CompileOptions"):
             for cond in self.cond:
                 asrt = Assert(cond, comment=self.comment)
                 asrt.trace = cond.trace
+                asrt._sframes_container = cond
                 asserts.append(asrt)
             return Seq(*asserts).__teal__(options)
 
diff --git a/pyteal/ast/cond.py b/pyteal/ast/cond.py
index 4f4635419..6d5f0aad1 100644
--- a/pyteal/ast/cond.py
+++ b/pyteal/ast/cond.py
@@ -77,12 +77,12 @@ def __init__(self, *argv: List[Expr]):
     def __teal__(self, options: "CompileOptions"):
         start = None
         end = TealSimpleBlock([])
-        prevBranch = None
+        prevBranch: TealConditionalBlock | None = None
         for i, (cond, pred) in enumerate(self.args):
             condStart, condEnd = cond.__teal__(options)
             predStart, predEnd = pred.__teal__(options)
 
-            branchBlock = TealConditionalBlock([])
+            branchBlock = TealConditionalBlock([], root_expr=cond)
             branchBlock.setTrueBlock(predStart)
 
             condEnd.setNextBlock(branchBlock)
@@ -91,6 +91,7 @@ def __teal__(self, options: "CompileOptions"):
                 start = condStart
             else:
                 cast(TealConditionalBlock, prevBranch).setFalseBlock(condStart)
+
             prevBranch = branchBlock
 
         errBlock = TealSimpleBlock([TealOp(self, Op.err)])
diff --git a/pyteal/ast/expr.py b/pyteal/ast/expr.py
index 0f57eebce..761392513 100644
--- a/pyteal/ast/expr.py
+++ b/pyteal/ast/expr.py
@@ -1,8 +1,9 @@
 from abc import ABC, abstractmethod
 from typing import TYPE_CHECKING
 
-from pyteal.types import TealType
 from pyteal.ir import TealBlock, TealSimpleBlock
+from pyteal.stack_frame import NatalStackFrame
+from pyteal.types import TealType
 
 if TYPE_CHECKING:
     from pyteal.compiler import CompileOptions
@@ -15,6 +16,7 @@ def __init__(self):
         import traceback
 
         self.trace = traceback.format_stack()[0:-1]
+        self.stack_frames: NatalStackFrame = NatalStackFrame()
 
     def getDefinitionTrace(self) -> list[str]:
         return self.trace
diff --git a/pyteal/ast/for_.py b/pyteal/ast/for_.py
index a21ff9f7f..29bb5b2ef 100644
--- a/pyteal/ast/for_.py
+++ b/pyteal/ast/for_.py
@@ -55,9 +55,10 @@ def __teal__(self, options: "CompileOptions"):
 
         stepStart, stepEnd = self.step.__teal__(options)
         stepEnd.setNextBlock(condStart)
+        stepEnd._sframes_container = self
         doEnd.setNextBlock(stepStart)
 
-        branchBlock = TealConditionalBlock([])
+        branchBlock = TealConditionalBlock([], root_expr=self)
         branchBlock.setTrueBlock(doStart)
         branchBlock.setFalseBlock(end)
 
diff --git a/pyteal/ast/frame.py b/pyteal/ast/frame.py
index 19dcec12a..c55729934 100644
--- a/pyteal/ast/frame.py
+++ b/pyteal/ast/frame.py
@@ -8,6 +8,7 @@
 from pyteal.types import TealType, require_type
 from pyteal.errors import TealInputError, TealInternalError, verifyProgramVersion
 from pyteal.ir import TealBlock, TealSimpleBlock, TealOp, Op
+from pyteal.stack_frame import NatalStackFrame
 
 if TYPE_CHECKING:
     from pyteal.compiler import CompileOptions
@@ -190,6 +191,7 @@ def __teal__(self, options: "CompileOptions") -> tuple[TealBlock, TealSimpleBloc
         proto_srt, proto_end = TealBlock.FromOp(options, op)
         local_srt, local_end = self.mem_layout.__teal__(options)
         proto_end.setNextBlock(local_srt)
+        NatalStackFrame.reframe_ops_in_blocks(self, proto_srt)
         return proto_srt, local_end
 
     def __str__(self) -> str:
diff --git a/pyteal/ast/if_.py b/pyteal/ast/if_.py
index 18e0df3ad..8bc2a9ed8 100644
--- a/pyteal/ast/if_.py
+++ b/pyteal/ast/if_.py
@@ -1,13 +1,13 @@
 from typing import TYPE_CHECKING
 
+from pyteal.ast.expr import Expr
+from pyteal.ast.seq import _use_seq_if_multiple
 from pyteal.errors import (
     TealCompileError,
     TealInputError,
 )
 from pyteal.types import TealType, require_type
 from pyteal.ir import TealSimpleBlock, TealConditionalBlock
-from pyteal.ast.expr import Expr
-from pyteal.ast.seq import _use_seq_if_multiple
 
 if TYPE_CHECKING:
     from pyteal.compiler import CompileOptions
@@ -49,6 +49,8 @@ def __init__(
         self.thenBranch = thenBranch
         self.elseBranch = elseBranch
 
+        self._label_cond: Expr | None = None
+
     def __teal__(self, options: "CompileOptions"):
         if self.thenBranch is None:
             raise TealCompileError("If expression must have a thenBranch", self)
@@ -57,7 +59,7 @@ def __teal__(self, options: "CompileOptions"):
         thenStart, thenEnd = self.thenBranch.__teal__(options)
         end = TealSimpleBlock([])
 
-        branchBlock = TealConditionalBlock([])
+        branchBlock = TealConditionalBlock([], root_expr=(self._label_cond or self))
         branchBlock.setTrueBlock(thenStart)
 
         condEnd.setNextBlock(branchBlock)
@@ -65,10 +67,12 @@ def __teal__(self, options: "CompileOptions"):
 
         if self.elseBranch is None:
             branchBlock.setFalseBlock(end)
+            branchBlock._sframes_container = self
         else:
             elseStart, elseEnd = self.elseBranch.__teal__(options)
             branchBlock.setFalseBlock(elseStart)
             elseEnd.setNextBlock(end)
+            elseEnd._sframes_container = self
 
         return condStart, end
 
@@ -120,6 +124,7 @@ def ElseIf(self, cond):
 
         if not self.elseBranch:
             self.elseBranch = If(cond)
+            self.elseBranch._label_cond = cond
         else:
             if not isinstance(self.elseBranch, If):
                 raise TealInputError("Else-ElseIf block is malformed")
diff --git a/pyteal/ast/maybe.py b/pyteal/ast/maybe.py
index e22c79816..9d2182a49 100644
--- a/pyteal/ast/maybe.py
+++ b/pyteal/ast/maybe.py
@@ -52,6 +52,7 @@ def local_version_check(options: "CompileOptions"):
             compile_check=(
                 local_version_check if compile_check is None else compile_check
             ),
+            root_expr=self,
         )
 
     def hasValue(self) -> ScratchLoad:
diff --git a/pyteal/ast/multi.py b/pyteal/ast/multi.py
index 4fed823a6..7f8195822 100644
--- a/pyteal/ast/multi.py
+++ b/pyteal/ast/multi.py
@@ -1,4 +1,4 @@
-from typing import Callable, List, Union, TYPE_CHECKING
+from typing import Callable, List, Union, TYPE_CHECKING, cast
 
 from pyteal.types import TealType
 from pyteal.ir import TealOp, Op, TealBlock
@@ -6,6 +6,7 @@
 from pyteal.ast.leafexpr import LeafExpr
 from pyteal.ast.scratch import ScratchSlot
 from pyteal.ast.seq import Seq
+from pyteal.ast.scratch import ScratchStackStore
 
 if TYPE_CHECKING:
     from pyteal.compiler import CompileOptions
@@ -22,6 +23,7 @@ def __init__(
         immediate_args: List[Union[int, str]] | None = None,
         args: List[Expr] | None = None,
         compile_check: Callable[["CompileOptions"], None] = lambda _: None,
+        root_expr: Expr | None = None,
     ):
         """Create a new MultiValue.
 
@@ -39,6 +41,7 @@ def __init__(
         self.compile_check = compile_check
 
         self.output_slots = [ScratchSlot() for _ in self.types]
+        self._sframes_container = root_expr
 
     def outputReducer(self, reducer: Callable[..., Expr]) -> Expr:
         input = [slot.load(self.types[i]) for i, slot in enumerate(self.output_slots)]
@@ -69,7 +72,8 @@ def __teal__(self, options: "CompileOptions"):
         # values. ie the output to stack [A, B, C] should correspond to C->output_slots[2]
         # B->output_slots[1], and A->output_slots[0].
         for slot in reversed(self.output_slots):
-            store = slot.store()
+            store = cast(ScratchStackStore, slot.store())
+            store._sframes_container = self._sframes_container
             storeStart, storeEnd = store.__teal__(options)
             curEnd.setNextBlock(storeStart)
             curEnd = storeEnd
diff --git a/pyteal/ast/router.py b/pyteal/ast/router.py
index 517f8fbf9..00de22752 100644
--- a/pyteal/ast/router.py
+++ b/pyteal/ast/router.py
@@ -1,40 +1,40 @@
 from contextlib import contextmanager
-from dataclasses import dataclass, field, fields, astuple
+from dataclasses import astuple, dataclass, field
 from enum import IntFlag
-from typing import cast, Optional, Callable
+from typing import Callable, Final, Optional, cast
 
 from algosdk import abi as sdk_abi
 from algosdk import encoding
-
-from pyteal.config import METHOD_ARG_NUM_CUTOFF
-from pyteal.errors import (
-    TealInputError,
-    TealInternalError,
-)
-from pyteal.types import TealType
-from pyteal.compiler.compiler import compileTeal, DEFAULT_TEAL_VERSION, OptimizeOptions
-from pyteal.ir.ops import Mode
+from algosdk.v2client.algod import AlgodClient
 
 from pyteal.ast import abi
-from pyteal.ast.subroutine import (
-    OutputKwArgInfo,
-    Subroutine,
-    SubroutineCall,
-    SubroutineFnWrapper,
-    ABIReturnSubroutine,
-)
+from pyteal.ast.app import OnComplete
 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.int import EnumInt, Int
 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
+from pyteal.ast.scratch import ScratchSlot
+from pyteal.ast.seq import Seq
+from pyteal.ast.subroutine import (
+    ABIReturnSubroutine,
+    OutputKwArgInfo,
+    Subroutine,
+    SubroutineCall,
+    SubroutineFnWrapper,
+)
+from pyteal.ast.txn import Txn
+from pyteal.compiler.compiler import DEFAULT_TEAL_VERSION, Compilation, OptimizeOptions
+from pyteal.compiler.sourcemap import PyTealSourceMap, _PyTealSourceMapper
+from pyteal.config import METHOD_ARG_NUM_CUTOFF
+from pyteal.errors import AlgodClientError, TealInputError, TealInternalError
+from pyteal.ir.ops import Mode
+from pyteal.stack_frame import NatalStackFrame, sourcemapping_off_context
+from pyteal.types import TealType
+from pyteal.util import algod_with_assertion
 
 ActionType = Expr | SubroutineFnWrapper | ABIReturnSubroutine
 
@@ -135,13 +135,13 @@ def approval_cond(self) -> Expr | int:
 MethodConfig.__module__ = "pyteal"
 
 
-@dataclass(frozen=True)
+@dataclass
 class OnCompleteAction:
     """
     OnComplete Action, registers bare calls to one single OnCompletion case.
     """
 
-    action: Optional[ActionType] = field(kw_only=True, default=None)
+    action: ActionType | None = field(kw_only=True, default=None)
     call_config: CallConfig = field(kw_only=True, default=CallConfig.NEVER)
 
     def __post_init__(self):
@@ -149,6 +149,7 @@ def __post_init__(self):
             raise TealInputError(
                 f"action {self.action} and call_config {self.call_config!r} contradicts"
             )
+        self.stack_frames: NatalStackFrame = NatalStackFrame()
 
     @staticmethod
     def never() -> "OnCompleteAction":
@@ -173,26 +174,27 @@ def is_empty(self) -> bool:
 OnCompleteAction.__module__ = "pyteal"
 
 
-@dataclass(frozen=True)
 class BareCallActions:
     """
     BareCallActions keep track of bare-call registrations to all OnCompletion cases.
     """
 
-    close_out: OnCompleteAction = field(kw_only=True, default=OnCompleteAction.never())
-    clear_state: OnCompleteAction = field(
-        kw_only=True, default=OnCompleteAction.never()
-    )
-    delete_application: OnCompleteAction = field(
-        kw_only=True, default=OnCompleteAction.never()
-    )
-    no_op: OnCompleteAction = field(kw_only=True, default=OnCompleteAction.never())
-    opt_in: OnCompleteAction = field(kw_only=True, default=OnCompleteAction.never())
-    update_application: OnCompleteAction = field(
-        kw_only=True, default=OnCompleteAction.never()
-    )
-
-    def __post_init__(self):
+    def __init__(
+        self,
+        *,
+        close_out: OnCompleteAction = OnCompleteAction.never(),
+        clear_state: OnCompleteAction = OnCompleteAction.never(),
+        delete_application: OnCompleteAction = OnCompleteAction.never(),
+        no_op: OnCompleteAction = OnCompleteAction.never(),
+        opt_in: OnCompleteAction = OnCompleteAction.never(),
+        update_application: OnCompleteAction = OnCompleteAction.never(),
+    ):
+        self.close_out: Final[OnCompleteAction] = close_out
+        self.clear_state: Final[OnCompleteAction] = clear_state
+        self.delete_application: Final[OnCompleteAction] = delete_application
+        self.no_op: Final[OnCompleteAction] = no_op
+        self.opt_in: Final[OnCompleteAction] = opt_in
+        self.update_application: Final[OnCompleteAction] = update_application
         if not self.clear_state.is_empty():
             raise TealInputError(
                 "Attempt to construct clear state program from bare app call: "
@@ -201,6 +203,8 @@ def __post_init__(self):
                 "https://pyteal.readthedocs.io/en/latest/abi.html#registering-bare-app-calls"
             )
 
+        self.stack_frames: NatalStackFrame = NatalStackFrame()
+
     def asdict(self) -> dict[str, OnCompleteAction]:
         return {
             "clear_state": self.clear_state,
@@ -211,12 +215,11 @@ def asdict(self) -> dict[str, OnCompleteAction]:
             "update_application": self.update_application,
         }
 
+    def aslist(self) -> list[OnCompleteAction]:
+        return list(self.asdict().values())
+
     def is_empty(self) -> bool:
-        for action_field in fields(self):
-            action: OnCompleteAction = getattr(self, action_field.name)
-            if not action.is_empty():
-                return False
-        return True
+        return all([a.is_empty() for a in self.aslist()])
 
     def approval_construction(self) -> Optional[Expr]:
         oc_action_pair: list[tuple[EnumInt, OnCompleteAction]] = [
@@ -228,6 +231,7 @@ def approval_construction(self) -> Optional[Expr]:
         ]
         if all(oca.is_empty() for _, oca in oc_action_pair):
             return None
+
         conditions_n_branches: list[CondNode] = list()
         for oc, oca in oc_action_pair:
             if oca.is_empty():
@@ -327,12 +331,21 @@ def to_cond_node(self, use_frame_pt: bool = False) -> CondNode:
         res = ASTBuilder.wrap_handler(True, self.method, use_frame_pt=use_frame_pt)
         if isinstance(self.condition, Expr):
             res = Seq(Assert(self.condition), res)
+            NatalStackFrame.reframe_asts(self.condition.stack_frames, res)
         return CondNode(walk_in_cond, res)
 
 
 CondWithMethod.__module__ = "pyteal"
 
 
+def _smap_friendly_approve():
+    # TODO: Consider replacing _smap_friendly_approve() with a reframe_asts()
+    a = Approve()
+    a.stack_frames._compiler_gen = True
+    return a
+
+
+@dataclass
 class ASTBuilder:
     def __init__(self):
         self.methods_with_conds: list[CondWithMethod] = []
@@ -346,7 +359,6 @@ 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):
@@ -503,15 +515,12 @@ def wrap_handler(
         use_frame_pt: bool = False,
     ) -> Expr:
         """This is a helper function that handles transaction arguments passing in bare-app-call/abi-method handlers.
-
         If `is_method_call` is True, then it can only be `ABIReturnSubroutine`,
         otherwise:
             - both `ABIReturnSubroutine` and `Subroutine` takes 0 argument on the stack.
             - all three cases have none (or void) type.
-
         On ABI method case, if the ABI method has more than 15 args, this function manages to de-tuple
         the last (16-th) Txn app-arg into a list of ABI method arguments, and pass in to the ABI method.
-
         Args:
             is_method_call: a boolean value that specify if the handler is an ABI method.
             handler: an `ABIReturnSubroutine`, or `SubroutineFnWrapper` (for `Subroutine` case), or an `Expr`.
@@ -531,7 +540,11 @@ def wrap_handler(
                         raise TealInputError(
                             f"{wrap_to_name} handler should be TealType.none not {handler.type_of()}."
                         )
-                    return handler if handler.has_return() else Seq(handler, Approve())
+                    return (
+                        handler
+                        if handler.has_return()
+                        else Seq(handler, _smap_friendly_approve())
+                    )
                 case SubroutineFnWrapper():
                     if handler.type_of() != TealType.none:
                         raise TealInputError(
@@ -542,7 +555,7 @@ def wrap_handler(
                             f"subroutine call should take 0 arg for {wrap_to_name}. "
                             f"this subroutine takes {handler.subroutine.argument_count()}."
                         )
-                    return Seq(handler(), Approve())
+                    return Seq(handler(), _smap_friendly_approve())
                 case ABIReturnSubroutine():
                     if handler.type_of() != "void":
                         raise TealInputError(
@@ -553,7 +566,7 @@ def wrap_handler(
                             f"abi-returning subroutine call should take 0 arg for {wrap_to_name}. "
                             f"this abi-returning subroutine takes {handler.subroutine.argument_count()}."
                         )
-                    return Seq(cast(Expr, handler()), Approve())
+                    return Seq(cast(Expr, handler()), _smap_friendly_approve())
                 case _:
                     raise TealInputError(
                         f"{wrap_to_name} can only accept: none type Expr, or Subroutine/ABIReturnSubroutine with none return and no arg"
@@ -629,7 +642,7 @@ def __de_abify_subroutine_vanilla(handler: ActionType) -> Expr:
             return Seq(
                 *decode_instructions,
                 cast(SubroutineCall, handler(*arg_vals)),
-                Approve(),
+                _smap_friendly_approve(),
             )
         else:
             output_temp: abi.BaseType = cast(
@@ -642,7 +655,7 @@ def __de_abify_subroutine_vanilla(handler: ActionType) -> Expr:
                 *decode_instructions,
                 returned_val.store_into(output_temp),
                 abi.MethodReturn(output_temp),
-                Approve(),
+                _smap_friendly_approve(),
             )
 
     @staticmethod
@@ -742,7 +755,7 @@ def __de_abify_subroutine_frame_pointers(handler: ActionType) -> Expr:
         def declaration():
             return Seq(*decoding_steps, *returning_steps)
 
-        return Seq(subroutine_caster(declaration)(), Approve())
+        return Seq(subroutine_caster(declaration)(), _smap_friendly_approve())
 
     def add_method_to_ast(
         self, method_signature: str, cond: Expr | int, handler: ABIReturnSubroutine
@@ -765,6 +778,94 @@ def program_construction(self, use_frame_pt: bool = False) -> Expr:
 ASTBuilder.__module__ = "pyteal"
 
 
+@dataclass(frozen=True)
+class RouterResults:
+    approval_teal: str
+    clear_teal: str
+    abi_contract: sdk_abi.Contract
+    approval_sourcemap: Optional[PyTealSourceMap] = None
+    clear_sourcemap: Optional[PyTealSourceMap] = None
+
+
+@dataclass
+class _RouterBundle:
+    """Private class that includes a full sourcemapper object"""
+
+    approval_program: Expr
+    clear_program: Expr
+    abi_contract: sdk_abi.Contract
+    approval_teal: str
+    clear_teal: str
+    approval_sourcemapper: Optional[_PyTealSourceMapper] = None
+    clear_sourcemapper: Optional[_PyTealSourceMapper] = None
+    input: Optional["_RouterCompileInput"] = None
+
+    def get_results(self) -> RouterResults:
+        approval_sm: PyTealSourceMap | None = None
+        clear_sm: PyTealSourceMap | None = None
+        if self.approval_sourcemapper:
+            approval_sm = self.approval_sourcemapper.get_sourcemap(self.approval_teal)
+        if self.clear_sourcemapper:
+            clear_sm = self.clear_sourcemapper.get_sourcemap(self.clear_teal)
+
+        return RouterResults(
+            self.approval_teal,
+            self.clear_teal,
+            self.abi_contract,
+            approval_sm,
+            clear_sm,
+        )
+
+
+@dataclass
+class _RouterCompileInput:
+    version: int
+    assemble_constants: bool
+    optimize: Optional[OptimizeOptions] = None
+    with_sourcemaps: bool = False
+    pcs_in_sourcemaps: bool = False
+    approval_filename: Optional[str] = None
+    clear_filename: Optional[str] = None
+    algod_client: Optional[AlgodClient] = None
+    annotate_teal: bool = False
+    annotate_teal_headers: bool = False
+    annotate_teal_concise: bool = True
+
+    def __post_init__(self):
+        # The following params are non-sensical when truthy without sourcemaps.
+        # However, they are not defining anything actionable so are simple ignored
+        # rather than erroring when `with_source == False`:
+        # * pcs_in_sourcemap
+        # * approval_filename
+        # * clear_filename
+        # * algod_client
+
+        # On the other hand, self.annotate_teal indicates a user request which cannot
+        # be provided on when there isn't a sourcemap
+        if self.annotate_teal and not self.with_sourcemaps:
+            raise ValueError(
+                "In order annotate generated teal source, must set with_sourcemap True"
+            )
+
+        if self.pcs_in_sourcemaps:
+            # bootstrap an algod_client if not provided, and in either case, run a healthcheck
+            try:
+                self.algod_client = algod_with_assertion(self.algod_client)
+            except AlgodClientError as ace:
+                raise ResourceWarning(
+                    "algod_with_assertion has failed: are you sure there is an available node such as Sandbox?"
+                ) from ace
+
+    def get_compilation(self, program: Expr) -> Compilation:
+        return Compilation(
+            ast=program,
+            mode=Mode.Application,
+            version=self.version,
+            assemble_constants=self.assemble_constants,
+            optimize=self.optimize,
+        )
+
+
 class Router:
     """
     The Router class helps construct the approval and clear state programs for an ARC-4 compliant
@@ -925,7 +1026,6 @@ def method(
         # - None
         # - CallConfig.Never
         # both cases evaluate to False in if statement.
-
         if clear_state is not None:
             raise TealInputError(
                 "Attempt to register ABI method for clear state program: "
@@ -937,27 +1037,24 @@ def method(
         def wrap(_func) -> ABIReturnSubroutine:
             wrapped_subroutine = ABIReturnSubroutine(_func, overriding_name=name)
             call_configs: MethodConfig
-            if (
-                no_op is None
-                and opt_in is None
-                and close_out is None
-                and update_application is None
-                and delete_application is None
-            ):
+
+            ocs = dict(
+                no_op=no_op,
+                opt_in=opt_in,
+                close_out=close_out,
+                clear_state=clear_state,
+                update_application=update_application,
+                delete_application=delete_application,
+            )
+            if all(oc is None for oc in ocs.values()):
                 call_configs = MethodConfig(no_op=CallConfig.CALL)
             else:
-                _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
+
+                def none_to_never(x: None | CallConfig):
+                    return CallConfig.NEVER if x is None else x
 
                 call_configs = MethodConfig(
-                    no_op=_no_op,
-                    opt_in=_opt_in,
-                    close_out=_close_out,
-                    update_application=_update_app,
-                    delete_application=_delete_app,
+                    **{k: none_to_never(v) for k, v in ocs.items()}
                 )
             return self.add_method_handler(
                 wrapped_subroutine, name, call_configs, description
@@ -1005,10 +1102,12 @@ def _build_program(
             if bare_call_approval:
                 self.approval_ast.bare_calls = [
                     CondNode(
-                        Txn.application_args.length() == Int(0),
-                        cast(Expr, bare_call_approval),
+                        cond := Txn.application_args.length() == Int(0),
+                        act := cast(Expr, bare_call_approval),
                     )
                 ]
+                NatalStackFrame.reframe_asts(bare_call_approval.stack_frames, cond)
+                act.stack_frames = bare_call_approval.stack_frames
 
         optimize = optimize or OptimizeOptions()
         use_frame_pt = optimize.use_frame_pointers(version)
@@ -1035,11 +1134,13 @@ def compile_program(
         optimize: Optional[OptimizeOptions] = None,
     ) -> tuple[str, str, sdk_abi.Contract]:
         """
+        DEPRECATED BUT KEPT FOR BACKWARDS COMPATIBILITY. PREFER Router.compile().
+
         Constructs and compiles 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
         the methods easily.
 
-        This method combines `Router._build_program` and :any:`compileTeal`.
+        This method combines `Router._build_program` and `Compilation.compile`.
 
         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.
@@ -1051,23 +1152,139 @@ 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
         """
+        input = _RouterCompileInput(
+            version=version,
+            assemble_constants=assemble_constants,
+            optimize=optimize,
+        )
+        cpb = self._build_impl(input)
+
+        return cpb.approval_teal, cpb.clear_teal, cpb.abi_contract
+
+    def compile(
+        self,
+        *,
+        version: int = DEFAULT_TEAL_VERSION,
+        assemble_constants: bool = False,
+        optimize: Optional[OptimizeOptions] = None,
+        approval_filename: Optional[str] = None,
+        clear_filename: Optional[str] = None,
+        with_sourcemaps: bool = False,
+        pcs_in_sourcemap: bool = False,
+        algod_client: Optional[AlgodClient] = None,
+        annotate_teal: bool = False,
+        annotate_teal_headers: bool = False,
+        annotate_teal_concise: bool = True,
+    ) -> RouterResults:
+        """
+        Constructs and compiles 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
+        the methods easily.
+
+        This method combines `Router._build_program` and `Compilation.compile`.
+
+        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.
+
+        Returns:
+            A RouterResults containing the following:
+            * approval_teal (str): compiled approval program
+            * clear_teal (str): compiled clear-state program
+            * abi_contract (abi.Contract): a Python SDK Contract object to allow clients to make off-chain calls
+            * approval_sourcemap (PyTealSourceMap | None): source map results for approval program
+            * clear_sourcemap (PyTealSourceMap | None): source map results for clear-state program
+        """
+        approval_filename = approval_filename or f"{self.name}_approval.teal"
+        clear_filename = clear_filename or f"{self.name}_clear.teal"
+
+        input = _RouterCompileInput(
+            version=version,
+            assemble_constants=assemble_constants,
+            optimize=optimize,
+            with_sourcemaps=with_sourcemaps,
+            approval_filename=approval_filename,
+            clear_filename=clear_filename,
+            pcs_in_sourcemaps=pcs_in_sourcemap,
+            algod_client=algod_client,
+            annotate_teal=annotate_teal,
+            annotate_teal_headers=annotate_teal_headers,
+            annotate_teal_concise=annotate_teal_concise,
+        )
+        return self._build_impl(input).get_results()
+
+    def _build_impl(self, input: _RouterCompileInput) -> _RouterBundle:
         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,
+            ap, csp, contract = self._build_program(
+                version=input.version, optimize=input.optimize
+            )
+
+            abundle = input.get_compilation(ap)._compile_impl(
+                with_sourcemap=input.with_sourcemaps,
+                teal_filename=input.approval_filename,
+                pcs_in_sourcemap=input.pcs_in_sourcemaps,
+                algod_client=input.algod_client,
+                annotate_teal=input.annotate_teal,
+                annotate_teal_headers=input.annotate_teal_headers,
+                annotate_teal_concise=input.annotate_teal_concise,
             )
-            csp_compiled = compileTeal(
-                csp,
-                Mode.Application,
-                version=version,
-                assembleConstants=assemble_constants,
-                optimize=optimize,
+
+            # TODO: ideally, the clear-state compilation ought to be in it's own
+            # _cleaning_context to allow for fresh slot numbering. However,
+            # the side effects of separating is not yet obvious and
+            # clear state programs generally aren't so complex so this isn't
+            # of high urgency
+            csbundle = input.get_compilation(csp)._compile_impl(
+                with_sourcemap=input.with_sourcemaps,
+                teal_filename=input.clear_filename,
+                pcs_in_sourcemap=input.pcs_in_sourcemaps,
+                algod_client=input.algod_client,
+                annotate_teal=input.annotate_teal,
+                annotate_teal_headers=input.annotate_teal_headers,
+                annotate_teal_concise=input.annotate_teal_concise,
             )
-        return ap_compiled, csp_compiled, contract
+
+        if input.with_sourcemaps:
+            # rerun the build and compilation without the source mapper
+            # and verify that the teal programs are the same
+            with self._cleaning_context(), sourcemapping_off_context():
+                assert NatalStackFrame.sourcemapping_is_off()
+
+                ap_wo, csp_wo, _ = self._build_program(
+                    version=input.version, optimize=input.optimize
+                )
+                input_wo = _RouterCompileInput(
+                    version=input.version,
+                    assemble_constants=input.assemble_constants,
+                    optimize=input.optimize,
+                    with_sourcemaps=False,
+                )
+                abundle_wo = input_wo.get_compilation(ap_wo)._compile_impl(
+                    with_sourcemap=False
+                )
+                csbundle_wo = input_wo.get_compilation(csp_wo)._compile_impl(
+                    with_sourcemap=False
+                )
+                _PyTealSourceMapper._validate_teal_identical(
+                    abundle.teal,
+                    abundle_wo.teal,
+                    msg="FATAL ERROR. Approval Program without sourcemaps (LEFT) differs from Approval Program with (RIGHT)",
+                )
+                _PyTealSourceMapper._validate_teal_identical(
+                    csbundle.teal,
+                    csbundle_wo.teal,
+                    msg="FATAL ERROR. Clear Program without sourcemaps (LEFT) differs from Clear Program with (RIGHT)",
+                )
+
+        return _RouterBundle(
+            approval_program=ap,
+            clear_program=csp,
+            abi_contract=contract,
+            approval_teal=abundle.teal,
+            clear_teal=csbundle.teal,
+            approval_sourcemapper=abundle.sourcemapper,
+            clear_sourcemapper=csbundle.sourcemapper,
+            input=input,
+        )
 
 
 Router.__module__ = "pyteal"
diff --git a/pyteal/ast/scratch.py b/pyteal/ast/scratch.py
index 6b2cf4702..c3a675c0c 100644
--- a/pyteal/ast/scratch.py
+++ b/pyteal/ast/scratch.py
@@ -249,6 +249,7 @@ def __init__(self, slot: ScratchSlot):
         """
         super().__init__()
         self.slot = slot
+        self._sframes_container: Expr | None = None
 
     def __str__(self):
         return "(StackStore {})".format(self.slot)
@@ -257,6 +258,7 @@ def __teal__(self, options: "CompileOptions"):
         from pyteal.ir import TealOp, Op, TealBlock
 
         op = TealOp(self, Op.store, self.slot)
+        op._sframes_container = self._sframes_container
         return TealBlock.FromOp(options, op)
 
     def type_of(self):
diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py
index fa1cb6823..90f1aff1f 100644
--- a/pyteal/ast/subroutine.py
+++ b/pyteal/ast/subroutine.py
@@ -6,7 +6,7 @@
 from docstring_parser import parse as parse_docstring
 from inspect import isclass, Parameter, signature, get_annotations
 from types import MappingProxyType, NoneType
-from typing import Any, Callable, Final, Optional, TYPE_CHECKING, cast, ClassVar
+from typing import Any, Callable, Final, TYPE_CHECKING, cast, ClassVar
 
 from pyteal.ast import abi
 from pyteal.ast.expr import Expr
@@ -15,6 +15,7 @@
 from pyteal.ast.frame import FrameBury, Proto, FrameVar, ProtoStackLayout
 from pyteal.errors import TealInputError, TealInternalError, verifyProgramVersion
 from pyteal.ir import TealOp, Op, TealBlock
+from pyteal.stack_frame import NatalStackFrame
 from pyteal.types import TealType
 
 if TYPE_CHECKING:
@@ -24,7 +25,7 @@
 class _SubroutineDeclByOption:
     def __init__(self, subroutine_def: "SubroutineDefinition") -> None:
         self.subroutine: SubroutineDefinition = subroutine_def
-        self.option_map: dict[bool, Optional[SubroutineDeclaration]] = {
+        self.option_map: dict[bool, SubroutineDeclaration | None] = {
             True: None,
             False: None,
         }
@@ -32,8 +33,8 @@ def __init__(self, subroutine_def: "SubroutineDefinition") -> None:
             True: SubroutineEval.fp_evaluator(),
             False: SubroutineEval.normal_evaluator(),
         }
-        self.has_return: Optional[bool] = None
-        self.type_of: Optional[TealType] = None
+        self.has_return: bool | None = None
+        self.type_of: TealType | None = None
 
     def get_declaration(self) -> "SubroutineDeclaration":
         warnings.warn(
@@ -101,7 +102,7 @@ def __init__(
         self,
         implementation: Callable[..., Expr],
         return_type: TealType,
-        name_str: Optional[str] = None,
+        name_str: str | None = None,
         has_abi_output: bool = False,
     ) -> None:
         """
@@ -117,7 +118,7 @@ def __init__(
         SubroutineDefinition.nextSubroutineId += 1
 
         self.return_type = return_type
-        self.declaration: Optional["SubroutineDeclaration"] = None
+        self.declaration: "SubroutineDeclaration | None" = None
         self.declarations: _SubroutineDeclByOption = _SubroutineDeclByOption(self)
 
         self.implementation: Callable = implementation
@@ -141,6 +142,8 @@ def __init__(
 
         self.__name: str = name_str if name_str else self.implementation.__name__
 
+        self.stack_frames: NatalStackFrame = NatalStackFrame()
+
     def _validate(
         self, input_types: list[TealType | None] | None = None
     ) -> tuple[
@@ -396,12 +399,13 @@ def __init__(
         self,
         subroutine: SubroutineDefinition,
         body: Expr,
-        deferred_expr: Optional[Expr] = None,
+        deferred_expr: Expr | None = None,
     ) -> None:
         super().__init__()
         self.subroutine = subroutine
         self.body = body
         self.deferred_expr = deferred_expr
+        self._sframes_container: SubroutineDefinition = subroutine
 
     def __teal__(self, options: "CompileOptions"):
         return self.body.__teal__(options)
@@ -425,7 +429,7 @@ class OutputKwArgInfo:
     abi_type: abi.TypeSpec
 
     @staticmethod
-    def from_dict(kwarg_info: dict[str, abi.TypeSpec]) -> Optional["OutputKwArgInfo"]:
+    def from_dict(kwarg_info: dict[str, abi.TypeSpec]) -> "OutputKwArgInfo | None":
         match list(kwarg_info.keys()):
             case []:
                 return None
@@ -496,17 +500,22 @@ def __teal__(self, options: "CompileOptions"):
         )
 
         def handle_arg(arg: Expr | ScratchVar | abi.BaseType) -> Expr:
+            ret_expr: Expr
             if isinstance(arg, ScratchVar):
-                return arg.index()
+                ret_expr = arg.index()
+                ret_expr.stack_frames = self.subroutine.stack_frames
             elif isinstance(arg, Expr):
-                return arg
+                ret_expr = arg
             elif isinstance(arg, abi.BaseType):
-                return arg._stored_value.load()
+                ret_expr = arg._stored_value.load()
+                ret_expr.stack_frames = self.subroutine.stack_frames
             else:
                 raise TealInputError(
                     f"cannot handle current arg: {arg} to put it on stack"
                 )
 
+            return ret_expr
+
         op = TealOp(self, Op.callsub, self.subroutine)
         return TealBlock.FromOp(options, op, *[handle_arg(x) for x in self.args])
 
@@ -536,7 +545,7 @@ def __init__(
         self,
         fn_implementation: Callable[..., Expr],
         return_type: TealType,
-        name: Optional[str] = None,
+        name: str | None = None,
     ) -> None:
         self.subroutine = SubroutineDefinition(
             fn_implementation,
@@ -610,9 +619,9 @@ def __init__(
         fn_implementation: Callable[..., Expr],
         /,
         *,
-        overriding_name: Optional[str] = None,
+        overriding_name: str | None = None,
     ) -> None:
-        self.output_kwarg_info: Optional[OutputKwArgInfo] = self._get_output_kwarg_info(
+        self.output_kwarg_info: OutputKwArgInfo | None = self._get_output_kwarg_info(
             fn_implementation
         )
         self.subroutine = SubroutineDefinition(
@@ -632,7 +641,7 @@ def wrapper(fn_impl: Callable[..., Expr]) -> ABIReturnSubroutine:
     @classmethod
     def _get_output_kwarg_info(
         cls, fn_implementation: Callable[..., Expr]
-    ) -> Optional[OutputKwArgInfo]:
+    ) -> OutputKwArgInfo | None:
         if not callable(fn_implementation):
             raise TealInputError("Input to ABIReturnSubroutine is not callable")
         sig = signature(fn_implementation)
@@ -809,7 +818,7 @@ def mySubroutine(a: Expr, b: Expr) -> Expr:
             ])
     """
 
-    def __init__(self, return_type: TealType, name: Optional[str] = None) -> None:
+    def __init__(self, return_type: TealType, name: str | None = None) -> None:
         """Define a new subroutine with the given return type.
 
         Args:
@@ -907,11 +916,11 @@ class SubroutineEval:
     """
 
     var_n_loaded_method: Callable[
-        [SubroutineDefinition, str, Optional[Proto]],
-        tuple[Optional[ScratchVar], ScratchVar | abi.BaseType | Expr],
+        [SubroutineDefinition, str, Proto | None],
+        tuple[ScratchVar | None, ScratchVar | abi.BaseType | Expr],
     ]
     use_frame_pt: bool = False
-    _current_proto: ClassVar[Optional[Proto]] = None
+    _current_proto: ClassVar[Proto | None] = None
 
     @staticmethod
     def _new_abi_instance_from_storage(
@@ -934,7 +943,7 @@ def _new_abi_instance_from_storage(
     def var_n_loaded_scratch(
         subroutine: SubroutineDefinition,
         param: str,
-        _: Optional[Proto] = None,
+        _: Proto | None = None,
     ) -> tuple[ScratchVar, ScratchVar | abi.BaseType | Expr]:
         loaded_var: ScratchVar | abi.BaseType | Expr
         argument_var: ScratchVar
@@ -957,15 +966,15 @@ def var_n_loaded_scratch(
     def var_n_loaded_fp(
         subroutine: SubroutineDefinition,
         param: str,
-        proto: Optional[Proto],
-    ) -> tuple[Optional[ScratchVar], ScratchVar | abi.BaseType | Expr]:
+        proto: Proto | None,
+    ) -> tuple[ScratchVar | None, ScratchVar | abi.BaseType | Expr]:
         if not proto:
             raise TealInternalError(
                 "proto should be available for frame pointer based subroutine."
             )
 
         loaded_var: ScratchVar | abi.BaseType | Expr
-        argument_var: Optional[ScratchVar]
+        argument_var: ScratchVar | None
 
         if param in subroutine.by_ref_args:
             argument_var = DynamicScratchVar(TealType.anytype)
@@ -1021,7 +1030,9 @@ def __proto(subroutine: SubroutineDefinition) -> Proto:
             else int(subroutine.return_type != TealType.none)
         )
 
-        return Proto(subroutine.argument_count(), num_stack_outputs, mem_layout=layout)
+        proto = Proto(subroutine.argument_count(), num_stack_outputs, mem_layout=layout)
+        NatalStackFrame.mark_asts_as_compiler_gen(proto)
+        return proto
 
     def evaluate(self, subroutine: SubroutineDefinition) -> SubroutineDeclaration:
         proto = self.__proto(subroutine)
@@ -1039,7 +1050,7 @@ def evaluate(self, subroutine: SubroutineDefinition) -> SubroutineDeclaration:
 
         abi_output_kwargs: dict[str, abi.BaseType] = {}
         output_kwarg_info = OutputKwArgInfo.from_dict(subroutine.output_kwarg)
-        output_carrying_abi: Optional[abi.BaseType] = None
+        output_carrying_abi: abi.BaseType | None = None
 
         if output_kwarg_info:
             if not self.use_frame_pt:
@@ -1065,7 +1076,7 @@ def evaluate(self, subroutine: SubroutineDefinition) -> SubroutineDeclaration:
                 f"Subroutine function does not return a PyTeal expression. Got type {type(subroutine_body)}."
             )
 
-        deferred_expr: Optional[Expr] = None
+        deferred_expr: Expr | None = None
 
         # if there is an output keyword argument for ABI
         # place the storing on the stack with deferred expr only when compile to scratch var
diff --git a/pyteal/ast/txn.py b/pyteal/ast/txn.py
index d8de1bd84..99cc97d24 100644
--- a/pyteal/ast/txn.py
+++ b/pyteal/ast/txn.py
@@ -21,13 +21,13 @@
 class TxnType:
     """Enum of all possible transaction types."""
 
-    Unknown = EnumInt("unknown")
-    Payment = EnumInt("pay")
-    KeyRegistration = EnumInt("keyreg")
-    AssetConfig = EnumInt("acfg")
-    AssetTransfer = EnumInt("axfer")
-    AssetFreeze = EnumInt("afrz")
-    ApplicationCall = EnumInt("appl")
+    Unknown = EnumInt("unknown")  # T2PT7
+    Payment = EnumInt("pay")  # T2PT7
+    KeyRegistration = EnumInt("keyreg")  # T2PT7
+    AssetConfig = EnumInt("acfg")  # T2PT7
+    AssetTransfer = EnumInt("axfer")  # T2PT7
+    AssetFreeze = EnumInt("afrz")  # T2PT7
+    ApplicationCall = EnumInt("appl")  # T2PT7
 
 
 TxnType.__module__ = "pyteal"
diff --git a/pyteal/ast/while_.py b/pyteal/ast/while_.py
index 176830aad..0d8dee108 100644
--- a/pyteal/ast/while_.py
+++ b/pyteal/ast/while_.py
@@ -48,8 +48,9 @@ def __teal__(self, options: "CompileOptions"):
         end = TealSimpleBlock([])
 
         doEnd.setNextBlock(condStart)
+        doEnd._sframes_container = self
 
-        branchBlock = TealConditionalBlock([])
+        branchBlock = TealConditionalBlock([], root_expr=self)
         branchBlock.setTrueBlock(doStart)
         branchBlock.setFalseBlock(end)
 
@@ -86,6 +87,7 @@ def Do(self, doBlock: Expr, *do_block_multi: Expr):
         doBlock = _use_seq_if_multiple(doBlock, *do_block_multi)
 
         require_type(doBlock, TealType.none)
+
         self.doBlock = doBlock
         return self
 
diff --git a/pyteal/compiler/__init__.py b/pyteal/compiler/__init__.py
index e0d29450d..00646cf77 100644
--- a/pyteal/compiler/__init__.py
+++ b/pyteal/compiler/__init__.py
@@ -6,10 +6,11 @@
     MIN_PROGRAM_VERSION,
     DEFAULT_PROGRAM_VERSION,
     CompileOptions,
+    Compilation,
     compileTeal,
 )
-
 from pyteal.compiler.optimizer import OptimizeOptions
+from pyteal.compiler.sourcemap import PyTealSourceMap
 
 __all__ = [
     "MAX_TEAL_VERSION",
@@ -19,6 +20,8 @@
     "MIN_PROGRAM_VERSION",
     "DEFAULT_PROGRAM_VERSION",
     "CompileOptions",
+    "Compilation",
     "compileTeal",
     "OptimizeOptions",
+    "PyTealSourceMap",
 ]
diff --git a/pyteal/compiler/compiler.py b/pyteal/compiler/compiler.py
index 8186822cd..efca355cf 100644
--- a/pyteal/compiler/compiler.py
+++ b/pyteal/compiler/compiler.py
@@ -1,22 +1,38 @@
-from typing import Final, List, Tuple, Set, Dict, Optional, cast
+from dataclasses import dataclass
+from typing import Dict, Final, List, Optional, Set, Tuple, cast
 
-from pyteal.compiler.optimizer import OptimizeOptions, apply_global_optimizations
+from algosdk.v2client.algod import AlgodClient
 
-from pyteal.ast import Expr, Return, Seq, SubroutineDefinition, SubroutineDeclaration
-from pyteal.compiler.sort import sortBlocks
+from pyteal.ast import Expr, Return, Seq, SubroutineDeclaration, SubroutineDefinition
+from pyteal.compiler.constants import createConstantBlocks
 from pyteal.compiler.flatten import flattenBlocks, flattenSubroutines
+from pyteal.compiler.optimizer import OptimizeOptions, apply_global_optimizations
 from pyteal.compiler.scratchslots import (
     assignScratchSlotsToSubroutines,
     collect_unoptimized_slots,
 )
+from pyteal.compiler.sort import sortBlocks
+from pyteal.compiler.sourcemap import (
+    _PyTealSourceMapper,
+    PyTealSourceMap,
+)
 from pyteal.compiler.subroutines import (
-    spillLocalSlotsDuringRecursion,
     resolveSubroutines,
+    spillLocalSlotsDuringRecursion,
 )
-from pyteal.compiler.constants import createConstantBlocks
-from pyteal.errors import TealInputError, TealInternalError
-from pyteal.ir import Mode, Op, TealComponent, TealOp, TealBlock, TealSimpleBlock
+from pyteal.errors import SourceMapDisabledError, TealInputError, TealInternalError
+from pyteal.ir import (
+    Mode,
+    Op,
+    TealBlock,
+    TealComponent,
+    TealOp,
+    TealPragma,
+    TealSimpleBlock,
+)
+from pyteal.stack_frame import NatalStackFrame, sourcemapping_off_context
 from pyteal.types import TealType
+from pyteal.util import algod_with_assertion
 
 
 MAX_PROGRAM_VERSION = 8
@@ -133,15 +149,16 @@ def compileSubroutine(
         else None
     )
 
+    ret_expr: Optional[Expr] = None
     if not ast.has_return():
         if ast.type_of() == TealType.none:
-            ret_expr = Return()
+            ret_expr = Return()  # T2PT2
             ret_expr.trace = ast.trace
             seq_expr = Seq([ast, ret_expr])
             seq_expr.trace = ret_expr.trace
             ast = seq_expr
         else:
-            ret_expr = Return(ast)
+            ret_expr = Return(ast)  # T2PT3
             ret_expr.trace = ast.trace
             ast = ret_expr
 
@@ -150,49 +167,41 @@ def compileSubroutine(
     start, end = ast.__teal__(options)
     start.addIncoming()
     start.validateTree()
+    if currentSubroutine:
+        decl = currentSubroutine.get_declaration_by_option(options.use_frame_pointers)
+        if end.ops:
+            end.ops[0]._sframes_container = decl
+
+        if deferred_expr := decl.deferred_expr:
+            # this represents code that should be inserted before each retsub op
+            for block in TealBlock.Iterate(start):
+                if not any(op.getOp() == Op.retsub for op in block.ops):
+                    continue
+
+                if len(block.ops) != 1:
+                    # we expect all retsub ops to be in their own block at this point since
+                    # TealBlock.NormalizeBlocks has not yet been used
+                    raise TealInternalError(
+                        f"Expected retsub to be the only op in the block, but there are {len(block.ops)} ops"
+                    )
 
-    if (
-        currentSubroutine
-        and currentSubroutine.get_declaration_by_option(
-            options.use_frame_pointers
-        ).deferred_expr
-    ):
-        # this represents code that should be inserted before each retsub op
-        deferred_expr = cast(
-            Expr,
-            currentSubroutine.get_declaration_by_option(
-                options.use_frame_pointers
-            ).deferred_expr,
-        )
-
-        for block in TealBlock.Iterate(start):
-            if not any(op.getOp() == Op.retsub for op in block.ops):
-                continue
-
-            if len(block.ops) != 1:
-                # we expect all retsub ops to be in their own block at this point since
-                # TealBlock.NormalizeBlocks has not yet been used
-                raise TealInternalError(
-                    f"Expected retsub to be the only op in the block, but there are {len(block.ops)} ops"
-                )
-
-            # we invoke __teal__ here and not outside of this loop because the same block cannot be
-            # added in multiple places to the control flow graph
-            deferred_start, deferred_end = deferred_expr.__teal__(options)
-            deferred_start.addIncoming()
-            deferred_start.validateTree()
+                # we invoke __teal__ here and not outside of this loop because the same block cannot be
+                # added in multiple places to the control flow graph
+                deferred_start, deferred_end = deferred_expr.__teal__(options)
+                deferred_start.addIncoming()
+                deferred_start.validateTree()
 
-            # insert deferred blocks between the previous block(s) and this one
-            deferred_start.incoming = block.incoming
-            block.incoming = [deferred_end]
-            deferred_end.nextBlock = block
+                # insert deferred blocks between the previous block(s) and this one
+                deferred_start.incoming = block.incoming
+                block.incoming = [deferred_end]
+                deferred_end.nextBlock = block
 
-            for prev in deferred_start.incoming:
-                prev.replaceOutgoing(block, deferred_start)
+                for prev in deferred_start.incoming:
+                    prev.replaceOutgoing(block, deferred_start)
 
-            if block is start:
-                # this is the start block, replace start
-                start = deferred_start
+                if block is start:
+                    # this is the start block, replace start
+                    start = deferred_start
 
     start.validateTree()
 
@@ -214,7 +223,7 @@ def compileSubroutine(
     newSubroutines = referencedSubroutines - subroutine_start_blocks.keys()
     for subroutine in sorted(newSubroutines, key=lambda subroutine: subroutine.id):
         compileSubroutine(
-            subroutine.get_declaration_by_option(options.use_frame_pointers),
+            subroutine.get_declaration_by_option(options.use_frame_pointers),  # T2PT4
             options,
             subroutineGraph,
             subroutine_start_blocks,
@@ -236,13 +245,292 @@ def sort_subroutine_blocks(
     return subroutine_mapping
 
 
+@dataclass(frozen=True)
+class CompileResults:
+    """Summary of compilation"""
+
+    teal: str
+    sourcemap: PyTealSourceMap | None = None
+
+
+@dataclass
+class _FullCompilationBundle:
+    """
+    Private class that groups together various artifacts required and produced by the compiler.
+
+    The following artifacts should _NOT_ be returned to the user, as they could
+    interfere with the compiler's idempotency. For example, keeping these
+    artifacts around could make it difficult to guarantee that scratchslots
+    are allocated as efficiently as possible, and assuming that such artifacts
+    might continue existing after compilation interferes with the goal of making
+    the compiler as reliable and efficient as possible:
+    * teal_chunks
+    * components
+
+    NOTE: `annotated_teal` can grow quite large and become unsuitable for compilation as algod's compile
+    endpoint may throw a "request body too large" error.
+    Therefore, it is recommended that `teal` be used for algod compilation purposes.
+    """
+
+    ast: Expr
+    mode: Mode
+    version: int
+    assemble_constants: bool
+    optimize: Optional[OptimizeOptions]
+    teal: str
+    teal_chunks: list[str]
+    components: list[TealComponent]
+    sourcemapper: _PyTealSourceMapper | None = None
+    annotated_teal: str | None = None
+
+    def get_results(self) -> CompileResults:
+        sourcemap: PyTealSourceMap | None = None
+        if self.sourcemapper:
+            sourcemap = self.sourcemapper.get_sourcemap(self.teal)
+
+        return CompileResults(self.teal, sourcemap)
+
+
+class Compilation:
+    """
+    A class that encapsulates the data needed to compile a PyTeal expression
+    """
+
+    def __init__(
+        self,
+        ast: Expr,
+        mode: Mode,
+        *,
+        version: int = DEFAULT_PROGRAM_VERSION,
+        assemble_constants: bool = False,
+        optimize: OptimizeOptions | None = None,
+    ):
+        """
+        Instantiate a Compilation object providing the necessary data to compile a PyTeal expression.
+
+        Args:
+            ast: The PyTeal expression to assemble
+            mode: The program's mode for execution. Either `Mode.Signature` or `Mode.Application`
+            version (optional):  The program version used to assemble the program. This will determine which
+                expressions and fields are able to be used in the program and how expressions compile to
+                TEAL opcodes. Defaults to 2 if not included.
+            assembleConstants (optional): When `True`, the compiler will produce a program with fully
+                assembled constants, rather than using the pseudo-ops `int`, `byte`, and `addr`. These
+                constants will be assembled in the most space-efficient way, so enabling this may reduce
+                the compiled program's size. Enabling this option requires a minimum program version of 3.
+                Defaults to `False`.
+            optimize (optional): `OptimizeOptions` that determine which optimizations will be applied.
+        """
+        self.ast = ast
+        self.mode = mode
+        self.version = version
+        self.assemble_constants = assemble_constants
+        self.optimize: OptimizeOptions = optimize or OptimizeOptions()
+
+    def compile(
+        self,
+        *,
+        with_sourcemap: bool = False,
+        teal_filename: str | None = None,
+        pcs_in_sourcemap: bool = False,
+        algod_client: AlgodClient | None = None,
+        annotate_teal: bool = False,
+        annotate_teal_headers: bool = False,
+        annotate_teal_concise: bool = False,
+    ) -> CompileResults:
+        """Execute the compilation, producing a TEAL assembly along with other optional artifacts.
+
+        Args:
+            with_sourcemap (optional): When `True`, the compiler will produce a sourcemap that maps the
+                generated TEAL assembly back to the original PyTeal source code. Defaults to `False`.
+            teal_filename (optional): The filename to use in the sourcemap. Defaults to `None`.
+            pcs_in_sourcemap (optional): When `True`, the compiler will include the program counter in
+                relevant sourcemap artifacts. This requires an `AlgodClient` (see next param). Defaults to `False`.
+            algod_client (optional): An `AlgodClient` to use to fetch the program counter. Defaults to `None`.
+                When `pcs_in_sourcemap` is `True` and `algod_client` is not provided, the compiler will
+                assume that an Algorand Sandbox algod client is running on the default port (4001) and -if
+                this is not the case- will raise an exception.
+            annotate_teal (optional): When `True`, the compiler will produce a TEAL assembly with comments
+                that describe the PyTeal source code that generated each line of the assembly. Defaults to `False`.
+            annotate_teal_headers (optional): When `True` along with `annotate_teal` being `True`, a header
+                line with column names will be added at the top of the annotated teal. Defaults to `False`.
+            annotate_teal_concise (optional): When `True` along with `annotate_teal` being `True`, the compiler
+                will provide fewer columns in the annotated teal. Defaults to `False`.
+
+        Returns:
+            A `CompileResults` object with the following data:
+            * teal: the TEAL assembly
+            * sourcemap (optional): if `with_sourcemap` is `True`, the following source map data is provided:
+                * teal_filename (optional): the TEAL filename, if this was provided
+                * r3_sourcemap: an `R3SourceMap` object that maps the generated TEAL assembly back to the original PyTeal source code and conforms to the specs of the `Source Map Revision 3 Proposal <https://sourcemaps.info/spec.html>`_
+                * pc_sourcemap (optional): if `pcs_in_sourcemap` is `True`, a `PCSourceMap` object that maps the program counters assembled by the `AlgodClient` which was utilized in the compilation back to the TEAL assembly which was generated by the compiler. This conforms to the specs of the `Source Map Revision 3 Proposal <https://sourcemaps.info/spec.html>`_
+                * annotated_teal (optional): if `annotate_teal` is `True`, the TEAL assembly with comments that describe the PyTeal source code that generated each line of the assembly
+
+        Raises:
+            TealInputError: if an operation in ast is not supported by the supplied mode and version.
+            TealInternalError: if an internal error is encountered during compilation.
+        """
+        return self._compile_impl(
+            with_sourcemap=with_sourcemap,
+            teal_filename=teal_filename,
+            pcs_in_sourcemap=pcs_in_sourcemap,
+            algod_client=algod_client,
+            annotate_teal=annotate_teal,
+            annotate_teal_headers=annotate_teal_headers,
+            annotate_teal_concise=annotate_teal_concise,
+        ).get_results()
+
+    def _compile_impl(
+        self,
+        with_sourcemap: bool = True,
+        teal_filename: str | None = None,
+        pcs_in_sourcemap: bool = False,
+        algod_client: AlgodClient | None = None,
+        annotate_teal: bool = False,
+        annotate_teal_headers: bool = False,
+        annotate_teal_concise: bool = True,
+    ) -> _FullCompilationBundle:
+        if (
+            not (MIN_PROGRAM_VERSION <= self.version <= MAX_PROGRAM_VERSION)
+            or type(self.version) is not int
+        ):
+            raise TealInputError(
+                "Unsupported program version: {}. Excepted an integer in the range [{}, {}]".format(
+                    self.version, MIN_PROGRAM_VERSION, MAX_PROGRAM_VERSION
+                )
+            )
+
+        if with_sourcemap and NatalStackFrame.sourcemapping_is_off():
+            raise SourceMapDisabledError()
+
+        if annotate_teal and not with_sourcemap:
+            raise ValueError(
+                "In order annotate generated teal source, must set with_sourcemap True"
+            )
+
+        if pcs_in_sourcemap:
+            # bootstrap an algod_client if not provided, and in either case, run a healthcheck
+            algod_client = algod_with_assertion(
+                algod_client, msg="Adding PC's to sourcemap requires live Algod"
+            )
+
+        options = CompileOptions(
+            mode=self.mode, version=self.version, optimize=self.optimize
+        )
+
+        subroutineGraph: Dict[SubroutineDefinition, Set[SubroutineDefinition]] = dict()
+        subroutine_start_blocks: Dict[
+            Optional[SubroutineDefinition], TealBlock
+        ] = dict()
+        subroutine_end_blocks: Dict[Optional[SubroutineDefinition], TealBlock] = dict()
+        compileSubroutine(
+            self.ast,
+            options,
+            subroutineGraph,
+            subroutine_start_blocks,
+            subroutine_end_blocks,
+        )
+
+        # note: optimizations are off by default, in which case, apply_global_optimizations
+        # won't make any changes. Because the optimizer is invoked on a subroutine's
+        # control flow graph, the optimizer requires context across block boundaries. This
+        # is necessary for the dependency checking of local slots. Global slots, slots
+        # used by DynamicScratchVar, and reserved slots are not optimized.
+        if options.optimize.optimize_scratch_slots(self.version):
+            options.optimize._skip_slots = collect_unoptimized_slots(
+                subroutine_start_blocks
+            )
+            for start in subroutine_start_blocks.values():
+                apply_global_optimizations(start, options.optimize, self.version)
+
+        localSlotAssignments: Dict[
+            Optional[SubroutineDefinition], Set[int]
+        ] = assignScratchSlotsToSubroutines(subroutine_start_blocks)
+
+        subroutineMapping: Dict[
+            Optional[SubroutineDefinition], List[TealComponent]
+        ] = sort_subroutine_blocks(subroutine_start_blocks, subroutine_end_blocks)
+
+        spillLocalSlotsDuringRecursion(
+            self.version, subroutineMapping, subroutineGraph, localSlotAssignments
+        )
+
+        subroutineLabels = resolveSubroutines(subroutineMapping)
+        components: list[TealComponent] = flattenSubroutines(
+            subroutineMapping, subroutineLabels, options
+        )
+
+        verifyOpsForVersion(components, options.version)
+        verifyOpsForMode(components, options.mode)
+
+        if self.assemble_constants:
+            if self.version < 3:
+                raise TealInternalError(
+                    f"The minimum program version required to enable assembleConstants is 3. The current version is {self.version}."
+                )
+            components = createConstantBlocks(components)
+
+        components = [TealPragma(self.version)] + components  # T2PT0
+        teal_chunks = [tl.assemble() for tl in components]
+        teal_code = "\n".join(teal_chunks)
+
+        full_cpb = _FullCompilationBundle(
+            ast=self.ast,
+            mode=self.mode,
+            version=self.version,
+            assemble_constants=self.assemble_constants,
+            optimize=self.optimize,
+            teal=teal_code,
+            teal_chunks=teal_chunks,
+            components=components,
+        )
+        if not with_sourcemap:
+            return full_cpb
+
+        # Below is purely for the source mapper:
+
+        source_mapper = _PyTealSourceMapper(
+            teal_chunks=teal_chunks,
+            components=components,
+            build=True,
+            teal_filename=teal_filename,
+            include_pcs=pcs_in_sourcemap,
+            algod=algod_client,
+            annotate_teal=annotate_teal,
+            annotate_teal_headers=annotate_teal_headers,
+            annotate_teal_concise=annotate_teal_concise,
+        )
+        full_cpb.sourcemapper = source_mapper
+
+        # run a second time without, and assert that the same teal is produced
+        with sourcemapping_off_context():
+            assert NatalStackFrame.sourcemapping_is_off()
+
+            # implicitly recursive call!!
+            teal_code_wo = compileTeal(
+                self.ast,
+                self.mode,
+                version=self.version,
+                assembleConstants=self.assemble_constants,
+                optimize=self.optimize,
+            )
+
+            _PyTealSourceMapper._validate_teal_identical(
+                teal_code_wo,
+                teal_code,
+                msg="FATAL ERROR. Program without sourcemaps (LEFT) differs from Program with (RIGHT)",
+            )
+
+        return full_cpb
+
+
 def compileTeal(
     ast: Expr,
     mode: Mode,
     *,
     version: int = DEFAULT_PROGRAM_VERSION,
     assembleConstants: bool = False,
-    optimize: Optional[OptimizeOptions] = None,
+    optimize: OptimizeOptions | None = None,
 ) -> str:
     """Compile a PyTeal expression into TEAL assembly.
 
@@ -264,62 +552,13 @@ def compileTeal(
 
     Raises:
         TealInputError: if an operation in ast is not supported by the supplied mode and version.
-        TealInternalError: if an internal error is encounter during compilation.
+        TealInternalError: if an internal error is encountered during compilation.
     """
-    if (
-        not (MIN_PROGRAM_VERSION <= version <= MAX_PROGRAM_VERSION)
-        or type(version) is not int
-    ):
-        raise TealInputError(
-            "Unsupported program version: {}. Excepted an integer in the range [{}, {}]".format(
-                version, MIN_PROGRAM_VERSION, MAX_PROGRAM_VERSION
-            )
-        )
-
-    options = CompileOptions(mode=mode, version=version, optimize=optimize)
-
-    subroutineGraph: Dict[SubroutineDefinition, Set[SubroutineDefinition]] = dict()
-    subroutine_start_blocks: Dict[Optional[SubroutineDefinition], TealBlock] = dict()
-    subroutine_end_blocks: Dict[Optional[SubroutineDefinition], TealBlock] = dict()
-    compileSubroutine(
-        ast, options, subroutineGraph, subroutine_start_blocks, subroutine_end_blocks
-    )
-
-    # note: optimizations are off by default, in which case, apply_global_optimizations
-    # won't make any changes. Because the optimizer is invoked on a subroutine's
-    # control flow graph, the optimizer requires context across block boundaries. This
-    # is necessary for the dependency checking of local slots. Global slots, slots
-    # used by DynamicScratchVar, and reserved slots are not optimized.
-    if options.optimize.optimize_scratch_slots(version):
-        options.optimize._skip_slots = collect_unoptimized_slots(
-            subroutine_start_blocks
-        )
-        for start in subroutine_start_blocks.values():
-            apply_global_optimizations(start, options.optimize, version)
-
-    localSlotAssignments = assignScratchSlotsToSubroutines(subroutine_start_blocks)
-
-    subroutineMapping: Dict[
-        Optional[SubroutineDefinition], List[TealComponent]
-    ] = sort_subroutine_blocks(subroutine_start_blocks, subroutine_end_blocks)
-
-    spillLocalSlotsDuringRecursion(
-        version, subroutineMapping, subroutineGraph, localSlotAssignments
-    )
-
-    subroutineLabels = resolveSubroutines(subroutineMapping)
-    teal = flattenSubroutines(subroutineMapping, subroutineLabels)
-
-    verifyOpsForVersion(teal, options.version)
-    verifyOpsForMode(teal, options.mode)
-
-    if assembleConstants:
-        if version < 3:
-            raise TealInternalError(
-                f"The minimum program version required to enable assembleConstants is 3. The current version is {version}."
-            )
-        teal = createConstantBlocks(teal)
-
-    lines = ["#pragma version {}".format(version)]
-    lines += [i.assemble() for i in teal]
-    return "\n".join(lines)
+    bundle = Compilation(
+        ast,
+        mode,
+        version=version,
+        assemble_constants=assembleConstants,
+        optimize=optimize,
+    )._compile_impl(with_sourcemap=False)
+    return bundle.teal
diff --git a/pyteal/compiler/flatten.py b/pyteal/compiler/flatten.py
index 99674836c..8a0dfac48 100644
--- a/pyteal/compiler/flatten.py
+++ b/pyteal/compiler/flatten.py
@@ -1,7 +1,8 @@
-from typing import List, Dict, DefaultDict, Optional
 from collections import defaultdict
 
-from pyteal.ast import SubroutineDefinition
+from pyteal.ast import Expr, SubroutineDeclaration, SubroutineDefinition
+from pyteal import compiler
+from pyteal.errors import TealInternalError, TealInputError
 from pyteal.ir import (
     Op,
     TealOp,
@@ -12,19 +13,23 @@
     TealConditionalBlock,
     LabelReference,
 )
-from pyteal.errors import TealInternalError
 
 
-def flattenBlocks(blocks: List[TealBlock]) -> List[TealComponent]:
+def flattenBlocks(blocks: list[TealBlock]) -> list[TealComponent]:
     """Lowers a list of TealBlocks into a list of TealComponents.
 
     Args:
         blocks: The blocks to lower.
     """
-    codeblocks = []
-    references: DefaultDict[int, int] = defaultdict(int)
+    codeblocks: list[list[TealOp]] = []
+    references: defaultdict[int, int] = defaultdict(int)
+    referer: dict[int, int] = {}
 
-    labelRefs: Dict[int, LabelReference] = dict()
+    def add_if_new(nextIndex, i):
+        if nextIndex not in referer:
+            referer[nextIndex] = i
+
+    labelRefs: dict[int, LabelReference] = dict()
 
     def indexToLabel(index: int) -> LabelReference:
         if index not in labelRefs:
@@ -37,12 +42,15 @@ def blockIndexByReference(block: TealBlock) -> int:
                 return i
         raise ValueError("Block not present in list: {}".format(block))
 
+    root_expr: Expr | None = None
     for i, block in enumerate(blocks):
         code = list(block.ops)
         codeblocks.append(code)
         if block.isTerminal():
             continue
 
+        root_expr = block._sframes_container
+
         if type(block) is TealSimpleBlock:
             assert block.nextBlock is not None
 
@@ -50,7 +58,8 @@ def blockIndexByReference(block: TealBlock) -> int:
 
             if nextIndex != i + 1:
                 references[nextIndex] += 1
-                code.append(TealOp(None, Op.b, indexToLabel(nextIndex)))
+                add_if_new(nextIndex, i)
+                code.append(TealOp(root_expr, Op.b, indexToLabel(nextIndex)))  # T2PT5
 
         elif type(block) is TealConditionalBlock:
             assert block.trueBlock is not None
@@ -61,35 +70,46 @@ def blockIndexByReference(block: TealBlock) -> int:
 
             if falseIndex == i + 1:
                 references[trueIndex] += 1
-                code.append(TealOp(None, Op.bnz, indexToLabel(trueIndex)))
+                add_if_new(trueIndex, i)
+                code.append(TealOp(root_expr, Op.bnz, indexToLabel(trueIndex)))  # T2PT5
                 continue
 
             if trueIndex == i + 1:
                 references[falseIndex] += 1
-                code.append(TealOp(None, Op.bz, indexToLabel(falseIndex)))
+                add_if_new(falseIndex, i)
+                code.append(TealOp(root_expr, Op.bz, indexToLabel(falseIndex)))  # T2PT5
                 continue
 
             references[trueIndex] += 1
-            code.append(TealOp(None, Op.bnz, indexToLabel(trueIndex)))
+            add_if_new(trueIndex, i)
+            code.append(TealOp(root_expr, Op.bnz, indexToLabel(trueIndex)))  # T2PT5
 
             references[falseIndex] += 1
-            code.append(TealOp(None, Op.b, indexToLabel(falseIndex)))
+            add_if_new(falseIndex, i)
+            code.append(TealOp(root_expr, Op.b, indexToLabel(falseIndex)))  # T2PT5
         else:
             raise TealInternalError("Unrecognized block type: {}".format(type(block)))
 
-    teal: List[TealComponent] = []
+    teal: list[TealComponent] = []
+    root_expr = None
     for i, code in enumerate(codeblocks):
         if references[i] != 0:
-            teal.append(TealLabel(None, indexToLabel(i)))
+            root_expr = (
+                blocks[i]._sframes_container
+                or blocks[referer[i]]._sframes_container
+                or root_expr
+            )
+            teal.append(TealLabel(root_expr, indexToLabel(i)))  # T2PT6
         teal += code
 
     return teal
 
 
 def flattenSubroutines(
-    subroutineMapping: Dict[Optional[SubroutineDefinition], List[TealComponent]],
-    subroutineToLabel: Dict[SubroutineDefinition, str],
-) -> List[TealComponent]:
+    subroutineMapping: dict[SubroutineDefinition | None, list[TealComponent]],
+    subroutineToLabel: dict[SubroutineDefinition, str],
+    options: "compiler.CompileOptions",
+) -> list[TealComponent]:
     """Combines each subroutine's list of TealComponents into a single list of TealComponents that
     represents the entire program.
 
@@ -101,7 +121,7 @@ def flattenSubroutines(
     Returns:
         A single list of TealComponents representing the entire program.
     """
-    combinedOps: List[TealComponent] = []
+    combinedOps: list[TealComponent] = []
 
     # By default all branch labels in each subroutine will start from "l0". To
     # make each subroutine have unique labels, we prefix "main_" to the ones
@@ -123,7 +143,14 @@ def flattenSubroutines(
             if isinstance(stmt, TealLabel):
                 stmt.getLabelRef().addPrefix(labelPrefix)
 
-        combinedOps.append(TealLabel(None, LabelReference(label), comment))
+        # this is needed for source map generation
+        dexpr: SubroutineDeclaration | None
+        try:
+            dexpr = subroutine.get_declaration_by_option(options.use_frame_pointers)
+        except TealInputError:
+            dexpr = None
+
+        combinedOps.append(TealLabel(dexpr, LabelReference(label), comment))  # T2PT1
         combinedOps += subroutineOps
 
     return combinedOps
diff --git a/pyteal/compiler/sourcemap.py b/pyteal/compiler/sourcemap.py
new file mode 100644
index 000000000..48c60354f
--- /dev/null
+++ b/pyteal/compiler/sourcemap.py
@@ -0,0 +1,1318 @@
+import bisect
+from collections import defaultdict
+from dataclasses import dataclass, field
+from difflib import unified_diff
+from functools import partial
+from itertools import count
+import re
+from typing import Any, Final, Literal, Mapping, OrderedDict, TypedDict, cast
+
+from tabulate import tabulate  # type: ignore
+
+from algosdk.source_map import SourceMap as PCSourceMap  # disambiguate
+from algosdk.v2client.algod import AlgodClient
+
+import pyteal as pt
+from pyteal.errors import TealInternalError
+from pyteal.stack_frame import (
+    PT_GENERATED,
+    NatalStackFrame,
+    PyTealFrame,
+    PyTealFrameStatus,
+)
+from pyteal.util import algod_with_assertion
+
+# ### ---- R3SourceMap is based on mjpieters code snippets ---- ### #
+#
+#     Modified from the original `SourceMap` available under MIT License here (as of Nov. 12, 2022): https://gist.github.com/mjpieters/86b0d152bb51d5f5979346d11005588b
+#    `R3` is a nod to "Revision 3" of John Lenz's Source Map Proposal: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit?hl=en_US&pli=1&pli=1
+#
+# ###
+
+_b64chars = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
+_b64table = [-1] * (max(_b64chars) + 1)
+for i, b in enumerate(_b64chars):
+    _b64table[b] = i
+
+shiftsize, flag, mask = 5, 1 << 5, (1 << 5) - 1
+
+
+def _base64vlq_decode(vlqval: str) -> list[int]:
+    """Decode Base64 VLQ value"""
+    results = []
+    shift = value = 0
+    # use byte values and a table to go from base64 characters to integers
+    for v in map(_b64table.__getitem__, vlqval.encode("ascii")):
+        v = cast(int, v)
+        value += (v & mask) << shift
+        if v & flag:
+            shift += shiftsize
+            continue
+        # determine sign and add to results
+        results.append((value >> 1) * (-1 if value & 1 else 1))
+        shift = value = 0
+    return results
+
+
+def _base64vlq_encode(*values: int) -> str:
+    """Encode integers to a VLQ value"""
+    results: list[int] = []
+    add = results.append
+    for v in values:
+        # add sign bit
+        v = (abs(v) << 1) | int(v < 0)
+        while True:
+            toencode, v = v & mask, v >> shiftsize
+            add(toencode | (v and flag))
+            if not v:
+                break
+    # TODO: latest version of gist avoids the decode() step
+    return bytes(map(_b64chars.__getitem__, results)).decode()
+
+
+class autoindex(defaultdict):
+    def __init__(self, *args, **kwargs):
+        super().__init__(partial(next, count()), *args, **kwargs)
+
+
+@dataclass(frozen=False)
+class R3SourceMapping:
+    line: int
+    # line_end: Optional[int] = None #### NOT PROVIDED (AND NOT CONFORMING WITH R3 SPEC) AS TARGETS ARE ASSUMED TO SPAN AT MOST ONE LINE ####
+    column: int
+    source: str | None = None
+    source_line: int | None = None
+    source_column: int | None = None
+    source_content: list[str] | None = None
+    source_extract: str | None = None
+    target_extract: str | None = None
+    name: str | None = None
+    source_line_end: int | None = None
+    source_column_end: int | None = None
+    column_end: int | None = None
+
+    def __post_init__(self):
+        if self.source is not None and (
+            self.source_line is None or self.source_column is None
+        ):
+            raise TypeError(
+                "Invalid source mapping; missing line and column for source file"
+            )
+        if self.name is not None and self.source is None:
+            raise TypeError(
+                "Invalid source mapping; name entry without source location info"
+            )
+
+    def __lt__(self, other: "R3SourceMapping") -> bool:
+        assert isinstance(other, type(self)), f"received incomparable {type(other)}"
+
+        return (self.line, self.column) < (other.line, other.column)
+
+    def __ge__(self, other: "R3SourceMapping") -> bool:
+        return not self < other
+
+    def location(self, source=False) -> tuple[str, int, int]:
+        return (
+            (
+                self.source if self.source else "",
+                self.source_line if self.source_line else -1,
+                self.source_column if self.source_column else -1,
+            )
+            if source
+            else ("", self.line, self.column)
+        )
+
+    @classmethod
+    def extract_window(
+        cls,
+        source_lines: list[str] | None,
+        line: int,
+        column: int,
+        right_column: int | None,
+    ) -> str | None:
+        return (
+            (
+                source_lines[line][column:right_column]
+                if right_column is not None
+                else source_lines[line][column:]
+            )
+            if source_lines
+            else None
+        )
+
+    def __str__(self) -> str:
+        def swindow(file, line, col, rcol, extract):
+            if file == "unknown":
+                file = None
+            if not rcol:
+                rcol = ""
+            if extract is None:
+                extract = "?"
+            return f"{file + '::' if file else ''}L{line}C{col}-{rcol}='{extract}'"
+
+        return (
+            f"{swindow(self.source, self.source_line, self.source_column, self.source_column_end, self.source_extract)} <- "
+            f"{swindow(None, self.line, self.column, self.column_end, self.target_extract)}"
+        )
+
+    __repr__ = __str__
+
+
+class R3SourceMapJSON(TypedDict, total=False):
+    version: Literal[3]
+    file: str | None
+    sourceRoot: str | None
+    sources: list[str]
+    sourcesContent: list[str | None] | None
+    names: list[str]
+    mappings: str
+
+
+@dataclass(frozen=True)
+class R3SourceMap:
+    """
+    This class is renames mjpieters' SourceMap
+    (https://gist.github.com/mjpieters/86b0d152bb51d5f5979346d11005588b#file-sourcemap-py-L62)
+    and tweaks it a bit, adding the following functionality:
+    * adds fields `file_liens`, `source_files`, `entries`
+    * __post_init__ (new) - runs a sanity check validation on the ordering of provided entries
+    * __repr__ - printing out "R3SourceMap(...)" instead of "MJPSourceMap(...)"
+    * from_json - accepting new params `sources_override`, `sources_content_override`, `target`, `add_right_bounds`
+    * add_right_bounds (new) - allow specifying the right column bounds
+    * to_json - accepting new param `with_contents`
+
+    The main methods for this class are `from_json` and `to_json` which
+    follow the encoding conventions outlined in the Source Map Revison 3 Proposal
+    (https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit?hl=en_US&pli=1&pli=1)
+    """
+
+    filename: str | None
+    source_root: str | None
+    entries: Mapping[tuple[int, int], "R3SourceMapping"]
+    index: list[tuple[int, ...]] = field(default_factory=list)
+    file_lines: list[str] | None = None
+    source_files: list[str] | None = None
+    source_files_lines: list[list[str] | None] | None = None
+
+    def __post_init__(self):
+        entries = list(self.entries.values())
+        for i, entry in enumerate(entries):
+            if i + 1 >= len(entries):
+                return
+
+            if entry >= entries[i + 1]:
+                raise TypeError(
+                    f"Invalid source map as entries aren't properly ordered: entries[{i}] = {entry} >= entries[{i+1}] = {entries[i + 1]}"
+                )
+
+    def __repr__(self) -> str:
+        parts = []
+        if self.filename is not None:
+            parts += [f"file={self.filename!r}"]
+        if self.source_root is not None:
+            parts += [f"source_root={self.source_root!r}"]
+        parts += [f"len={len(self.entries)}"]
+        return f"<R3SourceMap({', '.join(parts)})>"
+
+    @classmethod
+    def from_json(
+        cls,
+        smap: R3SourceMapJSON,
+        sources_override: list[str] | None = None,
+        sources_content_override: list[str] = [],
+        target: str | None = None,
+        add_right_bounds: bool = True,
+    ) -> "R3SourceMap":
+        """
+        NOTE about `*_if_missing` arguments
+        * sources_override - STRICTLY SPEAKING `sources` OUGHT NOT BE MISSING OR EMPTY in R3SourceMapJSON.
+            However, currently the POST v2/teal/compile endpoint populate this field with an empty list, as it is not provided the name of the
+            Teal file which is being compiled. In order comply with the R3 spec, this field is populated with ["unknown"] when either missing or empty
+            in the JSON and not supplied during construction.
+            An error will be raised when attempting to replace a nonempty R3SourceMapJSON.sources.
+        * sources_content_override - `sourcesContent` is optional and this provides a way at runtime to supply the actual source.
+            When provided, and the R3SourceMapJSON is either missing or empty, this will be substituted.
+            An error will be raised when attempting to replace a nonempty R3SourceMapJSON.sourcesContent.
+        """
+        if smap.get("version") != 3:
+            raise ValueError("Only version 3 sourcemaps are supported ")
+        entries: dict[tuple[int, int], R3SourceMapping] = {}
+        index: list[list[int]] = []
+        spos = npos = sline = scol = 0
+
+        sources = smap.get("sources")
+        if sources and sources_override:
+            raise AssertionError("ambiguous sources from JSON and method argument")
+        sources = sources or sources_override or ["unknown"]
+
+        contents: list[str | None] | list[str] | None = smap.get("sourcesContent")
+        if contents and sources_content_override:
+            raise AssertionError(
+                "ambiguous sourcesContent from JSON and method argument"
+            )
+        contents = contents or sources_content_override
+
+        names = smap.get("names")
+
+        tcont, sp_conts = (
+            target.splitlines() if target else None,
+            [c.splitlines() if c else None for c in contents],
+        )
+
+        if "mappings" in smap:
+            for gline, vlqs in enumerate(smap["mappings"].split(";")):
+                index += [[]]
+                if not vlqs:
+                    continue
+                gcol = 0
+                for gcd, *ref in map(_base64vlq_decode, vlqs.split(",")):
+                    gcol += gcd
+                    kwargs = {}
+                    if len(ref) >= 3:
+                        sd, sld, scd, *namedelta = ref
+                        spos, sline, scol = spos + sd, sline + sld, scol + scd
+                        scont = sp_conts[spos] if len(sp_conts) > spos else None  # type: ignore
+                        # extract the referenced source till the end of the current line
+                        extract = R3SourceMapping.extract_window
+                        kwargs = {
+                            "source": sources[spos] if spos < len(sources) else None,
+                            "source_line": sline,
+                            "source_column": scol,
+                            "source_content": scont,
+                            "source_extract": extract(scont, sline, scol, None),
+                            "target_extract": extract(tcont, gline, gcol, None),
+                        }
+                        if namedelta and names:
+                            npos += namedelta[0]
+                            kwargs["name"] = names[npos]
+                    entries[gline, gcol] = R3SourceMapping(
+                        line=gline, column=gcol, **kwargs  # type: ignore
+                    )
+                    index[gline].append(gcol)
+
+        sourcemap = cls(
+            smap.get("file"),
+            smap.get("sourceRoot"),
+            entries,
+            [tuple(cs) for cs in index],
+            tcont,
+            sources,
+            sp_conts,
+        )
+        if add_right_bounds:
+            sourcemap.add_right_bounds()
+        return sourcemap
+
+    def add_right_bounds(self) -> None:
+        entries = list(self.entries.values())
+        for i, entry in enumerate(entries):
+            if i + 1 >= len(entries):
+                continue
+
+            next_entry = entries[i + 1]
+
+            def same_line_less_than(lc, nlc):
+                return (lc[0], lc[1]) == (nlc[0], nlc[1]) and lc[2] < nlc[2]
+
+            if not same_line_less_than(entry.location(), next_entry.location()):
+                continue
+            entry.column_end = next_entry.column
+            entry.target_extract = entry.extract_window(
+                self.file_lines, entry.line, entry.column, entry.column_end
+            )
+
+            if not all(
+                [
+                    self.source_files,
+                    self.source_files_lines,
+                    next_entry.source,
+                    same_line_less_than(
+                        entry.location(source=True),
+                        next_entry.location(source=True),
+                    ),
+                ]
+            ):
+                continue
+            entry.source_column_end = next_entry.source_column
+            try:
+                fidx = self.source_files.index(next_entry.source)  # type: ignore
+            except ValueError:
+                continue
+            if (
+                self.source_files_lines
+                and isinstance(entry.source_line, int)
+                and isinstance(entry.source_column, int)
+            ):
+                entry.source_extract = entry.extract_window(
+                    self.source_files_lines[fidx],
+                    entry.source_line,
+                    entry.source_column,
+                    next_entry.source_column,
+                )
+
+    def to_json(self, with_contents: bool = False) -> R3SourceMapJSON:
+        content: list[str | None] = []
+        mappings: list[str] = []
+        sources, names = autoindex(), autoindex()
+        entries = self.entries
+        spos = sline = scol = npos = 0
+        for gline, cols in enumerate(self.index):
+            gcol = 0
+            mapping = []
+            for col in cols:
+                entry = entries[gline, col]
+                ds, gcol = [col - gcol], col
+
+                if entry.source is not None:
+                    assert entry.source_line is not None
+                    assert entry.source_column is not None
+                    ds += (
+                        sources[entry.source] - spos,
+                        entry.source_line - sline,
+                        entry.source_column - scol,
+                    )
+                    spos, sline, scol = (
+                        spos + ds[1],
+                        sline + ds[2],
+                        scol + ds[3],
+                    )
+                    if spos == len(content):
+                        c = entry.source_content
+                        content.append("\n".join(c) if c else None)
+                    if entry.name is not None:
+                        ds += (names[entry.name] - npos,)
+                        npos += ds[-1]
+                mapping.append(_base64vlq_encode(*ds))
+
+            mappings.append(",".join(mapping))
+
+        encoded = {
+            "version": 3,
+            "sources": [s for s, _ in sorted(sources.items(), key=lambda si: si[1])],
+            "names": [n for n, _ in sorted(names.items(), key=lambda ni: ni[1])],
+            "mappings": ";".join(mappings),
+        }
+        if with_contents:
+            encoded["sourcesContent"] = content
+        if self.filename is not None:
+            encoded["file"] = self.filename
+        if self.source_root is not None:
+            encoded["sourceRoot"] = self.source_root
+        return encoded  # type: ignore
+
+    def __getitem__(self, idx: int | tuple[int, int]):
+        l: int
+        c: int
+        try:
+            l, c = idx  # type: ignore   # The exception handler deals with the int case
+        except TypeError:
+            l, c = idx, 0  # type: ignore   # yes, idx is guaranteed to be an int
+        try:
+            return self.entries[l, c]
+        except KeyError:
+            # find the closest column
+            if not (cols := self.index[l]):
+                raise IndexError(idx)
+            cidx = bisect.bisect(cols, c)
+            return self.entries[l, cols[cidx and cidx - 1]]
+
+
+# #### PyTeal Specific Classes below #### #
+
+_TEAL_LINE_NUMBER = "TL"
+_TEAL_COLUMN = "TC"
+_TEAL_COLUMN_END = "TCE"
+_TEAL_LINE = "Teal Line"
+_PROGRAM_COUNTERS = "PC"
+_PYTEAL_HYBRID_UNPARSED = "PyTeal Hybrid Unparsed"
+_PYTEAL_NODE_AST_UNPARSED = "PyTeal AST Unparsed"
+_PYTEAL_NODE_AST_QUALNAME = "PyTeal Qualname"
+_PYTEAL_COMPONENT = "PyTeal Qualname"
+_PYTEAL_NODE_AST_SOURCE_BOUNDARIES = "PT Window"
+_PYTEAL_FILENAME = "PT path"
+_PYTEAL_LINE_NUMBER = "PTL"
+_PYTEAL_LINE_NUMBER_END = "PTLE"
+_PYTEAL_COLUMN = "PTC"
+_PYTEAL_COLUMN_END = "PTCE"
+_PYTEAL_LINE = "PyTeal"
+_PYTEAL_NODE_AST = "PT AST"
+_PYTEAL_NODE_AST_NONE = "FAILED"
+_STATUS_CODE = "Sourcemap Status Code"
+_STATUS = "Sourcemap Status"
+
+
+class TealMapItem(PyTealFrame):
+    """
+    TealMapItem extends PyTealFrame to add information about the teal code that
+    corresponds to the purported pyteal source.
+    It also encapsulates any program counter (PC) information, when given.
+
+    The main consumers of TealMapItem are:
+    * _PyTealSourceMapper._build_r3sourcemap() which calls `source_mapping()`
+        and provides the result for constructing an R3SourceMap
+    * _PyTealSourceMapper.tabulate() which calls `asdict()` and is useful for
+        creating PyTeal (and PC) annotated *.teal files
+    """
+
+    def __init__(
+        self,
+        pt_frame: PyTealFrame,
+        teal_lineno: int,
+        teal_line: str,
+        teal_component: "pt.TealComponent",
+        pcs: list[int] | None = None,
+    ):
+        super().__init__(
+            frame_info=pt_frame.frame_info,
+            node=pt_frame.node,
+            creator=pt_frame.creator,
+            full_stack=pt_frame.full_stack,
+            rel_paths=pt_frame.rel_paths,
+            parent=pt_frame.parent,
+        )
+        self.teal_lineno: Final[int] = teal_lineno
+        self.teal_line: Final[str] = teal_line
+        self.teal_component: Final[pt.TealComponent] = teal_component
+        self.pcs: Final[list[int] | None] = pcs if pcs else None
+
+    def pcs_repr(self, prefix: str = "") -> str:
+        if not self.pcs:
+            return ""
+        return f"{prefix}({self.pcs[0]})"
+
+    def __repr__(self) -> str:
+        P = " // "
+        return f"TealLine({self.teal_lineno}: {self.teal_line}{self.pcs_repr(prefix=P)} // PyTeal: {self._hybrid_w_offset()[0]}"
+
+    def teal_column(self) -> int:
+        """Always returns 0 as the 0-index STARTING column offset"""
+        return 0
+
+    def teal_column_end(self) -> int:
+        """The 0-index ENDING column offset"""
+        return len(self.teal_line)
+
+    _dict_lazy_attrs = {
+        _TEAL_LINE_NUMBER: lambda tmi: tmi.teal_lineno,
+        _TEAL_LINE: lambda tmi: tmi.teal_line,
+        _TEAL_COLUMN: lambda tmi: tmi.teal_column(),
+        _TEAL_COLUMN_END: lambda tmi: tmi.teal_column_end(),
+        _PROGRAM_COUNTERS: lambda tmi: tmi.pcs_repr(),
+        _PYTEAL_HYBRID_UNPARSED: lambda tmi: tmi.hybrid_unparsed(),
+        _PYTEAL_NODE_AST_UNPARSED: lambda tmi: tmi.node_source(),
+        _PYTEAL_NODE_AST_QUALNAME: lambda tmi: tmi.code_qualname(),
+        _PYTEAL_COMPONENT: lambda tmi: tmi.teal_component,
+        _PYTEAL_NODE_AST_SOURCE_BOUNDARIES: lambda tmi: tmi.node_source_window(),
+        _PYTEAL_FILENAME: lambda tmi: tmi.file(),
+        _PYTEAL_LINE_NUMBER: lambda tmi: tmi.lineno(),
+        _PYTEAL_LINE_NUMBER_END: lambda tmi: tmi.node_end_lineno(),
+        _PYTEAL_COLUMN: lambda tmi: tmi.column(),
+        _PYTEAL_COLUMN_END: lambda tmi: tmi.node_end_col_offset(),
+        _PYTEAL_LINE: lambda tmi: tmi.raw_code(),
+        _PYTEAL_NODE_AST: lambda tmi: tmi.node,
+        _PYTEAL_NODE_AST_NONE: lambda tmi: tmi.failed_ast(),
+        _STATUS_CODE: lambda tmi: tmi.status_code(),
+        _STATUS: lambda tmi: tmi.status(),
+    }
+
+    def asdict(self, **kwargs) -> OrderedDict[str, Any]:
+        """kwargs serve as a rename mapping when present"""
+        assert (
+            kwargs.keys() <= (attrs := self._dict_lazy_attrs).keys()
+        ), f"unrecognized parameters {kwargs.keys() - attrs.keys()}"
+
+        return OrderedDict(((kwargs[k], attrs[k](self)) for k in kwargs))
+
+    def validate_for_export(self) -> None:
+        """
+        Ensure providing necessary and unambiguous data before exporting.
+        """
+        if self.teal_lineno is None:
+            raise ValueError("unable to export without valid target TEAL line number")
+        if self.lineno() is None:
+            raise ValueError("unable to export without valid target PyTEAL line number")
+
+    def source_mapping(self) -> "R3SourceMapping":
+        self.validate_for_export()
+        return R3SourceMapping(
+            line=cast(int, self.teal_lineno) - 1,
+            column=self.teal_column(),
+            column_end=self.teal_column_end(),
+            source=self.file(),
+            source_line=cast(int, self.lineno()) - 1,
+            source_column=self.column(),
+            source_line_end=nel - 1 if (nel := self.node_end_lineno()) else None,
+            source_column_end=self.node_end_col_offset(),
+            source_extract=self.hybrid_unparsed(),
+            target_extract=self.teal_line,
+        )
+
+
+@dataclass(frozen=True)
+class PyTealSourceMap:
+    """
+    Encapsulate Expr-less source mapping data.
+    NOTE: type `PCSourceMap` is an alias for algosdk.source_map.SourceMap
+    """
+
+    teal_filename: str | None
+    r3_sourcemap: R3SourceMap | None
+    pc_sourcemap: PCSourceMap | None
+    annotated_teal: str | None
+
+
+class _PyTealSourceMapper:
+    """
+    _PyTealSourceMapper is the workhorse class that runs the sourcemapping algorithm.
+
+    User-safe Expr-less artifacts are provided by methods:
+    * get_sourcemap()
+    * as_r3sourcemap()
+    * annotated_teal()
+
+    Source Mapping Algorithm:
+
+    INPUT: (teal_chunks, components) - these are present in the output object of
+        pyteal.compiler.compiler.Copmilation._compile_impl()
+
+    OUTPUT: (self._cached_tmis, _cached_r3sourcemap, self._cached_pc_sourcemap)
+
+    PASS I. Deduce the Best Frame Candidate (BFC) from each individual `NatalStackFrame`
+
+    NOTE: this logic actually occurs at creation of the PyTeal Expr which is responsible
+    for each teal component. The `build()` method simply retrieves the result of this
+    pre-computation through NatalStackFrame.best()
+
+    for each component in components:
+        # Deduce the "best" frame for the component's PyTeal source (@ Expr creation)
+
+        # [1-7] Inside NatalStackFrame.__init__()
+        1. call inspect.stack() to generate a full stack trace
+        2. filter out "py crud" frames whose filename starts with "<" and don't have a code_context
+        3. start searching at frame index i = 2 (right before Expr's NatalStackFrame was constructed)
+        4. fast forward through PyTeal frames until the first non PyTeal frame is found
+        5. in the case this was in import statement, back up until a known compiler generated line is discovered
+        6. keep the last frame in the list
+        7. convert this into a list[StackFrame] of size 1
+
+        # [8] Inside _PyTealSourceMapper.build() @ source-map creation:
+        8. self._best_frames[i] = the singleton StackFrame in 7 converted to PyTealFrame  # i == component's index of the component
+
+    PASS II. Attempt to fill any "gaps" by inferring from adjacent BFC's
+    This logic is contained in _PyTealSourceMapper.infer():
+
+    NOTE: the mutations of self._best_framed described below are "sticky" in the sense
+    that when this `pyteal_frame` is modified and referenced by `prev_pyteal_frame`
+    in the next interation, it is the new _modified_ version that is used.
+
+    for each pyteal_frame in self._best_frames:
+        status = pyteal_frame.status()
+        if status NOT in PyTealFrameStatus.{MISSING, MISSING_AST, MISSING_CODE, PYTEAL_GENERATED}
+            NO-OP # we can't do any better
+
+        else: # call local method _PyTealSourceMapper.infer.infer_source()
+            prev_pyteal_frame, next_pyteal_frame <--- get these from self._best_frames (or None if doesn't exist)
+            if both exist:
+                if both are equal (have the equal `frame_info, node, rel_paths`):
+                    replace this pyteal_frame by next_pyteal_frame
+
+                if pyteal_frame is flagged as compiler generated:
+                    if its reason is one of PT_GENERATED.{TYPE_ENUM_ONCOMPLETE, TYPE_ENUM_TXN, BRANCH_LABEL, BRANCH}:
+                        # these occur when one of the # T2PT* comments is encountered
+                        replace this pyteal_frame by next_pyteal_frame
+                    if its reason is PT_GENERATED.FLAGGED_BY_DEV:
+                        # this occurs when NatalStackFrame._compiler_gen is set True
+                        replace this pyteal_frame by prev_pyteal_frame
+
+            if ONLY prev_frame exists:
+                replace this pyteal_frame by prev_pyteal_frame
+
+            # NOTE: Coverage shows we never get past here.
+            # I.e. the first frame never results in one of the PyTealFrameStatus'es listed above
+            if ONLY next_frame exists:
+                replace this pyteal_frame by next_pyteal_frame
+
+            if NEITHER next_frame NOR pyteal_frame exist:
+                # this is basically impossible
+                NO-OP # we can't do any better
+
+    """
+
+    UNEXPECTED_ERROR_SUFFIX = """This is an unexpected error. Please report this to the PyTeal team or create an issue at:
+    https://github.com/algorand/pyteal/issues"""
+
+    def __init__(
+        self,
+        teal_chunks: list[str],
+        components: list["pt.TealComponent"],
+        *,
+        teal_filename: str | None = None,
+        include_pcs: bool = False,
+        algod: AlgodClient | None = None,
+        build: bool = True,
+        verbose: bool = False,
+        annotate_teal: bool = False,
+        annotate_teal_headers: bool = False,
+        annotate_teal_concise: bool = True,
+    ):
+        """
+        Args:
+            teal_chunks: strings -each possibly multi-line- which represent a chunk
+                of TEAL code generated by the PyTeal compiler
+            components: TealComponent object in 1-to-1 correspondence with
+                `teal_chunks` and which generated the chunk in the PyTeal compiler
+            teal_filename (optional): filename of TEAL source to be used in source mapping.
+                This file isn't actually saved
+            include_pcs (optional): specifies whether program counters
+                should be included in the map
+            algod (optional): when `include_pcs == True`, an algod client is required
+                and calls the compile endpoint in order to retrieve the PC's.
+                In the case `include_pcs == True` but `algod` isn't provided, an algod
+                client will be bootstrapped via pyteal.util.algod_with_assertion
+            build (default=True): when True, building the sourcemap occurs at initialization
+            verbose (default=False): when True, more debugging information will be logged
+            annotate_teal (default=False): when True, a TEAL file will be provided with
+                annotated comments that give PyTeal (and optionally PC) source information
+            annotate_teal_headers (default=False): when True, an extra line will be added
+                to the top of the annotated TEAL file to indicate each annotation column
+            annotate_teal_concise (default=True): when False, additional columns will be added
+                to the annotated TEAL file
+        """
+        if include_pcs:
+            # bootstrap an algod_client if not provided, and in either case, run a healthcheck
+            algod = algod_with_assertion(
+                algod, msg="Adding PC's to sourcemap requires live Algod"
+            )
+
+        if not teal_chunks:
+            raise TealInternalError("Please provide non-empty teal_chunks")
+
+        if not components:
+            raise TealInternalError("Please provide non-empty components")
+
+        self.teal_chunks: Final[list[str]] = teal_chunks
+        self.components: Final[list["pt.TealComponent"]] = components
+
+        self.algod: AlgodClient | None = algod
+
+        self.include_pcs: bool = include_pcs
+
+        self.teal_filename: str | None = teal_filename
+        self.verbose: bool = verbose
+
+        self._best_frames: list[PyTealFrame] = []
+        self._cached_r3sourcemap: R3SourceMap | None = None
+
+        self._cached_tmis: list[TealMapItem] = []
+        self._cached_pc_sourcemap: PCSourceMap | None = None
+
+        self._most_recent_omit_headers: bool | None = None
+
+        # FOR DEBUGGING PURPOSES ONLY:
+        self._inferred_frames_at: list[int] = []
+
+        if annotate_teal or build:
+            self.build()
+
+        self._annotated_teal: str | None = None
+        if annotate_teal:
+            self._annotated_teal = self.annotated_teal(
+                omit_headers=(oh := not annotate_teal_headers),
+                concise=annotate_teal_concise,
+            )
+            self._most_recent_omit_headers = oh
+
+    def get_sourcemap(self, teal_for_validation: str) -> PyTealSourceMap:
+        if not self._built():
+            raise self._unexpected_error("source map not built yet")
+
+        if self._annotated_teal:
+            if (oh := self._most_recent_omit_headers) is None:
+                raise self._unexpected_error(
+                    "_most_recent_omit_headers is None unexpectedly after calculating annotated_teal"
+                )
+
+            self._validate_annotated(
+                oh, teal_for_validation.splitlines(), self._annotated_teal.splitlines()
+            )
+
+        return PyTealSourceMap(
+            self.teal_filename,
+            self._cached_r3sourcemap,
+            self._cached_pc_sourcemap,
+            self._annotated_teal,
+        )
+
+    def compiled_teal(self) -> str:
+        return "\n".join(self.teal_chunks)
+
+    def _built(self) -> bool:
+        """
+        If any portion of source map is missing, re-build it from scratch
+        """
+        return all(
+            [
+                not self.include_pcs or self._cached_pc_sourcemap,
+                self._cached_r3sourcemap,
+                self._cached_tmis,
+            ]
+        )
+
+    @classmethod
+    def _unexpected_error(cls, msg: str) -> TealInternalError:
+        return TealInternalError(
+            f"""{msg}
+        {cls.UNEXPECTED_ERROR_SUFFIX}"""
+        )
+
+    def build(self) -> None:
+        if self._built():
+            return
+
+        if self.include_pcs:
+            self._build_pc_sourcemap()
+
+        # Validation
+        # sanity check teal_chunks, but discard them in the rest of the computation
+        # assert teal_chunks <--1-to-1--> components
+
+        if (n := len(self.teal_chunks)) != len(self.components):
+            raise self._unexpected_error(
+                f"expected same number of teal chunks ({n}) and components ({len(self.components)})"
+            )
+
+        if n == 0:
+            raise self._unexpected_error(
+                "cannot generate empty source map: no components"
+            )
+
+        # PASS I. Deduce the Best Frame Candidate (BFC) from each individual `NatalStackFrame`
+        # See NatalStackFrame.__init__() for steps 1-7 which happen when an Expr is created
+        # 8. the list comprehension below converts each element
+        #   FROM: the component's NatalStackFrame._frames : list[StackFrame]
+        #   TO:   a PyTealFrame
+        # overall resulting in a list[PyTealFrame]
+        self._best_frames = [
+            tc.stack_frames().best().as_pyteal_frame() for tc in self.components
+        ]
+
+        if not self._best_frames:
+            raise self._unexpected_error(
+                f"This shouldn't have happened as we already checked! Check again: {len(self.components)=}"
+            )
+
+        # PASS II. Attempt to fill any "gaps" by inferring from adjacent BFC's
+        self._best_frames, inferred = self._infer(self._best_frames)
+        if inferred:
+            self._inferred_frames_at = inferred
+
+        lineno = 1
+        for i, best_frame in enumerate(self._best_frames):
+            teal_chunk = self.teal_chunks[i]
+            for line in teal_chunk.splitlines():
+                pcsm = cast(PCSourceMap, self._cached_pc_sourcemap)
+                pcs = None
+                if self.include_pcs:
+                    pcs = pcsm.line_to_pc.get(lineno - 1, [])
+                self._cached_tmis.append(
+                    TealMapItem(
+                        pt_frame=best_frame,
+                        teal_lineno=lineno,
+                        teal_line=line,  # type: ignore
+                        teal_component=self.components[i],
+                        pcs=pcs,
+                    )
+                )
+                lineno += 1
+
+        self._build_r3sourcemap()
+
+        if not NatalStackFrame._debug:
+            # cf. https://stackoverflow.com/questions/850795/different-ways-of-clearing-lists#answer-44349418
+            self._best_frames *= 0
+            self._inferred_frames_at *= 0
+
+        self._validate_build()
+
+    def _validate_build(self):
+        dechunked = [line for chunk in self.teal_chunks for line in chunk.splitlines()]
+
+        if (ld := len(dechunked)) != (ltmi := len(self._cached_tmis)):
+            raise self._unexpected_error(
+                f"teal chunks has {ld} teal lines which doesn't match the number of cached TealMapItem's ({ltmi})"
+            )
+
+        if (lr3 := len(r3_target_lines := self._cached_r3sourcemap.file_lines)) != ltmi:
+            raise self._unexpected_error(
+                f"there are {ltmi} TealMapItem's which doesn't match the number of file_lines in the cached R3SourceMap ({lr3})"
+            )
+
+        for i, line in enumerate(dechunked):
+            if line != (tmi_line := self._cached_tmis[i].teal_line):
+                raise self._unexpected_error(
+                    f"teal chunk lines don't match TealMapItem's at index {i}. ('{line}' v. '{tmi_line}')"
+                )
+            if tmi_line != (target_line := r3_target_lines[i]):
+                raise self._unexpected_error(
+                    f"TealMapItem's don't match R3SourceMap.file_lines at index {i}. ('{tmi_line}' v. '{target_line}')"
+                )
+
+    def _build_r3sourcemap(self):
+        assert self._cached_tmis, "Unexpected error: no cached TealMapItems found"
+
+        root = self._cached_tmis[0].root()
+        assert all(
+            root == tmi.root() for tmi in self._cached_tmis
+        ), "inconsistent sourceRoot - aborting"
+
+        r3sms = [tmi.source_mapping() for tmi in self._cached_tmis]
+        entries = {(r3sm.line, r3sm.column): r3sm for r3sm in r3sms}
+        lines = [cast(str, r3sm.target_extract) for r3sm in r3sms]
+
+        index_l: list[list[int]] = [[]]
+
+        prev_line = 0
+        for line, col in entries.keys():
+            for _ in range(prev_line, line):
+                index_l.append([])
+            curr = index_l[-1]
+            curr.append(col)
+            prev_line = line
+
+        index: list[tuple[int, ...]] = [tuple(cs) for cs in index_l]
+        sources = []
+        for tmi in self._cached_tmis:
+            if (f := tmi.file()) not in sources:
+                sources.append(f)
+
+        self._cached_r3sourcemap = R3SourceMap(
+            filename=self.teal_filename,
+            source_root=root,
+            entries=entries,
+            index=index,
+            file_lines=lines,
+            source_files=sorted(sources),
+        )
+
+    def _build_pc_sourcemap(self):
+        """
+        Prereq: self.teal_chunks - a Final member
+        """
+        algod = algod_with_assertion(
+            self.algod, msg="Adding PC's to sourcemap requires live Algod"
+        )
+        algod_compilation = algod.compile(self.compiled_teal(), source_map=True)
+        raw_sourcemap = algod_compilation.get("sourcemap")
+        if not raw_sourcemap:
+            raise TealInternalError(
+                f"algod compilation did not return 'sourcemap' as expected. {algod_compilation=}"
+            )
+        self._cached_pc_sourcemap = PCSourceMap(raw_sourcemap)
+
+    def as_list(self) -> list[TealMapItem]:
+        self.build()
+        return self._cached_tmis
+
+    def as_r3sourcemap(self) -> R3SourceMap | None:
+        self.build()
+        return self._cached_r3sourcemap
+
+    @classmethod
+    def _infer(
+        cls, best_frames: list[PyTealFrame]
+    ) -> tuple[list[PyTealFrame], list[int]]:
+        inferred = []
+        frames = list(best_frames)
+        N = len(frames)
+
+        def infer_source(i: int) -> PyTealFrame | None:
+            frame = frames[i]
+            if not frame:
+                return None
+
+            prev_frame = None if i <= 0 else frames[i - 1]
+            next_frame = None if N <= i + 1 else frames[i + 1]
+            if prev_frame and next_frame:
+                if prev_frame == next_frame:
+                    return prev_frame.clone(PyTealFrameStatus.PATCHED_BY_PREV_AND_NEXT)
+
+                # PT Generated TypeEnum's presumably happened because of setting an transaction
+                # field in the next step:
+                reason = frame.compiler_generated_reason()
+                if reason in [
+                    PT_GENERATED.TYPE_ENUM_ONCOMPLETE,
+                    PT_GENERATED.TYPE_ENUM_TXN,
+                    PT_GENERATED.BRANCH_LABEL,
+                    PT_GENERATED.BRANCH,
+                ]:
+                    return next_frame.clone(
+                        PyTealFrameStatus.PATCHED_BY_NEXT_OVERRIDE_PREV
+                    )
+
+                if reason == PT_GENERATED.FLAGGED_BY_DEV:
+                    return prev_frame.clone(
+                        PyTealFrameStatus.PATCHED_BY_PREV_OVERRIDE_NEXT
+                    )
+
+                # NO-OP otherwise:
+                return None
+
+            if prev_frame and frame:
+                return prev_frame.clone(PyTealFrameStatus.PATCHED_BY_PREV)
+
+            # TODO: We never get here because we have no trouble with the #pragma component
+            # Either remove or make it useful
+            if next_frame and frame:
+                return next_frame.clone(PyTealFrameStatus.PATCHED_BY_NEXT)
+
+            return None
+
+        for i in range(N):
+            f = frames[i]
+            if f and f.status_code() <= PyTealFrameStatus.PYTEAL_GENERATED:
+                ptf_or_none = infer_source(i)
+                if ptf_or_none:
+                    inferred.append(i)
+                    frames[i] = ptf_or_none
+
+        return frames, inferred
+
+    def pure_teal(self) -> str:
+        return "\n".join(tmi.teal_line for tmi in self.as_list())
+
+    _tabulate_param_defaults: Final[dict[str, str]] = dict(
+        teal=_TEAL_LINE,
+        pyteal_hybrid_unparsed=_PYTEAL_HYBRID_UNPARSED,
+        pyteal=_PYTEAL_NODE_AST_UNPARSED,
+        teal_line_number=_TEAL_LINE_NUMBER,
+        teal_column=_TEAL_COLUMN,
+        teal_column_end=_TEAL_COLUMN_END,
+        program_counters=_PROGRAM_COUNTERS,
+        pyteal_component=_PYTEAL_COMPONENT,
+        pyteal_node_ast_qualname=_PYTEAL_NODE_AST_QUALNAME,
+        pyteal_filename=_PYTEAL_FILENAME,
+        pyteal_line_number=_PYTEAL_LINE_NUMBER,
+        pyteal_line_number_end=_PYTEAL_LINE_NUMBER_END,
+        pyteal_column=_PYTEAL_COLUMN,
+        pyteal_column_end=_PYTEAL_COLUMN_END,
+        pyteal_line=_PYTEAL_LINE,
+        pyteal_node_ast_source_boundaries=_PYTEAL_NODE_AST_SOURCE_BOUNDARIES,
+        pyteal_node_ast_none=_PYTEAL_NODE_AST_NONE,
+        status_code=_STATUS_CODE,
+        status=_STATUS,
+    )
+
+    def tabulate(
+        self,
+        *,
+        tablefmt="fancy_grid",
+        numalign="right",
+        omit_headers: bool = False,
+        omit_repeating_col_except: list[str] | None = None,
+        post_process_delete_cols: list[str] | None = None,
+        **kwargs: dict[str, str],
+    ) -> str:
+        """
+        Tabulate a sourcemap using Python's tabulate package: https://pypi.org/project/tabulate/
+
+        Columns are named and ordered by the arguments provided
+
+        Args:
+            tablefmt (default 'fancy_grid'): format specifier used by tabulate.
+                For choices see: https://github.com/astanin/python-tabulate#table-format
+
+            numalign (default 'right'): alignment of numbers. Choices are 'left', 'right', 'decimal', 'center' or None.
+                See: https://github.com/astanin/python-tabulate#column-alignment
+
+            omit_headers (default `False`): Do not include the column headers when `True`
+
+            omit_repeating_col_except (default None): specify columns for which repetitions should be printed out.
+                The Teal source column and constant columns such as the comment "//" column are always repeated regardless of this setting
+
+            post_process_delete_cols (default None): Specify columns to delete after tabulation
+
+            **kwargs: Additional keyword arguments are passed to tabulate to represent desired columns.
+                The order of these columns as arguments determines the column order. These MUST conform to the following parameters:
+
+                teal (required): Teal target source code. This is the only mandatory column
+
+                const_col_[.*] (optional): specify any number of columns to be treated as constant and always repeated
+
+                pyteal_hybrid_unparsed (optional): PyTeal source via `executing.unparse` when available, or otherwise
+                    via `FrameInfo.code_condext`
+
+                pyteal (optional): PyTeal source via `executing.unparse` when available, or otherwise "" (empty string)
+
+                teal_line_number (optional): Teal target's line number (1-based)
+
+                teal_column (optional): Teal target's 0-indexed starting column (CURRENTLY THIS IS ALWAYS 0)
+
+                teal_column_end (optional): Teal target's 0-indexed right boundary column (CURRENTLY THIS IS len(teal))
+
+                program_counters (optional): starting program counter as assembled by algod
+
+                pyteal_component (optional): representation of the PyTeal source component mapping to target
+
+                pyteal_node_ast_qualname (optional): the Python qualname of the PyTeal source
+
+                pyteal_filename (optional): the filename of the PyTeal source
+
+                pyteal_line_number (optional): the PyTeal source's beginning line number
+
+                pyteal_line_number_end (optional): the PyTeal source's ending line number
+
+                pyteal_column (optional): the PyTeal source's starting 0-indexed column
+
+                pyteal_column_end (optional): the PyTeal source's ending 0-indexed boundary column
+
+                pyteal_line (optional): the PyTeal source as provided by `FrameInfo.code_context`
+
+                pyteal_node_ast_source_boundaries (optional): formatted representation of the PyTeal source's line and column boundaries. Eg "L17:5-L42:3"
+
+                pyteal_node_ast_none (optional): boolean indicator of whether the AST node was successfully extracted for the PyTeal source
+
+                status_code (optional): `PyTealFrameStatus` int value indicating confidence level for locating the PyTeal source responsible for generated Teal
+
+                status (optional): simlar to `status_code` but with a human readable string representation
+
+        Returns:
+            A ready to print string containing the table information.
+        """
+
+        assert (
+            "teal" in kwargs
+        ), "teal column must be specified, but 'teal' is missing in kwargs"
+
+        # 0. e.g. suppose:
+        #
+        # kwargs == dict(
+        #     teal                      =   "// TEAL",
+        #     const_col_2               =   "//",
+        #     pyteal_filename           =   "PATH",
+        #     pyteal_line_number        =   "LINE",
+        #     const_col_5               =   "|",
+        #     pyteal_hybrid_unparsed    =   "PYTEAL",
+        # )
+
+        constant_columns = {}
+        new_kwargs = {}
+        for i, (k, v) in enumerate(kwargs.items()):
+            if k.startswith("const_col_"):
+                constant_columns[i] = v
+            else:
+                new_kwargs[k] = v
+
+        # 1. now we have:
+        #
+        # new_kwargs == dict(
+        #     teal                      =   "// TEAL",
+        #     pyteal_filename           =   "PATH",
+        #     pyteal_line_number        =   "LINE",
+        #     pyteal_hybrid_unparsed    =   "PYTEAL",
+        # )
+        #
+        # and
+        #
+        # constant_columns == {
+        #     1: "//",
+        #     4: "|",
+        # }
+
+        for k in new_kwargs:
+            assert k in self._tabulate_param_defaults, f"unrecognized parameter '{k}'"
+
+        # 2. now we know that all the provided keys were valid
+
+        renames = {self._tabulate_param_defaults[k]: v for k, v in new_kwargs.items()}
+
+        # 3. now we have:
+        #
+        # renames == {
+        #     _TEAL_LINE:                 "// TEAL",
+        #     _PYTEAL_FILENAME:           "PATH",
+        #     _PYTEAL_LINE_NUMBER:        "LINE",
+        #     _PYTEAL_HYBRID_UNPARSED:    "PYTEAL",
+        # }
+
+        rows = [teal_item.asdict(**renames) for teal_item in self.as_list()]
+
+        # 4. now we've populated the rows:
+        #
+        # rows == [ {_TEAL_LINE: 1, _PYTEAL_FILENAME: "foo.py", _PYTEAL_LINE_NUMBER: 79, _PYTEAL_HYBRID_UNPARSED: "Int(42)"}, ... ]
+
+        if constant_columns:
+
+            def add_const_cols(row):
+                i = 0
+                new_row = {}
+                for k, v in row.items():
+                    if i in constant_columns:
+                        new_row[f"_{i}"] = constant_columns[i]
+                        i += 1
+                    new_row[k] = v
+                    i += 1
+                return new_row
+
+            rows = list(map(add_const_cols, rows))
+            # 5. now we've added the constant columns to the rows:
+            #
+            # rows == [ {_TEAL_LINE: 1, "_1": "//", _PYTEAL_FILENAME: "foo.py", _PYTEAL_LINE_NUMBER: 79, "_4": "|", _PYTEAL_HYBRID_UNPARSED: "Int(42)"}, ... ]
+            #                           ^^^^^^^^^^                                                       ^^^^^^^^^
+
+            renames = add_const_cols(renames)
+            # 6. and we've added the constant columns at the required ordering to the renames as well:
+            #
+            # renames == {
+            #     _TEAL_LINE:                 "// TEAL",
+            #     "_1":                       "//",
+            #     _PYTEAL_FILENAME:           "PATH",
+            #     _PYTEAL_LINE_NUMBER:        "LINE",
+            #     "_4":                       "|",
+            #     _PYTEAL_HYBRID_UNPARSED:    "PYTEAL",
+            # }
+
+        teal_col_name = renames[_TEAL_LINE]
+        pt_simple_col_name = renames.get(_PYTEAL_COLUMN)
+        pt_hybrid_col_name = renames.get(_PYTEAL_HYBRID_UNPARSED)
+        pt_window_col_name = renames.get(_PYTEAL_NODE_AST_SOURCE_BOUNDARIES)
+        if omit_repeating_col_except:
+            # Assume the following column structure:
+            # * col 0 is the generated source with column name stored in `teal_col`
+            # * the source line number has column name stored in `pyteal_line_number`
+            # * the pyteal source has column name stored in `pyteal` OR `pyteal_hybrid_unparsed`
+            #
+            # Consequently, when `teal_col` is repeating we need to take extra care NOT
+            # to omit repeating source values, as these were likely coming from different portions of the source
+
+            def reduction(row, next_row):
+                drop_2nd_pyteal = True
+                if pt_window_col_name and (
+                    row[pt_window_col_name] or next_row[pt_window_col_name]
+                ):
+                    drop_2nd_pyteal = (
+                        row[pt_window_col_name] == next_row[pt_window_col_name]
+                    )
+                else:
+                    drop_2nd_pyteal = row[teal_col_name] != next_row[teal_col_name]
+                return {
+                    k: v2
+                    for k, v in row.items()
+                    if any(
+                        [
+                            (v2 := next_row[k]) != v,
+                            k in omit_repeating_col_except,
+                            k in (pt_hybrid_col_name, pt_simple_col_name)
+                            and not drop_2nd_pyteal,
+                        ]
+                    )
+                }
+
+            rows = [rows[0]] + list(
+                map(lambda r_and_n: reduction(*r_and_n), zip(rows[:-1], rows[1:]))
+            )
+            # 7. now we've removed repetitions of appropriate columns
+
+        if post_process_delete_cols:
+            for col in post_process_delete_cols:
+                col_name = renames.pop(col)
+                for row in rows:
+                    if col_name in row:
+                        del row[col_name]  # type: ignore
+        # 8. now we've removed any columns requested for deletion
+
+        calling_kwargs: dict[str, Any] = {"tablefmt": tablefmt, "numalign": numalign}
+        if not omit_headers:
+            calling_kwargs["headers"] = renames
+
+        return tabulate(rows, **calling_kwargs)
+
+    def annotated_teal(self, omit_headers: bool = True, concise: bool = True) -> str:
+        """
+        Helper function that hardcodes various tabulate parameters to produce a
+        reasonably formatted annotated teal output.
+
+        In theory, the output can be compiled.
+
+        In practice the output maybe be very large and therefore unacceptable to Algod.
+
+        In such cases, you should use the original accompanying Teal compilation.
+        """
+        if not self._built():
+            raise ValueError(
+                "not ready for annotated_teal() because build() has yet to be called"
+            )
+
+        if not (r3sm := cast(R3SourceMap, self._cached_r3sourcemap)):
+            raise self._unexpected_error("R3SourceMap not available but should be")
+
+        if not (file_lines := cast(list[str], r3sm.file_lines)):
+            raise self._unexpected_error(
+                "_cached_r3sourcemap.file_lines not available but should be"
+            )
+
+        teal_col = "// GENERATED TEAL"
+        comment_col = "_1"
+        kwargs = dict(
+            tablefmt="plain",
+            omit_headers=omit_headers,
+            omit_repeating_col_except=[teal_col, comment_col],
+            numalign="left",
+            teal=teal_col,
+            const_col_2="//",
+        )
+
+        if self.include_pcs:
+            kwargs["program_counters"] = "PC"
+
+        if not concise:
+            kwargs["pyteal_filename"] = "PYTEAL PATH"
+            kwargs["pyteal_line_number"] = "LINE"
+
+        kwargs["pyteal_hybrid_unparsed"] = "PYTEAL"
+        kwargs["pyteal_node_ast_source_boundaries"] = "PYTEAL RANGE"
+
+        kwargs["post_process_delete_cols"] = [_PYTEAL_NODE_AST_SOURCE_BOUNDARIES]
+        annotated = self.tabulate(**kwargs)  # type: ignore
+
+        self._validate_annotated(omit_headers, file_lines, annotated.splitlines())
+
+        return annotated
+
+    @classmethod
+    def _validate_annotated(
+        cls, omit_headers: bool, teal_lines: list[str], annotated_lines: list[str]
+    ):
+        header_delta = 1 - bool(omit_headers)
+        if (ltl := len(teal_lines)) + header_delta != (latl := len(annotated_lines)):
+            raise cls._unexpected_error(
+                f"mismatch between count of teal_lines ({ltl}) and annotated_lines ({latl}) for the case {omit_headers=}",
+            )
+
+        for i, (teal_line, annotated_line) in enumerate(
+            zip(teal_lines, annotated_lines[header_delta:])
+        ):
+            if not annotated_line.startswith(teal_line):
+                raise cls._unexpected_error(
+                    f"annotated teal ought to begin exactly with the teal line but line {i+1} [{annotated_line}] doesn't start with [{teal_line}]",
+                )
+            pattern = r"^\s*($|//.*)"
+            if not re.match(pattern, annotated_line[len(teal_line) :]):
+                raise cls._unexpected_error(
+                    f"annotated teal ought to begin exactly with the teal line followed by annotation in comments but line {i+1} [{annotated_line}] has non-commented out annotations"
+                )
+
+    @classmethod
+    def _validate_teal_identical(
+        cls,
+        original_teal: str,
+        new_teal: str,
+        msg: str,
+    ):
+        if original_teal == new_teal:
+            return
+
+        diff = list(unified_diff(original_teal.splitlines(), new_teal.splitlines()))
+        raise cls._unexpected_error(
+            f"""{msg}. Original teal differs with new: 
+    {''.join(diff)}"""
+        )
diff --git a/pyteal/errors.py b/pyteal/errors.py
index 121df49e1..4c34ec87e 100644
--- a/pyteal/errors.py
+++ b/pyteal/errors.py
@@ -86,6 +86,31 @@ def __str__(self):
 TealPragmaError.__module__ = "pyteal"
 
 
+class AlgodClientError(Exception):
+    def __init__(self, message: str) -> None:
+        self.message = message
+
+    def __str__(self):
+        return self.message
+
+
+AlgodClientError.__module__ = "pyteal"
+
+
+class SourceMapDisabledError(RuntimeError):
+    msg = value = """
+    Cannot calculate Teal to PyTeal source map because stack frame discovery is turned off.
+
+    To enable source maps, set `enabled = True` in `pyteal.ini`'s [pyteal-source-mapper] section.
+    """
+
+    def __str__(self):
+        return self.msg
+
+
+SourceMapDisabledError.__module__ = "pyteal"
+
+
 def verifyProgramVersion(minVersion: int, version: int, msg: str):
     if minVersion > version:
         msg = "{}. Minimum version needed is {}, but current version being compiled is {}".format(
diff --git a/pyteal/ir/__init__.py b/pyteal/ir/__init__.py
index e7db57cf8..72348f627 100644
--- a/pyteal/ir/__init__.py
+++ b/pyteal/ir/__init__.py
@@ -1,22 +1,23 @@
 from pyteal.ir.ops import Op, Mode
-
+from pyteal.ir.tealblock import TealBlock
 from pyteal.ir.tealcomponent import TealComponent
-from pyteal.ir.tealop import TealOp
+from pyteal.ir.tealconditionalblock import TealConditionalBlock
 from pyteal.ir.teallabel import TealLabel
-from pyteal.ir.tealblock import TealBlock
+from pyteal.ir.tealop import TealOp
+from pyteal.ir.tealpragma import TealPragma
 from pyteal.ir.tealsimpleblock import TealSimpleBlock
-from pyteal.ir.tealconditionalblock import TealConditionalBlock
 
 from pyteal.ir.labelref import LabelReference
 
 __all__ = [
-    "Op",
+    "LabelReference",
     "Mode",
+    "Op",
+    "TealBlock",
     "TealComponent",
-    "TealOp",
+    "TealConditionalBlock",
     "TealLabel",
-    "TealBlock",
+    "TealOp",
+    "TealPragma",
     "TealSimpleBlock",
-    "TealConditionalBlock",
-    "LabelReference",
 ]
diff --git a/pyteal/ir/tealblock.py b/pyteal/ir/tealblock.py
index 8ceed472e..55f3c0a5c 100644
--- a/pyteal/ir/tealblock.py
+++ b/pyteal/ir/tealblock.py
@@ -14,9 +14,10 @@
 class TealBlock(ABC):
     """Represents a basic block of TealComponents in a graph."""
 
-    def __init__(self, ops: List[TealOp]) -> None:
+    def __init__(self, ops: List[TealOp], root_expr: "Expr | None" = None) -> None:
         self.ops = ops
         self.incoming: List[TealBlock] = []
+        self._sframes_container = root_expr
 
     @abstractmethod
     def getOutgoing(self) -> List["TealBlock"]:
diff --git a/pyteal/ir/tealcomponent.py b/pyteal/ir/tealcomponent.py
index 0dc69cd9e..2704e0f87 100644
--- a/pyteal/ir/tealcomponent.py
+++ b/pyteal/ir/tealcomponent.py
@@ -1,14 +1,23 @@
 from abc import ABC, abstractmethod
-from typing import List, Optional, TYPE_CHECKING
 from contextlib import AbstractContextManager
+from typing import TYPE_CHECKING, List, cast
+
+from pyteal.stack_frame import NatalStackFrame
 
 if TYPE_CHECKING:
     from pyteal.ast import Expr, ScratchSlot, SubroutineDefinition
 
 
 class TealComponent(ABC):
-    def __init__(self, expr: Optional["Expr"]):
-        self.expr = expr
+    def __init__(self, expr: "Expr | None"):
+        self.expr: Expr | None = expr
+
+        # ALL BELOW: for source mapping only
+        self._stack_frames: NatalStackFrame | None = None
+        if not self.expr:  # expr already has the frame info
+            self._stack_frames = NatalStackFrame()
+
+        self._sframes_container: Expr | None = None
 
     def getSlots(self) -> List["ScratchSlot"]:
         return []
@@ -22,6 +31,17 @@ def getSubroutines(self) -> List["SubroutineDefinition"]:
     def resolveSubroutine(self, subroutine: "SubroutineDefinition", label: str) -> None:
         pass
 
+    def stack_frames(self) -> NatalStackFrame:
+        from pyteal.ast import Expr
+
+        root_expr = self._sframes_container or self.expr
+        if root_expr:
+            if subroot := getattr(root_expr, "_sframes_container", None):
+                root_expr = cast(Expr, subroot)
+            return root_expr.stack_frames
+
+        return cast(NatalStackFrame, self._stack_frames)
+
     @abstractmethod
     def assemble(self) -> str:
         pass
diff --git a/pyteal/ir/tealconditionalblock.py b/pyteal/ir/tealconditionalblock.py
index d4056fef3..42e5ec2c7 100644
--- a/pyteal/ir/tealconditionalblock.py
+++ b/pyteal/ir/tealconditionalblock.py
@@ -7,8 +7,8 @@
 class TealConditionalBlock(TealBlock):
     """Represents a basic block of TealComponents in a graph ending with a branch condition."""
 
-    def __init__(self, ops: List[TealOp]) -> None:
-        super().__init__(ops)
+    def __init__(self, ops: List[TealOp], root_expr: "Expr | None" = None) -> None:  # type: ignore
+        super().__init__(ops, root_expr=root_expr)
         self.trueBlock: TealBlock | None = None
         self.falseBlock: TealBlock | None = None
 
@@ -35,10 +35,11 @@ def replaceOutgoing(self, oldBlock: TealBlock, newBlock: TealBlock) -> None:
             self.falseBlock = newBlock
 
     def __repr__(self) -> str:
-        return "TealConditionalBlock({}, true={}, false={})".format(
+        return "TealConditionalBlock({}, true={}, false={}, conditional={})".format(
             repr(self.ops),
             repr(self.trueBlock),
             repr(self.falseBlock),
+            repr(self._sframes_container),
         )
 
     def __eq__(self, other: object) -> bool:
diff --git a/pyteal/ir/tealpragma.py b/pyteal/ir/tealpragma.py
new file mode 100644
index 000000000..4a0ff5d46
--- /dev/null
+++ b/pyteal/ir/tealpragma.py
@@ -0,0 +1,24 @@
+from pyteal.ir.tealcomponent import TealComponent
+
+
+class TealPragma(TealComponent):
+    def __init__(self, version: int):
+        super().__init__(None)
+        self.version = version
+
+    def assemble(self) -> str:
+        return f"#pragma version {self.version}"
+
+    def __repr__(self) -> str:
+        return f"TealPragma({self.version})"
+
+    def __hash__(self) -> int:
+        return hash(repr(self))
+
+    def __eq__(self, other: object) -> bool:
+        if not isinstance(other, TealPragma):
+            return False
+        return self.version == other.version
+
+
+TealPragma.__module__ = "pyteal"
diff --git a/pyteal/ir/tealsimpleblock.py b/pyteal/ir/tealsimpleblock.py
index e59c74377..18ef7e217 100644
--- a/pyteal/ir/tealsimpleblock.py
+++ b/pyteal/ir/tealsimpleblock.py
@@ -1,22 +1,20 @@
-from typing import Optional, List
-
-from pyteal.ir.tealop import TealOp
 from pyteal.ir.tealblock import TealBlock
+from pyteal.ir.tealop import TealOp
 
 
 class TealSimpleBlock(TealBlock):
     """Represents a basic block of TealComponents in a graph that does not contain a branch condition."""
 
-    def __init__(self, ops: List[TealOp]) -> None:
+    def __init__(self, ops: list[TealOp]) -> None:
         super().__init__(ops)
-        self.nextBlock: Optional[TealBlock] = None
+        self.nextBlock: TealBlock | None = None
         self.visited = False
 
     def setNextBlock(self, block: TealBlock) -> None:
         """Set the block that follows this one."""
         self.nextBlock = block
 
-    def getOutgoing(self) -> List[TealBlock]:
+    def getOutgoing(self) -> list[TealBlock]:
         if self.nextBlock is None:
             return []
         return [self.nextBlock]
diff --git a/pyteal/stack_frame.py b/pyteal/stack_frame.py
new file mode 100644
index 000000000..74fa8469a
--- /dev/null
+++ b/pyteal/stack_frame.py
@@ -0,0 +1,726 @@
+from ast import AST, FunctionDef, unparse
+from configparser import ConfigParser
+from contextlib import contextmanager
+from dataclasses import dataclass
+from enum import IntEnum
+from inspect import FrameInfo, stack
+from typing import Callable, Final, cast
+import os
+import re
+
+from executing import Source  # type: ignore
+
+
+@dataclass(frozen=True)
+class StackFrame:
+    """
+    StackFrame is an _internal_ PyTeal class and is
+    the first ancestor in the following linear class hierarchy:
+                        StackFrame
+                            |
+                        PyTealFrame
+                            |
+    compiler.sourcemap.TealMapItem
+
+    Somewhat confusingly, NatalStackFrame is _NOT_ in this hierarchy
+    so not "is-a" StackFrame, but it pretends to be and "has-a" list of StackFrame's.
+
+    Its most important members are:
+        * frame_info of type inspect.FrameInfo
+        * node of type ast.AST
+    The first is a python representation of a stack frame, and the
+    second represents a python AST node. The imported package `executing`
+    features the method `Source.executing` which when run shortly after
+    the frame_info's creation, _usually_ succeeds in recovering the
+    associated AST node.
+
+    In the current usage, there is a `creator` member which is a `NatalStackFrame`
+    object -usually belonging to a PyTeal Expr- and which is assumed to have called
+    the private constructor `_init_or_drop()`.
+
+    It is not recommended that this class be accessed directly.
+
+    Of special note, is the class variable `_internal_paths`.
+    This is a whitelist of file patterns which signal to the logic
+    of method `_frame_info_is_pyteal()` that a particular
+    frame was _NOT_ created by the user.
+
+    QUIRKS:
+    * Unfortunately, this means that if a user created file satisfies the above pattern,
+    the performance of the source mapper will degrade.
+    * User generated code that includes the pattern `NatalStackFrame` may produce degraded results
+    """
+
+    frame_info: FrameInfo
+    node: AST | None
+    creator: "NatalStackFrame"
+
+    # for debugging purposes:
+    full_stack: list[FrameInfo] | None = None
+
+    @classmethod
+    def _init_or_drop(
+        cls, creator: "NatalStackFrame", f: FrameInfo, full_stack: list[FrameInfo]
+    ) -> "StackFrame | None":
+        """
+        Attempt to create a StackFrame object.
+        However, if the resulting is considered "Python Crud" abandon and return None.
+        When debugging, also persist the full_stack that was provided.
+        """
+        node = cast(AST | None, Source.executing(f.frame).node)
+        frame = StackFrame(
+            f, node, creator, full_stack if NatalStackFrame._debug else None
+        )
+        return frame if frame._not_py_crud() else None
+
+    # TODO: when a source mapper is instantiated, it ought to survey
+    # the user's project files and warn in the case that some file
+    # matches the _internal_paths pattern
+    _internal_paths = [
+        "beaker/__init__.py",
+        "beaker/application.py",
+        "beaker/consts.py",
+        "beaker/decorators.py",
+        "beaker/state.py",
+        "pyteal/__init__.py",
+        "pyteal/ast",
+        "pyteal/compiler",
+        "pyteal/ir",
+        "pyteal/pragma",
+        "pyteal/stack_frame.py",
+        "tests/abi_roundtrip.py",
+        "tests/blackbox.py",
+        "tests/compile_asserts.py",
+        "tests/mock_version.py",
+    ]
+    _internal_paths_re = re.compile("|".join(_internal_paths))
+
+    def as_pyteal_frame(
+        self,
+        rel_paths: bool = True,
+        parent: "PyTealFrame | None" = None,
+    ) -> "PyTealFrame":
+        """
+        Downcast one level in the class hierarchy
+        """
+        return PyTealFrame(
+            frame_info=self.frame_info,
+            node=self.node,
+            creator=self.creator,
+            full_stack=self.full_stack,
+            rel_paths=rel_paths,
+            parent=parent,
+        )
+
+    @classmethod
+    def _frame_info_is_right_before_core(cls, f: FrameInfo) -> bool:
+        # We keep this method around to be used in a test . Originally,
+        # it was actually used in the __init__ method of StackFrame
+        # to calculate `last_drop_idx`. However, as -at the time of writing-
+        # last_drop_idx =ALWAYS= 1, the logic was simplified and
+        # this method was removed from the live calculation.
+        return bool(code := f.code_context or []) and "NatalStackFrame" in "".join(code)
+
+    def _is_pyteal(self) -> bool:
+        return self._frame_info_is_pyteal(self.frame_info)
+
+    @classmethod
+    def _frame_info_is_pyteal(cls, f: FrameInfo) -> bool:
+        return bool(cls._internal_paths_re.search(f.filename))
+
+    def _is_pyteal_import(self) -> bool:
+        return self._frame_info_is_pyteal_import(self.frame_info)
+
+    @classmethod
+    def _frame_info_is_pyteal_import(cls, f: FrameInfo) -> bool:
+        cc = f.code_context
+        if not cc:
+            return False
+
+        code = "".join(cc).split()
+        return "import" in code and "pyteal" in code
+
+    def _not_py_crud(self) -> bool:
+        """Hackery that depends on C-Python. Not sure how reliable."""
+        return self._frame_info_not_py_crud(self.frame_info)
+
+    @classmethod
+    def _frame_info_not_py_crud(cls, f: FrameInfo) -> bool:
+        return bool(f.code_context) or not f.filename.startswith("<")
+
+    def __repr__(self) -> str:
+        node = unparse(n) if (n := self.node) else None
+        context = "".join(cc) if (cc := (fi := self.frame_info).code_context) else None
+        return f"{node=}; {context=}; frame_info={fi}"
+
+    def compiler_generated(self) -> bool | None:
+        if self.creator._compiler_gen:
+            return True
+
+        return self._frame_info_compiler_generated(self.frame_info)
+
+    @classmethod
+    def _frame_info_compiler_generated(cls, f: FrameInfo) -> bool | None:
+        if not (cc := f.code_context):
+            return None  # we don't know / NA
+
+        return "# T2PT" in "".join(cc)
+
+
+def _sourcmapping_is_off(verbose=False) -> bool:
+    try:
+        config = ConfigParser()
+        config.read("pyteal.ini")
+        enabled = config.getboolean("pyteal-source-mapper", "enabled")
+        return not enabled
+    except Exception as e:
+        if verbose:
+            print(
+                f"""Turning off frame capture and disabling sourcemaps. 
+Could not read section (pyteal-source-mapper, enabled) of config "pyteal.ini": {e}"""
+            )
+    return True
+
+
+def _debug_frames(verbose=False) -> bool:
+    try:
+        config = ConfigParser()
+        config.read("pyteal.ini")
+        return config.getboolean("pyteal-source-mapper", "debug")
+    except Exception as e:
+        if verbose:
+            print(
+                f"""Disabling `debug` status for sourcemaps. 
+Could not read section (pyteal-source-mapper, debug) of config "pyteal.ini": {e}"""
+            )
+    return False
+
+
+@contextmanager
+def sourcemapping_off_context():
+    """Context manager that turns off sourcemapping for the duration of the context"""
+    _no_stackframes_before = NatalStackFrame._no_stackframes
+    _debug_before = NatalStackFrame._debug
+    NatalStackFrame._no_stackframes = True
+    NatalStackFrame._debug = False
+
+    try:
+        yield
+
+    finally:
+        NatalStackFrame._no_stackframes = _no_stackframes_before
+        NatalStackFrame._debug = _debug_before
+
+
+class NatalStackFrame:
+    """
+    PyTeal's source mapper deduces the code-location of a user's Expr
+    via a NatalStackFrame object that is associated with the Expr object.
+
+    When source mapping is disabled (cf. `pyteal.ini`), NatalStackFrame'
+    constructor is a no-op.
+
+    When source mapping is enabled, it wraps a list of StackFrame's.
+
+    Under normal operations, only the "best" frame is kept in the list, so
+    the name is misleading.
+    """
+
+    _no_stackframes: bool = _sourcmapping_is_off()
+    _debug: bool = _debug_frames()
+    _keep_all_debugging = False
+
+    @classmethod
+    def sourcemapping_is_off(cls, _force_refresh: bool = False) -> bool:
+        """
+        The `_force_refresh` parameter, is mainly for test validation purposes.
+        It is discouraged for use in the wild because:
+        * Frames are useful in an "all or nothing" capacity. For example, in preparing
+            for a source mapping, it would be error prone to generate frames for
+            a subset of analyzed PyTeal
+        * Setting `_force_refresh = True` will cause a read from the file system every
+            time Frames are initialized and will result in significant performance degredation
+        """
+        if _force_refresh:
+            cls._no_stackframes = _sourcmapping_is_off()
+
+        return cls._no_stackframes
+
+    def __init__(
+        self,
+    ):
+        self._compiler_gen: bool = False
+        self._frames: list[StackFrame] = []
+
+        if self.sourcemapping_is_off():
+            return
+
+        # 1. get the full stack trace
+        full_stack = stack()
+
+        # 2. discard frames whose filename begins with "<"
+        frame_infos = list(filter(StackFrame._frame_info_not_py_crud, full_stack))
+
+        def _make_stack_frames(fis):
+            return [
+                frame
+                for f in fis
+                if (frame := StackFrame._init_or_drop(self, f, full_stack))
+            ]
+
+        if self._keep_all_debugging or len(frame_infos) <= 1:
+            self._frames = _make_stack_frames(frame_infos)
+            return
+
+        # 3. start the best frame search right after where NatalStackFrame() was constructed
+        # For more details see the unit test:
+        # tests/unit/sourcemap_monkey_unit_test.py::test_frame_info_is_right_before_core_last_drop_idx
+        i = 2  # formerly this was `last_drop_idx = 1; i = last_drop_idx + 1`
+
+        # 4. fast forward the right bound until we're out of pyteal-library code
+        # This sets last_keep_idx to the first frame index which isn't pyteal
+        while i < len(frame_infos) and StackFrame._frame_info_is_pyteal(frame_infos[i]):
+            i += 1
+        last_keep_idx = i
+
+        # 5. if the pyteal-library exit point was an import, the expression was
+        # generated by pyteal itself. So let's back up and look for a "# T2PT*" comment
+        # which will give us a clue for what to do with this expression
+        if StackFrame._frame_info_is_pyteal_import(frame_infos[last_keep_idx]):
+            found = False
+            i = -1
+            for i in range(last_keep_idx - 1, -1, -1):
+                if StackFrame._frame_info_compiler_generated(frame_infos[i]):
+                    found = True
+                    break
+
+            if found and i >= 0:
+                last_keep_idx = i
+
+        # 6. Keep only the last frame in the list. We maintain _as_ a list
+        # since in the case of `self._debug == True`, we'd like access to the full list.
+        # TODO: this is likely obsolete since full_stack is available on the PyTealFrame object when debugging
+        frame_infos = frame_infos[last_keep_idx : last_keep_idx + 1]
+
+        # 7. we finish by constructing a list[StackFrame] from our one remaining frame_info
+        self._frames = _make_stack_frames(frame_infos)
+
+    def __len__(self) -> int:
+        return len(self._frames)
+
+    def best(self) -> StackFrame:
+        """
+        Return the best guess as to the user-authored birthplace of the
+        associated StackFrame's
+        """
+        assert (
+            self._frames
+        ), f"expected to have some frames but currently {self._frames=}"
+        return self._frames[-1]
+
+    def __repr__(self) -> str:
+        return f"{'C' if self._compiler_gen else 'U'}{self._frames}"
+
+    def nodes(self) -> list[AST | None]:
+        return [f.node for f in self._frames]
+
+    @classmethod
+    def _walk_asts(cls, func: Callable[["Expr"], None], *exprs: "Expr") -> None:  # type: ignore
+        from pyteal.ast import (
+            Assert,
+            BinaryExpr,
+            Cond,
+            Expr,
+            Return,
+            Seq,
+            SubroutineDeclaration,
+        )
+        from pyteal.ast.frame import Proto
+
+        for expr in exprs:
+            e = cast(Expr, expr)
+            func(e)
+
+            match e:
+                case Assert():
+                    cls._walk_asts(func, *e.cond)
+                case BinaryExpr():
+                    cls._walk_asts(func, e.argLeft, e.argRight)
+                case Cond():
+                    cls._walk_asts(func, *(y for x in e.args for y in x))
+                case Proto():
+                    cls._walk_asts(func, e.mem_layout)
+                case Seq():
+                    cls._walk_asts(func, *e.args)
+                case Return():
+                    cls._walk_asts(func, e.value)
+                case SubroutineDeclaration():
+                    cls._walk_asts(func, e.body)
+                case _:
+                    # TODO: implement more cases, but no need to error as this isn't used for functionality's sake.
+                    pass
+
+    @classmethod
+    def _debug_asts(cls, *exprs: "Expr") -> None:  # type: ignore
+        """
+        For deubgging purposes only!
+        """
+        from pyteal.ast import Expr
+
+        if cls.sourcemapping_is_off():
+            return
+
+        def dbg(e: Expr):
+            print(
+                type(e), ": ", e.stack_frames.best().as_pyteal_frame().hybrid_unparsed()
+            )
+
+        cls._walk_asts(dbg, *exprs)
+
+    @classmethod
+    def mark_asts_as_compiler_gen(cls, *exprs: "Expr") -> None:  # type: ignore
+        from pyteal.ast import Expr
+
+        if cls.sourcemapping_is_off():
+            return
+
+        def mark(e: Expr):
+            e.stack_frames._compiler_gen = True
+
+        cls._walk_asts(mark, *exprs)
+
+    @classmethod
+    def reframe_asts(cls, stack_frames: "NatalStackFrame", *exprs: "Expr") -> None:  # type: ignore
+        from pyteal.ast import Expr
+
+        if cls.sourcemapping_is_off():
+            return
+
+        def set_frames(e: Expr):
+            e.stack_frames = stack_frames
+
+        cls._walk_asts(set_frames, *exprs)
+
+    @classmethod
+    def reframe_ops_in_blocks(cls, root_expr: "Expr", start: "TealBlock") -> None:  # type: ignore
+        start._sframes_container = root_expr
+        for op in start.ops:
+            op._sframes_container = root_expr
+
+        if nxt := start.nextBlock:
+            cls.reframe_ops_in_blocks(root_expr, nxt)
+
+
+class PT_GENERATED:
+    PRAGMA = "PyTeal generated pragma"
+    SUBR_LABEL = "PyTeal generated subroutine label"
+    RETURN_NONE = "PyTeal generated return for TealType.none"
+    RETURN_VALUE = "PyTeal generated return for non-null TealType"
+    SUBR_PARAM = "PyTeal generated subroutine parameter handler instruction"
+    BRANCH = "PyTeal generated branching"
+    BRANCH_LABEL = "PyTeal generated branching label"
+    TYPE_ENUM_TXN = "PyTeal generated transaction Type Enum"
+    TYPE_ENUM_ONCOMPLETE = "PyTeal generated OnComplete Type Enum"
+    FLAGGED_BY_DEV = "Developer has flagged expression as compiler generated"
+
+
+_PT_GEN = {
+    "# T2PT0": PT_GENERATED.PRAGMA,
+    "# T2PT1": PT_GENERATED.SUBR_LABEL,
+    "# T2PT2": PT_GENERATED.RETURN_NONE,
+    "# T2PT3": PT_GENERATED.RETURN_VALUE,
+    "# T2PT4": PT_GENERATED.SUBR_PARAM,
+    "# T2PT5": PT_GENERATED.BRANCH,
+    "# T2PT6": PT_GENERATED.BRANCH_LABEL,
+    "# T2PT7": PT_GENERATED.TYPE_ENUM_TXN,
+    "# T2PT8": PT_GENERATED.TYPE_ENUM_ONCOMPLETE,
+}
+
+
+class PyTealFrameStatus(IntEnum):
+    """integer values indicate 'confidence' on a scale of 0 - 10"""
+
+    MISSING = 0
+    MISSING_AST = 1
+    MISSING_CODE = 2
+    PYTEAL_GENERATED = 3
+    PATCHED_BY_PREV_OVERRIDE_NEXT = 4
+    PATCHED_BY_NEXT_OVERRIDE_PREV = 5
+    PATCHED_BY_PREV = 6
+    PATCHED_BY_NEXT = 7
+    PATCHED_BY_PREV_AND_NEXT = 8
+    COPACETIC = 9  # currently, 90% confident is as good as it gets
+
+    def human(self) -> str:
+        match self:
+            case self.MISSING:
+                return "sourcemapping line: total failure"
+            case self.MISSING_AST:
+                return "unreliable as missing AST"
+            case self.MISSING_CODE:
+                return "unreliable as couldn't retrieve source"
+            case self.PYTEAL_GENERATED:
+                return "INCOMPLETE"  # "incomplete as source not user generated"
+            case self.PATCHED_BY_PREV_OVERRIDE_NEXT:
+                return "previously INCOMPLETE- patched to previous frame (ambiguous)"
+            case self.PATCHED_BY_NEXT_OVERRIDE_PREV:
+                return "previously INCOMPLETE- patched to next frame (ambiguous)"
+            case self.PATCHED_BY_PREV:
+                return "previously INCOMPLETE- patched to previous frame"
+            case self.PATCHED_BY_NEXT:
+                return "previously INCOMPLETE- patched to next frame"
+            case self.PATCHED_BY_PREV_AND_NEXT:
+                return "previously INCOMPLETE- patched"
+            case self.COPACETIC:
+                return "COPACETIC"
+
+        raise Exception(f"unrecognized {type(self)} - THIS SHOULD NEVER HAPPEN!")
+
+
+class PyTealFrame(StackFrame):
+    """
+    PyTealFrame is the middle generation in the StackFrame class hierarchy.
+
+    It adds a richer and more polished set of methods to learn about the PyTeal source.
+    """
+
+    def __init__(
+        self,
+        frame_info: FrameInfo,
+        node: AST | None,
+        creator: NatalStackFrame,
+        full_stack: list[FrameInfo] | None,
+        rel_paths: bool = True,
+        parent: "PyTealFrame | None" = None,
+    ):
+        super().__init__(frame_info, node, creator, full_stack)
+        self.rel_paths: Final[bool] = rel_paths
+        self.parent: "Final[PyTealFrame | None]" = parent
+
+        self._raw_code: str | None = None
+        self._status: PyTealFrameStatus | None = None
+
+    def __repr__(self) -> str:
+        """
+        This repr isn't compliant, but keeping as it's useful for debugging in VSCode
+        """
+        return self._str_impl(verbose=False)
+
+    def __eq__(self, other: object) -> bool:
+        """We don't care about parents here. TODO: this comment is too rude"""
+        if not isinstance(other, PyTealFrame):
+            return False
+
+        return all(
+            [
+                self.frame_info == other.frame_info,
+                self.node == other.node,
+                self.rel_paths == other.rel_paths,
+            ]
+        )
+
+    def clone(self, status: PyTealFrameStatus) -> "PyTealFrame":
+        ptf = PyTealFrame(
+            frame_info=self.frame_info,
+            node=self.node,
+            creator=self.creator,
+            full_stack=self.full_stack,
+            rel_paths=self.rel_paths,
+        )
+        ptf._status = status
+
+        return ptf
+
+    def location(self) -> str:
+        return f"{self.file()}:{self.lineno()}" if self.frame_info else ""
+
+    def file(self) -> str:
+        if not self.frame_info:
+            return ""
+
+        path = self.frame_info.filename
+        return os.path.relpath(path) if self.rel_paths else path
+
+    def root(self) -> str:
+        if not self.frame_info:
+            return ""
+
+        path = self.frame_info.filename
+        return path[: -len(self.file())]
+
+    def code_qualname(self) -> str:
+        return (
+            Source.executing(self.frame_info.frame).code_qualname()
+            if self.frame_info
+            else ""
+        )
+
+    def lineno(self) -> int | None:
+        naive_lineno = self.frame_info.lineno if self.frame_info else None
+        if naive_lineno is None:
+            return naive_lineno
+
+        hybrid_line, offset = self._hybrid_w_offset()
+        if hybrid_line.startswith(self.raw_code()):
+            return naive_lineno
+
+        return naive_lineno + offset
+
+    def column(self) -> int:
+        """Provide accurate 0-indexed column offset when available. Or 0 when not."""
+        return self.node_col_offset() or 0
+
+    def compiler_generated_reason(self) -> str | None:
+        """
+        None indicates either "unkown" or "not compiler generated".
+        To distinguish between these two usages, call `compiler_generated()` first.
+        """
+        if not self.compiler_generated():
+            return None
+
+        if self.creator._compiler_gen:
+            return PT_GENERATED.FLAGGED_BY_DEV
+
+        for k, v in _PT_GEN.items():
+            if k in self.raw_code():
+                return v
+
+        raise AssertionError(
+            "This should never happen as the call to self.compiler_generated() should have prevented this case."
+        )
+
+    # VARIATIONS ON THE THEME OF SOURCE CODE:
+
+    def raw_code(self) -> str:
+        if self._raw_code is None:
+            self._raw_code = (
+                ("".join(self.frame_info.code_context)).strip()
+                if self.frame_info and self.frame_info.code_context
+                else ""
+            )
+
+        return self._raw_code
+
+    def hybrid_unparsed(self) -> str:
+        """
+        Attempt to unparse the node and return the most apropos line
+        """
+        return self._hybrid_w_offset()[0]
+
+    def _hybrid_w_offset(self) -> tuple[str, int]:
+        """
+        Attempt to unparse the node and return the most apropos line,
+        together with its offset in comparison with its raw code.
+        """
+        raw_code = self.raw_code()
+        node = self.node
+        pt_chunk = self.node_source()
+        return self._hybrid_impl(raw_code, node, pt_chunk)
+
+    @classmethod
+    def _hybrid_impl(
+        cls, code: str, node: AST | None, pt_chunk: str
+    ) -> tuple[str, int]:
+        """
+        Given a chunk of PyTeal `pt_chunk` represending a node's source,
+        and `code` representing a FrameInfo's code_context,
+        return information about the most appropriate code to use in
+        the source map.
+        When the node source isn't available, return `code`
+        and an offset of 0.
+        When the node source is a single line, return `pt_chunk`
+        and an offset of 0.
+        When the node source is a multi-line chunk, in the case of
+        a non-FunctionDef node, we assume that the offset is 0.
+        Finally, in the case of a FunctionDef node, find the offset
+        by finding where the prefix `def` appears in the chunk.
+        """
+        if pt_chunk:
+            pt_lines = pt_chunk.splitlines()
+            if len(pt_lines) == 1:
+                return pt_chunk, 0
+
+            offset = i = 0
+            if pt_lines and isinstance(node, FunctionDef):
+                code_idx = -1
+                for i, line in enumerate(pt_lines):
+                    if line.startswith(code):
+                        code_idx = i
+                    if line.startswith("def"):
+                        if code_idx >= 0:
+                            offset = i - code_idx
+                        return pt_lines[i], offset
+                if code_idx >= 0:
+                    return pt_lines[code_idx], 0
+
+        return code, 0
+
+    # END OF VARIATIONS ON A THEME OF CODE
+    def failed_ast(self) -> bool:
+        return not self.node
+
+    def status_code(self) -> PyTealFrameStatus:
+        if self._status is not None:
+            return self._status
+
+        if self.frame_info is None:
+            return PyTealFrameStatus.MISSING
+
+        if self.node is None:
+            return PyTealFrameStatus.MISSING_AST
+
+        if self.compiler_generated():
+            return PyTealFrameStatus.PYTEAL_GENERATED
+
+        if not self.raw_code():
+            return PyTealFrameStatus.MISSING_CODE
+
+        return PyTealFrameStatus.COPACETIC
+
+    def status(self) -> str:
+        return self.status_code().human()
+
+    def node_source(self) -> str:
+        return unparse(self.node) if self.node else ""
+
+    def node_lineno(self) -> int | None:
+        return getattr(self.node, "lineno", None) if self.node else None
+
+    def node_col_offset(self) -> int | None:
+        """0-indexed BEGINNING column offset"""
+        return getattr(self.node, "col_offset", None) if self.node else None
+
+    def node_end_lineno(self) -> int | None:
+        return getattr(self.node, "end_lineno", None) if self.node else None
+
+    def node_end_col_offset(self) -> int | None:
+        """0-indexed ENDING column offset"""
+        return getattr(self.node, "end_col_offset", None) if self.node else None
+
+    def node_source_window(self) -> str:
+        boundaries = (
+            self.node_lineno(),
+            self.node_col_offset(),
+            self.node_end_lineno(),
+            self.node_end_col_offset(),
+        )
+        if not all(b is not None for b in boundaries):
+            return ""
+        return "L{}:{}-L{}:{}".format(*boundaries)
+
+    def __str__(self) -> str:
+        return self._str_impl(verbose=True)
+
+    def _str_impl(self, verbose: bool = True) -> str:
+        if not self.frame_info:
+            return "None"
+
+        spaces = "\n\t\t\t"
+        short = f"<{self.raw_code()}>{spaces}@{self.location()}"
+        if not verbose:
+            return short
+
+        return f"""{short}
+{self.frame_info.index=}
+{self.frame_info.function=}
+{self.frame_info.frame=}"""
diff --git a/pyteal/util.py b/pyteal/util.py
index 614919fc6..e4c0eec5c 100644
--- a/pyteal/util.py
+++ b/pyteal/util.py
@@ -1,4 +1,36 @@
-from pyteal.errors import TealInternalError
+from algosdk.v2client import algod
+
+from pyteal.errors import AlgodClientError, TealInternalError
+
+
+def algod_with_assertion(
+    client: algod.AlgodClient | None = None, msg: str = ""
+) -> algod.AlgodClient:
+    def wrap(e, msg):
+        raise AlgodClientError(f"{msg}: {e}" if msg else str(e))
+
+    if not client:
+        try:
+            client = _algod_client()
+        except Exception as e:
+            wrap(e, msg)
+
+    assert client
+    try:
+        if not client.status():
+            wrap("algod.status() did not produce any results", msg)
+
+    except Exception as e:
+        wrap(e, msg)
+
+    return client
+
+
+def _algod_client(
+    algod_address="http://localhost:4001", algod_token="a" * 64
+) -> algod.AlgodClient:
+    """Instantiate and return Algod client object."""
+    return algod.AlgodClient(algod_token, algod_address)
 
 
 def escapeStr(s: str) -> str:
diff --git a/requirements.txt b/requirements.txt
index 5811b9870..fe3b92066 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -5,6 +5,7 @@ graviton@git+https://github.com/algorand/graviton@v0.9.0
 mypy==0.991
 pytest==7.2.0
 pytest-cov==3.0.0
+pytest-custom-exit-code==0.3.0
 pytest-timeout==2.1.0
 pytest-xdist==3.0.2
 types-setuptools==57.4.18
diff --git a/setup.py b/setup.py
index 6f342f8d8..60b0446bf 100644
--- a/setup.py
+++ b/setup.py
@@ -22,9 +22,11 @@
     ),
     install_requires=[
         # when changing this list, also update docs/requirements.txt
+        "docstring-parser==0.14.1",
+        "executing==1.2.0",
         "py-algorand-sdk>=2.0.0,<3.0.0",
         "semantic-version>=2.9.0,<3.0.0",
-        "docstring-parser==0.14.1",
+        "tabulate>=0.9.0,<0.10.0",
     ],
     classifiers=[
         "Programming Language :: Python :: 3",


Test patch:
diff --git a/pyteal/ast/router_test.py b/pyteal/ast/router_test.py
index 2eb1fb937..9388259e6 100644
--- a/pyteal/ast/router_test.py
+++ b/pyteal/ast/router_test.py
@@ -377,7 +377,9 @@ def test_bare_call_config_clear_state_failure():
             )
         )
 
-    assert "Attempt to construct clear state program from bare app call" in str(tie)
+    assert "Attempt to construct clear state program from bare app call" in str(
+        tie.value
+    )
 
 
 def test_BareCallActions_asdict():
@@ -409,6 +411,29 @@ def test_BareCallActions_asdict():
     }
 
 
+def test_BareCallActions_aslist():
+    no_action = pt.OnCompleteAction()
+    optin_action = pt.OnCompleteAction(action=pt.Int(1), call_config=pt.CallConfig.ALL)
+    update_action = pt.OnCompleteAction(
+        action=pt.Int(2), call_config=pt.CallConfig.CALL
+    )
+
+    bca = pt.BareCallActions(
+        update_application=update_action,
+        opt_in=optin_action,
+    )
+
+    bcal = bca.aslist()
+    assert bcal == [
+        no_action,
+        no_action,
+        no_action,
+        no_action,
+        optin_action,
+        update_action,
+    ]
+
+
 def test_BareCallActions_get_method_config():
     from pyteal.ast.router import MethodConfig, CallConfig
 
diff --git a/pyteal/compiler/flatten_test.py b/pyteal/compiler/flatten_test.py
index a01c45d64..80c63ea9d 100644
--- a/pyteal/compiler/flatten_test.py
+++ b/pyteal/compiler/flatten_test.py
@@ -295,11 +295,21 @@ def test_flattenSubroutines_no_subroutines():
         pt.TealOp(None, pt.Op.return_),
     ]
 
-    actual = flattenSubroutines(subroutineMapping, subroutineToLabel)
+    opts = pt.CompileOptions()
+    actual = flattenSubroutines(subroutineMapping, subroutineToLabel, opts)
 
     assert actual == expected
 
 
+def without_expr(comp):
+    if isinstance(comp, pt.TealOp):
+        return pt.TealOp(None, comp.op, *comp.args)
+    if isinstance(comp, pt.TealLabel):
+        return pt.TealLabel(None, comp.label, comp.comment)
+
+    assert False, "should never get here"
+
+
 def test_flattenSubroutines_1_subroutine():
     subroutine = pt.SubroutineDefinition(
         lambda: pt.Int(1) + pt.Int(2) + pt.Int(3), pt.TealType.uint64
@@ -354,9 +364,10 @@ def test_flattenSubroutines_1_subroutine():
         pt.TealOp(None, pt.Op.retsub),
     ]
 
-    actual = flattenSubroutines(subroutineMapping, subroutineToLabel)
+    opts = pt.CompileOptions()
+    actual = flattenSubroutines(subroutineMapping, subroutineToLabel, opts)
 
-    assert actual == expected
+    assert list(map(without_expr, actual)) == expected
 
 
 def test_flattenSubroutines_multiple_subroutines():
@@ -539,6 +550,7 @@ def sub3Impl(a1, a2, a3):
         pt.TealOp(None, pt.Op.b, expectedSubroutine3L1Label),
     ]
 
-    actual = flattenSubroutines(subroutineMapping, subroutineToLabel)
+    opts = pt.CompileOptions()
+    actual = flattenSubroutines(subroutineMapping, subroutineToLabel, opts)
 
     assert actual == expected
diff --git a/pyteal/stack_frame_test.py b/pyteal/stack_frame_test.py
new file mode 100644
index 000000000..78fcb889b
--- /dev/null
+++ b/pyteal/stack_frame_test.py
@@ -0,0 +1,20 @@
+from unittest.mock import Mock
+
+from pyteal.stack_frame import NatalStackFrame, StackFrame
+
+
+def test_is_pyteal():
+    FrameInfo = Mock()
+    FrameInfo.return_value = Mock()
+
+    FrameInfo.return_value.filename = "not_pyteal.py"
+    sf = StackFrame(FrameInfo(), None, NatalStackFrame())
+    assert not sf._is_pyteal()
+
+    FrameInfo.return_value.filename = "blahblah/pyteal/ir/blah"
+    sf = StackFrame(FrameInfo(), None, NatalStackFrame())
+    assert sf._is_pyteal()
+
+    FrameInfo.return_value.filename = "blahblah/pyteal/not_really..."
+    sf = StackFrame(FrameInfo(), None, NatalStackFrame())
+    assert not sf._is_pyteal()
diff --git a/pytest.ini b/pytest.ini
index c443cbdee..81d9bbf83 100644
--- a/pytest.ini
+++ b/pytest.ini
@@ -1,3 +1,4 @@
 [pytest]
 markers =
     serial: marks tests requiring serial execution
+    slow: marks tests which are slow
diff --git a/tests/integration/algod_test.py b/tests/integration/algod_test.py
new file mode 100644
index 000000000..84e1deaa5
--- /dev/null
+++ b/tests/integration/algod_test.py
@@ -0,0 +1,76 @@
+from unittest import mock
+
+import pytest
+
+from pyteal.errors import AlgodClientError
+from pyteal.util import algod_with_assertion
+
+
+def test_algod_with_assertion_copacetic():
+    """
+    In C.I. integration tests we expect the happy path for the Algod client
+    """
+    client = algod_with_assertion()
+
+    assert client
+
+    reclient = algod_with_assertion(client)
+    assert client is reclient
+
+
+def test_algod_errors():
+    from algosdk.v2client.algod import AlgodClient
+
+    from pyteal import util
+
+    with mock.patch.object(util, "_algod_client", side_effect=Exception("1337")):
+        with pytest.raises(AlgodClientError) as ace:
+            algod_with_assertion()
+        assert "1337" in str(ace)
+
+        with pytest.raises(AlgodClientError) as ace:
+            algod_with_assertion(msg="okey dokey")
+        assert "1337" in str(ace)
+        assert "okey dokey" in str(ace)
+
+    with mock.patch.object(AlgodClient, "status", side_effect=Exception("42")):
+        with pytest.raises(AlgodClientError) as ace:
+            algod_with_assertion()
+        assert "42" in str(ace)
+
+        with pytest.raises(AlgodClientError) as ace:
+            algod_with_assertion(msg="yoyo ma")
+        assert "42" in str(ace)
+        assert "yoyo ma" in str(ace)
+
+    with mock.patch.object(AlgodClient, "status", lambda _: None):
+        with pytest.raises(AlgodClientError) as ace:
+            algod_with_assertion()
+        assert "did not produce any results" in str(ace)
+
+        with pytest.raises(AlgodClientError) as ace:
+            algod_with_assertion(msg="mellow yellow")
+        assert "did not produce any results" in str(ace)
+        assert "mellow yellow" in str(ace)
+
+    with pytest.raises(AlgodClientError) as ace:
+        algod_with_assertion("foo")  # type: ignore
+    assert "has no attribute 'status'" in str(ace)
+
+    with pytest.raises(AlgodClientError) as ace:
+        algod_with_assertion("foo", msg="blarney")  # type: ignore
+    assert "has no attribute 'status'" in str(ace)
+    assert "blarney" in str(ace)
+
+    client = algod_with_assertion()
+    assert client
+
+    with mock.patch.object(client, "status", side_effect=Exception("42")):
+        with pytest.raises(AlgodClientError) as ace:
+            algod_with_assertion(client)
+        assert "42" in str(ace)
+
+        with pytest.raises(AlgodClientError) as ace:
+            algod_with_assertion(client, msg="hokus pokus")
+        assert "42" in str(ace)
+        assert "hokus pokus" in str(ace)
diff --git a/tests/integration/sourcemap_monkey_integ_test.py b/tests/integration/sourcemap_monkey_integ_test.py
new file mode 100644
index 000000000..bf4ee848f
--- /dev/null
+++ b/tests/integration/sourcemap_monkey_integ_test.py
@@ -0,0 +1,245 @@
+from configparser import ConfigParser
+from importlib import import_module
+from itertools import product
+from pathlib import Path
+from unittest import mock
+
+import pytest
+
+"""
+This file monkey-patches ConfigParser in order to enable source mapping
+and test the results of source mapping various PyTeal dapps.
+"""
+
+
+BRUTE_FORCE_TERRIBLE_SKIPPING = (
+    """The second router is flaky due to issue 199, so skipping for now"""
+)
+FIXTURES = Path.cwd() / "tests" / "integration" / "teal" / "annotated"
+
+ROUTERS = [
+    ("examples.application.abi.algobank", "router"),
+    ("pyteal.compiler.compiler_test", "FIRST_ROUTER"),
+]
+
+if BRUTE_FORCE_TERRIBLE_SKIPPING:
+    del ROUTERS[1]
+
+
+@pytest.fixture
+def mock_ConfigParser():
+    patcher = mock.patch.object(ConfigParser, "getboolean", return_value=True)
+    patcher.start()
+    yield
+    patcher.stop()
+
+
+@pytest.mark.serial
+@pytest.mark.parametrize("path, obj", ROUTERS)
+@pytest.mark.parametrize("annotate_teal_headers", [True, False])
+@pytest.mark.parametrize("annotate_teal_concise", [True, False])
+def test_sourcemap_annotate(
+    mock_ConfigParser, path, obj, annotate_teal_headers, annotate_teal_concise
+):
+    from pyteal import OptimizeOptions
+
+    router = getattr(import_module(path), obj)
+
+    a_fname, c_fname = "A.teal", "C.teal"
+    compile_bundle = router.compile(
+        version=6,
+        optimize=OptimizeOptions(scratch_slots=True),
+        assemble_constants=False,
+        with_sourcemaps=True,
+        approval_filename=a_fname,
+        clear_filename=c_fname,
+        pcs_in_sourcemap=True,
+        annotate_teal=True,
+        annotate_teal_headers=annotate_teal_headers,
+        annotate_teal_concise=annotate_teal_concise,
+    )
+
+    CL = 49  # COMPILATION LINE right before this
+    CFILE = "tests/integration/sourcemap_monkey_integ_test.py"  # this file
+    COMPILE = "router.compile(version=6, optimize=OptimizeOptions(scratch_slots=True), assemble_constants=False, with_sourcemaps=True, approval_filename=a_fname, clear_filename=c_fname, pcs_in_sourcemap=True, annotate_teal=True, annotate_teal_headers=annotate_teal_headers, annotate_teal_concise=annotate_teal_concise)"
+    INNERTXN = "InnerTxnBuilder.SetFields({TxnField.type_enum: TxnType.Payment, TxnField.receiver: recipient.address(), TxnField.amount: amount.get(), TxnField.fee: Int(0)})"
+
+    with_headers_int = int(annotate_teal_headers)
+    concise_int = int(annotate_teal_concise)
+    with open(
+        FIXTURES / f"{router.name}_h{with_headers_int}_c{concise_int}.tealf"
+    ) as f:
+        tealf = f.read()
+
+    # less confident that this annotated teal will remain identical in 310, but for now it's working:
+    EXPECTED_ANNOTATED_TEAL_311 = tealf.format(
+        CFILE=CFILE,
+        CL=CL,
+        COMPILE=COMPILE,
+        INNERTXN=INNERTXN,
+    ).strip()
+
+    annotated_approval, annotated_clear = (
+        compile_bundle.approval_sourcemap.annotated_teal,
+        compile_bundle.clear_sourcemap.annotated_teal,
+    )
+    assert annotated_approval
+    assert annotated_clear
+
+    the_same = EXPECTED_ANNOTATED_TEAL_311 == annotated_approval
+    print(f"{annotated_approval.splitlines()=}")
+    assert the_same, first_diff(EXPECTED_ANNOTATED_TEAL_311, annotated_approval)
+
+    raw_approval_lines, raw_clear_lines = (
+        compile_bundle.approval_teal.splitlines(),
+        compile_bundle.clear_teal.splitlines(),
+    )
+
+    ann_approval_lines, ann_clear_lines = (
+        annotated_approval.splitlines(),
+        annotated_clear.splitlines(),
+    )
+
+    assert len(raw_approval_lines) + with_headers_int == len(ann_approval_lines)
+    assert len(raw_clear_lines) + with_headers_int == len(ann_clear_lines)
+
+    for i, raw_line in enumerate(raw_approval_lines):
+        assert ann_approval_lines[i + with_headers_int].startswith(raw_line)
+
+    for i, raw_line in enumerate(raw_clear_lines):
+        assert ann_clear_lines[i + with_headers_int].startswith(raw_line)
+
+
+def first_diff(expected, actual):
+    alines = actual.splitlines()
+    elines = expected.splitlines()
+    for i, e in enumerate(elines):
+        if i >= len(alines):
+            return f"""LINE[{i+1}] missing from actual:
+{e}"""
+        if e != (a := alines[i]):
+            return f"""LINE[{i+1}]
+{e}
+VS.
+{a}
+"""
+    if len(alines) > len(elines):
+        return f"""LINE[{len(elines) + 1}] missing from expected:
+{alines[len(elines)]}"""
+
+    return ""
+
+
+def assert_lines_start_with(prefixes, lines):
+    assert len(prefixes) == len(lines)
+    for prefix, line in zip(prefixes, lines):
+        assert line.startswith(prefix)
+
+
+@pytest.mark.serial
+def test_no_regressions_via_fixtures_algobank(mock_ConfigParser):
+    import pyteal as pt
+
+    module_path, obj = ROUTERS[0]
+    algobank_router = getattr(import_module(module_path), obj)
+    assert algobank_router.name == "AlgoBank"
+
+    actual_approval, actual_clear, _ = algobank_router.compile_program(
+        version=6, optimize=pt.OptimizeOptions(scratch_slots=True)
+    )
+
+    algobank_path = Path.cwd() / "examples" / "application" / "abi"
+
+    with open(algobank_path / "algobank_approval.teal") as f:
+        expected_approval = f.read()
+
+    with open(algobank_path / "algobank_clear_state.teal") as f:
+        expected_clear = f.read()
+
+    assert expected_approval == actual_approval
+    assert expected_clear == actual_clear
+
+    bundle = algobank_router.compile(
+        version=6,
+        optimize=pt.OptimizeOptions(scratch_slots=True),
+    )
+    assert expected_approval == bundle.approval_teal
+    assert expected_clear == bundle.clear_teal
+
+    assert bundle.approval_sourcemap is None
+    assert bundle.clear_sourcemap is None
+
+    approval_prefixes = expected_approval.splitlines()
+    clear_prefixes = expected_clear.splitlines()
+
+    def assert_didnt_regress(pcs, headers, concise):
+        bundle = algobank_router.compile(
+            version=6,
+            optimize=pt.OptimizeOptions(scratch_slots=True),
+            with_sourcemaps=True,
+            pcs_in_sourcemap=pcs,
+            annotate_teal=True,
+            annotate_teal_headers=headers,
+            annotate_teal_concise=concise,
+        )
+        assert expected_approval == bundle.approval_teal
+        assert expected_clear == bundle.clear_teal
+
+        actual_approval_lines = bundle.approval_sourcemap.annotated_teal.splitlines()
+        actual_clear_lines = bundle.clear_sourcemap.annotated_teal.splitlines()
+
+        if headers:
+            del actual_approval_lines[0]
+            del actual_clear_lines[0]
+
+        assert_lines_start_with(approval_prefixes, actual_approval_lines)
+        assert_lines_start_with(clear_prefixes, actual_clear_lines)
+
+    for pcs, headers, concise in product([True, False], repeat=3):
+        assert_didnt_regress(pcs, headers, concise)
+
+
+RPS = Path.cwd() / "tests" / "teal"
+
+
+@pytest.mark.serial
+def test_no_regressions_via_fixtures_rps(mock_ConfigParser):
+    import pyteal as pt
+    from tests.teal.rps import approval_program
+
+    actual_approval = pt.compileTeal(approval_program(), pt.Mode.Application, version=6)
+
+    with open(RPS / "rps.teal") as f:
+        expected_approval = f.read()
+
+    assert expected_approval == actual_approval
+
+    compilation = pt.Compilation(approval_program(), pt.Mode.Application, version=6)
+
+    bundle = compilation.compile()
+
+    assert expected_approval == bundle.teal
+
+    assert bundle.sourcemap is None
+
+    approval_prefixes = expected_approval.splitlines()
+
+    def assert_didnt_regress(pcs, headers, concise):
+        bundle = compilation.compile(
+            with_sourcemap=True,
+            pcs_in_sourcemap=pcs,
+            annotate_teal=True,
+            annotate_teal_headers=headers,
+            annotate_teal_concise=concise,
+        )
+        assert expected_approval == bundle.teal
+
+        actual_approval_lines = bundle.sourcemap.annotated_teal.splitlines()
+
+        if headers:
+            del actual_approval_lines[0]
+
+        assert_lines_start_with(approval_prefixes, actual_approval_lines)
+
+    for pcs, headers, concise in product([True, False], repeat=3):
+        assert_didnt_regress(pcs, headers, concise)
diff --git a/tests/integration/teal/annotated/AlgoBank_h0_c0.tealf b/tests/integration/teal/annotated/AlgoBank_h0_c0.tealf
new file mode 100644
index 000000000..a587c7032
--- /dev/null
+++ b/tests/integration/teal/annotated/AlgoBank_h0_c0.tealf
@@ -0,0 +1,226 @@
+#pragma version 6                      //  (0)    {CFILE}  {CL}   {COMPILE}
+txn NumAppArgs                         //  (20)
+int 0                                  //  (22)
+==                                     //  (23)
+bnz main_l8                            //  (24)
+txna ApplicationArgs 0                 //  (27)
+method "deposit(pay,account)void"      //  (30)
+==                                     //  (36)
+bnz main_l7                            //  (37)
+txna ApplicationArgs 0                 //  (40)
+method "getBalance(account)uint64"     //  (43)
+==                                     //  (49)
+bnz main_l6                            //  (50)
+txna ApplicationArgs 0                 //  (53)
+method "withdraw(uint64,account)void"  //  (56)
+==                                     //  (62)
+bnz main_l5                            //  (63)
+err                                    //  (66)
+main_l5:                               //
+txn OnCompletion                       //  (67)   examples/application/abi/algobank.py              78   def withdraw(amount: abi.Uint64, recipient: abi.Account) -> Expr:
+int NoOp                               //  (69)
+==                                     //  (70)
+txn ApplicationID                      //  (71)
+int 0                                  //  (73)
+!=                                     //  (74)
+&&                                     //  (75)
+assert                                 //  (76)
+txna ApplicationArgs 1                 //  (77)   {CFILE}  {CL}   {COMPILE}
+btoi                                   //  (80)
+store 3                                //  (81)   examples/application/abi/algobank.py              78   def withdraw(amount: abi.Uint64, recipient: abi.Account) -> Expr:
+txna ApplicationArgs 2                 //  (83)   {CFILE}  {CL}   {COMPILE}
+int 0                                  //  (86)
+getbyte                                //  (87)
+store 4                                //  (88)   examples/application/abi/algobank.py              78   def withdraw(amount: abi.Uint64, recipient: abi.Account) -> Expr:
+load 3                                 //  (90)
+load 4                                 //  (92)
+callsub withdraw_3                     //  (94)
+int 1                                  //  (97)   {CFILE}  {CL}   {COMPILE}
+return                                 //  (98)   examples/application/abi/algobank.py              78   def withdraw(amount: abi.Uint64, recipient: abi.Account) -> Expr:
+main_l6:                               //         {CFILE}  {CL}   {COMPILE}
+txn OnCompletion                       //  (99)   examples/application/abi/algobank.py              65   def getBalance(user: abi.Account, *, output: abi.Uint64) -> Expr:
+int NoOp                               //  (101)
+==                                     //  (102)
+txn ApplicationID                      //  (103)
+int 0                                  //  (105)
+!=                                     //  (106)
+&&                                     //  (107)
+assert                                 //  (108)
+txna ApplicationArgs 1                 //  (109)  {CFILE}  {CL}   {COMPILE}
+int 0                                  //  (112)
+getbyte                                //  (113)
+callsub getBalance_2                   //  (114)
+store 2                                //  (117)  examples/application/abi/algobank.py              65   def getBalance(user: abi.Account, *, output: abi.Uint64) -> Expr:
+byte 0x151f7c75                        //  (119)
+load 2                                 //  (125)
+itob                                   //  (127)
+concat                                 //  (128)
+log                                    //  (129)
+int 1                                  //  (130)  {CFILE}  {CL}   {COMPILE}
+return                                 //  (131)  examples/application/abi/algobank.py              65   def getBalance(user: abi.Account, *, output: abi.Uint64) -> Expr:
+main_l7:                               //         {CFILE}  {CL}   {COMPILE}
+txn OnCompletion                       //  (132)  examples/application/abi/algobank.py              41   def deposit(payment: abi.PaymentTransaction, sender: abi.Account) -> Expr:
+int NoOp                               //  (134)
+==                                     //  (135)
+txn ApplicationID                      //  (136)
+int 0                                  //  (138)
+!=                                     //  (139)
+&&                                     //  (140)
+txn OnCompletion                       //  (141)
+int OptIn                              //  (143)
+==                                     //  (144)
+txn ApplicationID                      //  (145)
+int 0                                  //  (147)
+!=                                     //  (148)
+&&                                     //  (149)
+||                                     //  (150)
+assert                                 //  (151)
+txna ApplicationArgs 1                 //  (152)  {CFILE}  {CL}   {COMPILE}
+int 0                                  //  (155)
+getbyte                                //  (156)
+store 1                                //  (157)  examples/application/abi/algobank.py              41   def deposit(payment: abi.PaymentTransaction, sender: abi.Account) -> Expr:
+txn GroupIndex                         //  (159)  {CFILE}  {CL}   {COMPILE}
+int 1                                  //  (161)
+-                                      //  (162)
+store 0                                //  (163)  examples/application/abi/algobank.py              41   def deposit(payment: abi.PaymentTransaction, sender: abi.Account) -> Expr:
+load 0                                 //  (165)  {CFILE}  {CL}   {COMPILE}
+gtxns TypeEnum                         //  (167)  examples/application/abi/algobank.py              41   def deposit(payment: abi.PaymentTransaction, sender: abi.Account) -> Expr:
+int pay                                //  (169)
+==                                     //  (170)
+assert                                 //  (171)
+load 0                                 //  (172)
+load 1                                 //  (174)
+callsub deposit_1                      //  (176)
+int 1                                  //  (179)  {CFILE}  {CL}   {COMPILE}
+return                                 //  (180)  examples/application/abi/algobank.py              41   def deposit(payment: abi.PaymentTransaction, sender: abi.Account) -> Expr:
+main_l8:                               //         {CFILE}  {CL}   {COMPILE}
+txn OnCompletion                       //  (181)
+int NoOp                               //  (183)
+==                                     //  (184)
+bnz main_l18                           //  (185)
+txn OnCompletion                       //  (188)
+int OptIn                              //  (190)
+==                                     //  (191)
+bnz main_l17                           //  (192)
+txn OnCompletion                       //  (195)
+int CloseOut                           //  (197)
+==                                     //  (199)
+bnz main_l16                           //  (200)
+txn OnCompletion                       //  (203)
+int UpdateApplication                  //  (205)
+==                                     //  (207)
+bnz main_l15                           //  (208)
+txn OnCompletion                       //  (211)
+int DeleteApplication                  //  (213)
+==                                     //  (215)
+bnz main_l14                           //  (216)
+err                                    //  (219)
+main_l14:                              //
+txn ApplicationID                      //  (220)
+int 0                                  //  (222)
+!=                                     //  (223)
+assert                                 //  (224)
+callsub assertsenderiscreator_0        //  (225)
+int 1                                  //  (228)
+return                                 //  (229)
+main_l15:                              //
+txn ApplicationID                      //  (230)
+int 0                                  //  (232)
+!=                                     //  (233)
+assert                                 //  (234)
+callsub assertsenderiscreator_0        //  (235)
+int 1                                  //  (238)
+return                                 //  (239)
+main_l16:                              //
+txn ApplicationID                      //  (240)
+int 0                                  //  (242)
+!=                                     //  (243)
+assert                                 //  (244)
+byte "lost"                            //  (245)  examples/application/abi/algobank.py              13   Bytes('lost')
+byte "lost"                            //  (246)                                                    14   Bytes('lost')
+app_global_get                         //  (247)                                                         App.globalGet(Bytes('lost'))
+txn Sender                             //  (248)                                                         Txn.sender()
+byte "balance"                         //  (250)                                                         Bytes('balance')
+app_local_get                          //  (251)                                                         App.localGet(Txn.sender(), Bytes('balance'))
++                                      //  (252)                                                         App.globalGet(Bytes('lost')) + App.localGet(Txn.sender(), Bytes('balance'))
+app_global_put                         //  (253)                                                    12   App.globalPut(Bytes('lost'), App.globalGet(Bytes('lost')) + App.localGet(Txn.sender(), Bytes('balance')))
+int 1                                  //  (254)  {CFILE}  {CL}   {COMPILE}
+return                                 //  (255)
+main_l17:                              //
+int 1                                  //  (256)  examples/application/abi/algobank.py              23   Approve()
+return                                 //  (257)
+main_l18:                              //         {CFILE}  {CL}   {COMPILE}
+txn ApplicationID                      //  (258)
+int 0                                  //  (260)
+==                                     //  (261)
+assert                                 //  (262)
+int 1                                  //  (263)  examples/application/abi/algobank.py              21   Approve()
+return                                 //  (264)
+                                       //                                                           7    def assert_sender_is_creator() -> Expr:
+// assert_sender_is_creator            //
+assertsenderiscreator_0:               //
+txn Sender                             //  (265)                                                    8    Txn.sender()
+global CreatorAddress                  //  (267)                                                         Global.creator_address()
+==                                     //  (269)                                                         Txn.sender() == Global.creator_address()
+assert                                 //  (270)                                                         Assert(Txn.sender() == Global.creator_address())
+retsub                                 //  (271)                                                    7    def assert_sender_is_creator() -> Expr:
+                                       //                                                           41   def deposit(payment: abi.PaymentTransaction, sender: abi.Account) -> Expr:
+// deposit                             //
+deposit_1:                             //
+store 6                                //  (272)  {CFILE}  {CL}   {COMPILE}
+store 5                                //  (274)
+load 5                                 //  (276)  examples/application/abi/algobank.py              54   payment.get()
+gtxns Sender                           //  (278)                                                         payment.get().sender()
+load 6                                 //  (280)                                                         sender.address()
+txnas Accounts                         //  (282)
+==                                     //  (284)                                                         payment.get().sender() == sender.address()
+assert                                 //  (285)                                                         Assert(payment.get().sender() == sender.address())
+load 5                                 //  (286)                                                    55   payment.get()
+gtxns Receiver                         //  (288)                                                         payment.get().receiver()
+global CurrentApplicationAddress       //  (290)                                                         Global.current_application_address()
+==                                     //  (292)                                                         payment.get().receiver() == Global.current_application_address()
+assert                                 //  (293)                                                         Assert(payment.get().receiver() == Global.current_application_address())
+load 6                                 //  (294)                                                    57   sender.address()
+txnas Accounts                         //  (296)
+byte "balance"                         //  (298)                                                    58   Bytes('balance')
+load 6                                 //  (299)                                                    59   sender.address()
+txnas Accounts                         //  (301)
+byte "balance"                         //  (303)                                                         Bytes('balance')
+app_local_get                          //  (304)                                                         App.localGet(sender.address(), Bytes('balance'))
+load 5                                 //  (305)                                                         payment.get()
+gtxns Amount                           //  (307)                                                         payment.get().amount()
++                                      //  (309)                                                         App.localGet(sender.address(), Bytes('balance')) + payment.get().amount()
+app_local_put                          //  (310)                                                    56   App.localPut(sender.address(), Bytes('balance'), App.localGet(sender.address(), Bytes('balance')) + payment.get().amount())
+retsub                                 //  (311)                                                    41   def deposit(payment: abi.PaymentTransaction, sender: abi.Account) -> Expr:
+                                       //                                                           65   def getBalance(user: abi.Account, *, output: abi.Uint64) -> Expr:
+// getBalance                          //
+getBalance_2:                          //
+txnas Accounts                         //  (312)                                                    74   user.address()
+byte "balance"                         //  (314)                                                         Bytes('balance')
+app_local_get                          //  (315)                                                         App.localGet(user.address(), Bytes('balance'))
+retsub                                 //  (316)                                                    65   def getBalance(user: abi.Account, *, output: abi.Uint64) -> Expr:
+                                       //                                                           78   def withdraw(amount: abi.Uint64, recipient: abi.Account) -> Expr:
+// withdraw                            //
+withdraw_3:                            //
+store 8                                //  (317)  {CFILE}  {CL}   {COMPILE}
+store 7                                //  (319)
+txn Sender                             //  (321)  examples/application/abi/algobank.py              98   Txn.sender()
+byte "balance"                         //  (323)                                                    99   Bytes('balance')
+txn Sender                             //  (324)                                                    100  Txn.sender()
+byte "balance"                         //  (326)                                                         Bytes('balance')
+app_local_get                          //  (327)                                                         App.localGet(Txn.sender(), Bytes('balance'))
+load 7                                 //  (328)                                                         amount.get()
+-                                      //  (330)                                                         App.localGet(Txn.sender(), Bytes('balance')) - amount.get()
+app_local_put                          //  (331)                                                    97   App.localPut(Txn.sender(), Bytes('balance'), App.localGet(Txn.sender(), Bytes('balance')) - amount.get())
+itxn_begin                             //  (332)                                                    102  InnerTxnBuilder.Begin()
+int pay                                //  (333)                                                    41   def deposit(payment: abi.PaymentTransaction, sender: abi.Account) -> Expr:
+itxn_field TypeEnum                    //  (334)                                                    103  {INNERTXN}
+load 8                                 //  (336)                                                    106  recipient.address()
+txnas Accounts                         //  (338)
+itxn_field Receiver                    //  (340)                                                    103  {INNERTXN}
+load 7                                 //  (342)                                                    107  amount.get()
+itxn_field Amount                      //  (344)                                                    103  {INNERTXN}
+int 0                                  //  (346)                                                    108  Int(0)
+itxn_field Fee                         //  (347)                                                    103  {INNERTXN}
+itxn_submit                            //  (349)                                                    111  InnerTxnBuilder.Submit()
+retsub                                 //  (350)                                                    78   def withdraw(amount: abi.Uint64, recipient: abi.Account) -> Expr:
\ No newline at end of file
diff --git a/tests/integration/teal/annotated/AlgoBank_h0_c1.tealf b/tests/integration/teal/annotated/AlgoBank_h0_c1.tealf
new file mode 100644
index 000000000..bb5bd4942
--- /dev/null
+++ b/tests/integration/teal/annotated/AlgoBank_h0_c1.tealf
@@ -0,0 +1,226 @@
+#pragma version 6                      //  (0)    {COMPILE}
+txn NumAppArgs                         //  (20)
+int 0                                  //  (22)
+==                                     //  (23)
+bnz main_l8                            //  (24)
+txna ApplicationArgs 0                 //  (27)
+method "deposit(pay,account)void"      //  (30)
+==                                     //  (36)
+bnz main_l7                            //  (37)
+txna ApplicationArgs 0                 //  (40)
+method "getBalance(account)uint64"     //  (43)
+==                                     //  (49)
+bnz main_l6                            //  (50)
+txna ApplicationArgs 0                 //  (53)
+method "withdraw(uint64,account)void"  //  (56)
+==                                     //  (62)
+bnz main_l5                            //  (63)
+err                                    //  (66)
+main_l5:                               //
+txn OnCompletion                       //  (67)   def withdraw(amount: abi.Uint64, recipient: abi.Account) -> Expr:
+int NoOp                               //  (69)
+==                                     //  (70)
+txn ApplicationID                      //  (71)
+int 0                                  //  (73)
+!=                                     //  (74)
+&&                                     //  (75)
+assert                                 //  (76)
+txna ApplicationArgs 1                 //  (77)   {COMPILE}
+btoi                                   //  (80)
+store 3                                //  (81)   def withdraw(amount: abi.Uint64, recipient: abi.Account) -> Expr:
+txna ApplicationArgs 2                 //  (83)   {COMPILE}
+int 0                                  //  (86)
+getbyte                                //  (87)
+store 4                                //  (88)   def withdraw(amount: abi.Uint64, recipient: abi.Account) -> Expr:
+load 3                                 //  (90)
+load 4                                 //  (92)
+callsub withdraw_3                     //  (94)
+int 1                                  //  (97)   {COMPILE}
+return                                 //  (98)   def withdraw(amount: abi.Uint64, recipient: abi.Account) -> Expr:
+main_l6:                               //         {COMPILE}
+txn OnCompletion                       //  (99)   def getBalance(user: abi.Account, *, output: abi.Uint64) -> Expr:
+int NoOp                               //  (101)
+==                                     //  (102)
+txn ApplicationID                      //  (103)
+int 0                                  //  (105)
+!=                                     //  (106)
+&&                                     //  (107)
+assert                                 //  (108)
+txna ApplicationArgs 1                 //  (109)  {COMPILE}
+int 0                                  //  (112)
+getbyte                                //  (113)
+callsub getBalance_2                   //  (114)
+store 2                                //  (117)  def getBalance(user: abi.Account, *, output: abi.Uint64) -> Expr:
+byte 0x151f7c75                        //  (119)
+load 2                                 //  (125)
+itob                                   //  (127)
+concat                                 //  (128)
+log                                    //  (129)
+int 1                                  //  (130)  {COMPILE}
+return                                 //  (131)  def getBalance(user: abi.Account, *, output: abi.Uint64) -> Expr:
+main_l7:                               //         {COMPILE}
+txn OnCompletion                       //  (132)  def deposit(payment: abi.PaymentTransaction, sender: abi.Account) -> Expr:
+int NoOp                               //  (134)
+==                                     //  (135)
+txn ApplicationID                      //  (136)
+int 0                                  //  (138)
+!=                                     //  (139)
+&&                                     //  (140)
+txn OnCompletion                       //  (141)
+int OptIn                              //  (143)
+==                                     //  (144)
+txn ApplicationID                      //  (145)
+int 0                                  //  (147)
+!=                                     //  (148)
+&&                                     //  (149)
+||                                     //  (150)
+assert                                 //  (151)
+txna ApplicationArgs 1                 //  (152)  {COMPILE}
+int 0                                  //  (155)
+getbyte                                //  (156)
+store 1                                //  (157)  def deposit(payment: abi.PaymentTransaction, sender: abi.Account) -> Expr:
+txn GroupIndex                         //  (159)  {COMPILE}
+int 1                                  //  (161)
+-                                      //  (162)
+store 0                                //  (163)  def deposit(payment: abi.PaymentTransaction, sender: abi.Account) -> Expr:
+load 0                                 //  (165)  {COMPILE}
+gtxns TypeEnum                         //  (167)  def deposit(payment: abi.PaymentTransaction, sender: abi.Account) -> Expr:
+int pay                                //  (169)
+==                                     //  (170)
+assert                                 //  (171)
+load 0                                 //  (172)
+load 1                                 //  (174)
+callsub deposit_1                      //  (176)
+int 1                                  //  (179)  {COMPILE}
+return                                 //  (180)  def deposit(payment: abi.PaymentTransaction, sender: abi.Account) -> Expr:
+main_l8:                               //         {COMPILE}
+txn OnCompletion                       //  (181)
+int NoOp                               //  (183)
+==                                     //  (184)
+bnz main_l18                           //  (185)
+txn OnCompletion                       //  (188)
+int OptIn                              //  (190)
+==                                     //  (191)
+bnz main_l17                           //  (192)
+txn OnCompletion                       //  (195)
+int CloseOut                           //  (197)
+==                                     //  (199)
+bnz main_l16                           //  (200)
+txn OnCompletion                       //  (203)
+int UpdateApplication                  //  (205)
+==                                     //  (207)
+bnz main_l15                           //  (208)
+txn OnCompletion                       //  (211)
+int DeleteApplication                  //  (213)
+==                                     //  (215)
+bnz main_l14                           //  (216)
+err                                    //  (219)
+main_l14:                              //
+txn ApplicationID                      //  (220)
+int 0                                  //  (222)
+!=                                     //  (223)
+assert                                 //  (224)
+callsub assertsenderiscreator_0        //  (225)
+int 1                                  //  (228)
+return                                 //  (229)
+main_l15:                              //
+txn ApplicationID                      //  (230)
+int 0                                  //  (232)
+!=                                     //  (233)
+assert                                 //  (234)
+callsub assertsenderiscreator_0        //  (235)
+int 1                                  //  (238)
+return                                 //  (239)
+main_l16:                              //
+txn ApplicationID                      //  (240)
+int 0                                  //  (242)
+!=                                     //  (243)
+assert                                 //  (244)
+byte "lost"                            //  (245)  Bytes('lost')
+byte "lost"                            //  (246)  Bytes('lost')
+app_global_get                         //  (247)  App.globalGet(Bytes('lost'))
+txn Sender                             //  (248)  Txn.sender()
+byte "balance"                         //  (250)  Bytes('balance')
+app_local_get                          //  (251)  App.localGet(Txn.sender(), Bytes('balance'))
++                                      //  (252)  App.globalGet(Bytes('lost')) + App.localGet(Txn.sender(), Bytes('balance'))
+app_global_put                         //  (253)  App.globalPut(Bytes('lost'), App.globalGet(Bytes('lost')) + App.localGet(Txn.sender(), Bytes('balance')))
+int 1                                  //  (254)  {COMPILE}
+return                                 //  (255)
+main_l17:                              //
+int 1                                  //  (256)  Approve()
+return                                 //  (257)
+main_l18:                              //         {COMPILE}
+txn ApplicationID                      //  (258)
+int 0                                  //  (260)
+==                                     //  (261)
+assert                                 //  (262)
+int 1                                  //  (263)  Approve()
+return                                 //  (264)
+                                       //         def assert_sender_is_creator() -> Expr:
+// assert_sender_is_creator            //
+assertsenderiscreator_0:               //
+txn Sender                             //  (265)  Txn.sender()
+global CreatorAddress                  //  (267)  Global.creator_address()
+==                                     //  (269)  Txn.sender() == Global.creator_address()
+assert                                 //  (270)  Assert(Txn.sender() == Global.creator_address())
+retsub                                 //  (271)  def assert_sender_is_creator() -> Expr:
+                                       //         def deposit(payment: abi.PaymentTransaction, sender: abi.Account) -> Expr:
+// deposit                             //
+deposit_1:                             //
+store 6                                //  (272)  {COMPILE}
+store 5                                //  (274)
+load 5                                 //  (276)  payment.get()
+gtxns Sender                           //  (278)  payment.get().sender()
+load 6                                 //  (280)  sender.address()
+txnas Accounts                         //  (282)
+==                                     //  (284)  payment.get().sender() == sender.address()
+assert                                 //  (285)  Assert(payment.get().sender() == sender.address())
+load 5                                 //  (286)  payment.get()
+gtxns Receiver                         //  (288)  payment.get().receiver()
+global CurrentApplicationAddress       //  (290)  Global.current_application_address()
+==                                     //  (292)  payment.get().receiver() == Global.current_application_address()
+assert                                 //  (293)  Assert(payment.get().receiver() == Global.current_application_address())
+load 6                                 //  (294)  sender.address()
+txnas Accounts                         //  (296)
+byte "balance"                         //  (298)  Bytes('balance')
+load 6                                 //  (299)  sender.address()
+txnas Accounts                         //  (301)
+byte "balance"                         //  (303)  Bytes('balance')
+app_local_get                          //  (304)  App.localGet(sender.address(), Bytes('balance'))
+load 5                                 //  (305)  payment.get()
+gtxns Amount                           //  (307)  payment.get().amount()
++                                      //  (309)  App.localGet(sender.address(), Bytes('balance')) + payment.get().amount()
+app_local_put                          //  (310)  App.localPut(sender.address(), Bytes('balance'), App.localGet(sender.address(), Bytes('balance')) + payment.get().amount())
+retsub                                 //  (311)  def deposit(payment: abi.PaymentTransaction, sender: abi.Account) -> Expr:
+                                       //         def getBalance(user: abi.Account, *, output: abi.Uint64) -> Expr:
+// getBalance                          //
+getBalance_2:                          //
+txnas Accounts                         //  (312)  user.address()
+byte "balance"                         //  (314)  Bytes('balance')
+app_local_get                          //  (315)  App.localGet(user.address(), Bytes('balance'))
+retsub                                 //  (316)  def getBalance(user: abi.Account, *, output: abi.Uint64) -> Expr:
+                                       //         def withdraw(amount: abi.Uint64, recipient: abi.Account) -> Expr:
+// withdraw                            //
+withdraw_3:                            //
+store 8                                //  (317)  {COMPILE}
+store 7                                //  (319)
+txn Sender                             //  (321)  Txn.sender()
+byte "balance"                         //  (323)  Bytes('balance')
+txn Sender                             //  (324)  Txn.sender()
+byte "balance"                         //  (326)  Bytes('balance')
+app_local_get                          //  (327)  App.localGet(Txn.sender(), Bytes('balance'))
+load 7                                 //  (328)  amount.get()
+-                                      //  (330)  App.localGet(Txn.sender(), Bytes('balance')) - amount.get()
+app_local_put                          //  (331)  App.localPut(Txn.sender(), Bytes('balance'), App.localGet(Txn.sender(), Bytes('balance')) - amount.get())
+itxn_begin                             //  (332)  InnerTxnBuilder.Begin()
+int pay                                //  (333)  def deposit(payment: abi.PaymentTransaction, sender: abi.Account) -> Expr:
+itxn_field TypeEnum                    //  (334)  {INNERTXN}
+load 8                                 //  (336)  recipient.address()
+txnas Accounts                         //  (338)
+itxn_field Receiver                    //  (340)  {INNERTXN}
+load 7                                 //  (342)  amount.get()
+itxn_field Amount                      //  (344)  {INNERTXN}
+int 0                                  //  (346)  Int(0)
+itxn_field Fee                         //  (347)  {INNERTXN}
+itxn_submit                            //  (349)  InnerTxnBuilder.Submit()
+retsub                                 //  (350)  def withdraw(amount: abi.Uint64, recipient: abi.Account) -> Expr:
diff --git a/tests/integration/teal/annotated/AlgoBank_h1_c0.tealf b/tests/integration/teal/annotated/AlgoBank_h1_c0.tealf
new file mode 100644
index 000000000..159e10131
--- /dev/null
+++ b/tests/integration/teal/annotated/AlgoBank_h1_c0.tealf
@@ -0,0 +1,227 @@
+// GENERATED TEAL                      //    PC     PYTEAL PATH                                       LINE    PYTEAL
+#pragma version 6                      //    (0)    {CFILE}  {CL}      {COMPILE}
+txn NumAppArgs                         //    (20)
+int 0                                  //    (22)
+==                                     //    (23)
+bnz main_l8                            //    (24)
+txna ApplicationArgs 0                 //    (27)
+method "deposit(pay,account)void"      //    (30)
+==                                     //    (36)
+bnz main_l7                            //    (37)
+txna ApplicationArgs 0                 //    (40)
+method "getBalance(account)uint64"     //    (43)
+==                                     //    (49)
+bnz main_l6                            //    (50)
+txna ApplicationArgs 0                 //    (53)
+method "withdraw(uint64,account)void"  //    (56)
+==                                     //    (62)
+bnz main_l5                            //    (63)
+err                                    //    (66)
+main_l5:                               //
+txn OnCompletion                       //    (67)   examples/application/abi/algobank.py              78      def withdraw(amount: abi.Uint64, recipient: abi.Account) -> Expr:
+int NoOp                               //    (69)
+==                                     //    (70)
+txn ApplicationID                      //    (71)
+int 0                                  //    (73)
+!=                                     //    (74)
+&&                                     //    (75)
+assert                                 //    (76)
+txna ApplicationArgs 1                 //    (77)   {CFILE}  {CL}      {COMPILE}
+btoi                                   //    (80)
+store 3                                //    (81)   examples/application/abi/algobank.py              78      def withdraw(amount: abi.Uint64, recipient: abi.Account) -> Expr:
+txna ApplicationArgs 2                 //    (83)   {CFILE}  {CL}      {COMPILE}
+int 0                                  //    (86)
+getbyte                                //    (87)
+store 4                                //    (88)   examples/application/abi/algobank.py              78      def withdraw(amount: abi.Uint64, recipient: abi.Account) -> Expr:
+load 3                                 //    (90)
+load 4                                 //    (92)
+callsub withdraw_3                     //    (94)
+int 1                                  //    (97)   {CFILE}  {CL}      {COMPILE}
+return                                 //    (98)   examples/application/abi/algobank.py              78      def withdraw(amount: abi.Uint64, recipient: abi.Account) -> Expr:
+main_l6:                               //           {CFILE}  {CL}      {COMPILE}
+txn OnCompletion                       //    (99)   examples/application/abi/algobank.py              65      def getBalance(user: abi.Account, *, output: abi.Uint64) -> Expr:
+int NoOp                               //    (101)
+==                                     //    (102)
+txn ApplicationID                      //    (103)
+int 0                                  //    (105)
+!=                                     //    (106)
+&&                                     //    (107)
+assert                                 //    (108)
+txna ApplicationArgs 1                 //    (109)  {CFILE}  {CL}      {COMPILE}
+int 0                                  //    (112)
+getbyte                                //    (113)
+callsub getBalance_2                   //    (114)
+store 2                                //    (117)  examples/application/abi/algobank.py              65      def getBalance(user: abi.Account, *, output: abi.Uint64) -> Expr:
+byte 0x151f7c75                        //    (119)
+load 2                                 //    (125)
+itob                                   //    (127)
+concat                                 //    (128)
+log                                    //    (129)
+int 1                                  //    (130)  {CFILE}  {CL}      {COMPILE}
+return                                 //    (131)  examples/application/abi/algobank.py              65      def getBalance(user: abi.Account, *, output: abi.Uint64) -> Expr:
+main_l7:                               //           {CFILE}  {CL}      {COMPILE}
+txn OnCompletion                       //    (132)  examples/application/abi/algobank.py              41      def deposit(payment: abi.PaymentTransaction, sender: abi.Account) -> Expr:
+int NoOp                               //    (134)
+==                                     //    (135)
+txn ApplicationID                      //    (136)
+int 0                                  //    (138)
+!=                                     //    (139)
+&&                                     //    (140)
+txn OnCompletion                       //    (141)
+int OptIn                              //    (143)
+==                                     //    (144)
+txn ApplicationID                      //    (145)
+int 0                                  //    (147)
+!=                                     //    (148)
+&&                                     //    (149)
+||                                     //    (150)
+assert                                 //    (151)
+txna ApplicationArgs 1                 //    (152)  {CFILE}  {CL}      {COMPILE}
+int 0                                  //    (155)
+getbyte                                //    (156)
+store 1                                //    (157)  examples/application/abi/algobank.py              41      def deposit(payment: abi.PaymentTransaction, sender: abi.Account) -> Expr:
+txn GroupIndex                         //    (159)  {CFILE}  {CL}      {COMPILE}
+int 1                                  //    (161)
+-                                      //    (162)
+store 0                                //    (163)  examples/application/abi/algobank.py              41      def deposit(payment: abi.PaymentTransaction, sender: abi.Account) -> Expr:
+load 0                                 //    (165)  {CFILE}  {CL}      {COMPILE}
+gtxns TypeEnum                         //    (167)  examples/application/abi/algobank.py              41      def deposit(payment: abi.PaymentTransaction, sender: abi.Account) -> Expr:
+int pay                                //    (169)
+==                                     //    (170)
+assert                                 //    (171)
+load 0                                 //    (172)
+load 1                                 //    (174)
+callsub deposit_1                      //    (176)
+int 1                                  //    (179)  {CFILE}  {CL}      {COMPILE}
+return                                 //    (180)  examples/application/abi/algobank.py              41      def deposit(payment: abi.PaymentTransaction, sender: abi.Account) -> Expr:
+main_l8:                               //           {CFILE}  {CL}      {COMPILE}
+txn OnCompletion                       //    (181)
+int NoOp                               //    (183)
+==                                     //    (184)
+bnz main_l18                           //    (185)
+txn OnCompletion                       //    (188)
+int OptIn                              //    (190)
+==                                     //    (191)
+bnz main_l17                           //    (192)
+txn OnCompletion                       //    (195)
+int CloseOut                           //    (197)
+==                                     //    (199)
+bnz main_l16                           //    (200)
+txn OnCompletion                       //    (203)
+int UpdateApplication                  //    (205)
+==                                     //    (207)
+bnz main_l15                           //    (208)
+txn OnCompletion                       //    (211)
+int DeleteApplication                  //    (213)
+==                                     //    (215)
+bnz main_l14                           //    (216)
+err                                    //    (219)
+main_l14:                              //
+txn ApplicationID                      //    (220)
+int 0                                  //    (222)
+!=                                     //    (223)
+assert                                 //    (224)
+callsub assertsenderiscreator_0        //    (225)
+int 1                                  //    (228)
+return                                 //    (229)
+main_l15:                              //
+txn ApplicationID                      //    (230)
+int 0                                  //    (232)
+!=                                     //    (233)
+assert                                 //    (234)
+callsub assertsenderiscreator_0        //    (235)
+int 1                                  //    (238)
+return                                 //    (239)
+main_l16:                              //
+txn ApplicationID                      //    (240)
+int 0                                  //    (242)
+!=                                     //    (243)
+assert                                 //    (244)
+byte "lost"                            //    (245)  examples/application/abi/algobank.py              13      Bytes('lost')
+byte "lost"                            //    (246)                                                    14      Bytes('lost')
+app_global_get                         //    (247)                                                            App.globalGet(Bytes('lost'))
+txn Sender                             //    (248)                                                            Txn.sender()
+byte "balance"                         //    (250)                                                            Bytes('balance')
+app_local_get                          //    (251)                                                            App.localGet(Txn.sender(), Bytes('balance'))
++                                      //    (252)                                                            App.globalGet(Bytes('lost')) + App.localGet(Txn.sender(), Bytes('balance'))
+app_global_put                         //    (253)                                                    12      App.globalPut(Bytes('lost'), App.globalGet(Bytes('lost')) + App.localGet(Txn.sender(), Bytes('balance')))
+int 1                                  //    (254)  {CFILE}  {CL}      {COMPILE}
+return                                 //    (255)
+main_l17:                              //
+int 1                                  //    (256)  examples/application/abi/algobank.py              23      Approve()
+return                                 //    (257)
+main_l18:                              //           {CFILE}  {CL}      {COMPILE}
+txn ApplicationID                      //    (258)
+int 0                                  //    (260)
+==                                     //    (261)
+assert                                 //    (262)
+int 1                                  //    (263)  examples/application/abi/algobank.py              21      Approve()
+return                                 //    (264)
+                                       //                                                             7       def assert_sender_is_creator() -> Expr:
+// assert_sender_is_creator            //
+assertsenderiscreator_0:               //
+txn Sender                             //    (265)                                                    8       Txn.sender()
+global CreatorAddress                  //    (267)                                                            Global.creator_address()
+==                                     //    (269)                                                            Txn.sender() == Global.creator_address()
+assert                                 //    (270)                                                            Assert(Txn.sender() == Global.creator_address())
+retsub                                 //    (271)                                                    7       def assert_sender_is_creator() -> Expr:
+                                       //                                                             41      def deposit(payment: abi.PaymentTransaction, sender: abi.Account) -> Expr:
+// deposit                             //
+deposit_1:                             //
+store 6                                //    (272)  {CFILE}  {CL}      {COMPILE}
+store 5                                //    (274)
+load 5                                 //    (276)  examples/application/abi/algobank.py              54      payment.get()
+gtxns Sender                           //    (278)                                                            payment.get().sender()
+load 6                                 //    (280)                                                            sender.address()
+txnas Accounts                         //    (282)
+==                                     //    (284)                                                            payment.get().sender() == sender.address()
+assert                                 //    (285)                                                            Assert(payment.get().sender() == sender.address())
+load 5                                 //    (286)                                                    55      payment.get()
+gtxns Receiver                         //    (288)                                                            payment.get().receiver()
+global CurrentApplicationAddress       //    (290)                                                            Global.current_application_address()
+==                                     //    (292)                                                            payment.get().receiver() == Global.current_application_address()
+assert                                 //    (293)                                                            Assert(payment.get().receiver() == Global.current_application_address())
+load 6                                 //    (294)                                                    57      sender.address()
+txnas Accounts                         //    (296)
+byte "balance"                         //    (298)                                                    58      Bytes('balance')
+load 6                                 //    (299)                                                    59      sender.address()
+txnas Accounts                         //    (301)
+byte "balance"                         //    (303)                                                            Bytes('balance')
+app_local_get                          //    (304)                                                            App.localGet(sender.address(), Bytes('balance'))
+load 5                                 //    (305)                                                            payment.get()
+gtxns Amount                           //    (307)                                                            payment.get().amount()
++                                      //    (309)                                                            App.localGet(sender.address(), Bytes('balance')) + payment.get().amount()
+app_local_put                          //    (310)                                                    56      App.localPut(sender.address(), Bytes('balance'), App.localGet(sender.address(), Bytes('balance')) + payment.get().amount())
+retsub                                 //    (311)                                                    41      def deposit(payment: abi.PaymentTransaction, sender: abi.Account) -> Expr:
+                                       //                                                             65      def getBalance(user: abi.Account, *, output: abi.Uint64) -> Expr:
+// getBalance                          //
+getBalance_2:                          //
+txnas Accounts                         //    (312)                                                    74      user.address()
+byte "balance"                         //    (314)                                                            Bytes('balance')
+app_local_get                          //    (315)                                                            App.localGet(user.address(), Bytes('balance'))
+retsub                                 //    (316)                                                    65      def getBalance(user: abi.Account, *, output: abi.Uint64) -> Expr:
+                                       //                                                             78      def withdraw(amount: abi.Uint64, recipient: abi.Account) -> Expr:
+// withdraw                            //
+withdraw_3:                            //
+store 8                                //    (317)  {CFILE}  {CL}      {COMPILE}
+store 7                                //    (319)
+txn Sender                             //    (321)  examples/application/abi/algobank.py              98      Txn.sender()
+byte "balance"                         //    (323)                                                    99      Bytes('balance')
+txn Sender                             //    (324)                                                    100     Txn.sender()
+byte "balance"                         //    (326)                                                            Bytes('balance')
+app_local_get                          //    (327)                                                            App.localGet(Txn.sender(), Bytes('balance'))
+load 7                                 //    (328)                                                            amount.get()
+-                                      //    (330)                                                            App.localGet(Txn.sender(), Bytes('balance')) - amount.get()
+app_local_put                          //    (331)                                                    97      App.localPut(Txn.sender(), Bytes('balance'), App.localGet(Txn.sender(), Bytes('balance')) - amount.get())
+itxn_begin                             //    (332)                                                    102     InnerTxnBuilder.Begin()
+int pay                                //    (333)                                                    41      def deposit(payment: abi.PaymentTransaction, sender: abi.Account) -> Expr:
+itxn_field TypeEnum                    //    (334)                                                    103     {INNERTXN}
+load 8                                 //    (336)                                                    106     recipient.address()
+txnas Accounts                         //    (338)
+itxn_field Receiver                    //    (340)                                                    103     {INNERTXN}
+load 7                                 //    (342)                                                    107     amount.get()
+itxn_field Amount                      //    (344)                                                    103     {INNERTXN}
+int 0                                  //    (346)                                                    108     Int(0)
+itxn_field Fee                         //    (347)                                                    103     {INNERTXN}
+itxn_submit                            //    (349)                                                    111     InnerTxnBuilder.Submit()
+retsub                                 //    (350)                                                    78      def withdraw(amount: abi.Uint64, recipient: abi.Account) -> Expr:
\ No newline at end of file
diff --git a/tests/integration/teal/annotated/AlgoBank_h1_c1.tealf b/tests/integration/teal/annotated/AlgoBank_h1_c1.tealf
new file mode 100644
index 000000000..66ebfbd61
--- /dev/null
+++ b/tests/integration/teal/annotated/AlgoBank_h1_c1.tealf
@@ -0,0 +1,227 @@
+// GENERATED TEAL                      //    PC     PYTEAL
+#pragma version 6                      //    (0)    {COMPILE}
+txn NumAppArgs                         //    (20)
+int 0                                  //    (22)
+==                                     //    (23)
+bnz main_l8                            //    (24)
+txna ApplicationArgs 0                 //    (27)
+method "deposit(pay,account)void"      //    (30)
+==                                     //    (36)
+bnz main_l7                            //    (37)
+txna ApplicationArgs 0                 //    (40)
+method "getBalance(account)uint64"     //    (43)
+==                                     //    (49)
+bnz main_l6                            //    (50)
+txna ApplicationArgs 0                 //    (53)
+method "withdraw(uint64,account)void"  //    (56)
+==                                     //    (62)
+bnz main_l5                            //    (63)
+err                                    //    (66)
+main_l5:                               //
+txn OnCompletion                       //    (67)   def withdraw(amount: abi.Uint64, recipient: abi.Account) -> Expr:
+int NoOp                               //    (69)
+==                                     //    (70)
+txn ApplicationID                      //    (71)
+int 0                                  //    (73)
+!=                                     //    (74)
+&&                                     //    (75)
+assert                                 //    (76)
+txna ApplicationArgs 1                 //    (77)   {COMPILE}
+btoi                                   //    (80)
+store 3                                //    (81)   def withdraw(amount: abi.Uint64, recipient: abi.Account) -> Expr:
+txna ApplicationArgs 2                 //    (83)   {COMPILE}
+int 0                                  //    (86)
+getbyte                                //    (87)
+store 4                                //    (88)   def withdraw(amount: abi.Uint64, recipient: abi.Account) -> Expr:
+load 3                                 //    (90)
+load 4                                 //    (92)
+callsub withdraw_3                     //    (94)
+int 1                                  //    (97)   {COMPILE}
+return                                 //    (98)   def withdraw(amount: abi.Uint64, recipient: abi.Account) -> Expr:
+main_l6:                               //           {COMPILE}
+txn OnCompletion                       //    (99)   def getBalance(user: abi.Account, *, output: abi.Uint64) -> Expr:
+int NoOp                               //    (101)
+==                                     //    (102)
+txn ApplicationID                      //    (103)
+int 0                                  //    (105)
+!=                                     //    (106)
+&&                                     //    (107)
+assert                                 //    (108)
+txna ApplicationArgs 1                 //    (109)  {COMPILE}
+int 0                                  //    (112)
+getbyte                                //    (113)
+callsub getBalance_2                   //    (114)
+store 2                                //    (117)  def getBalance(user: abi.Account, *, output: abi.Uint64) -> Expr:
+byte 0x151f7c75                        //    (119)
+load 2                                 //    (125)
+itob                                   //    (127)
+concat                                 //    (128)
+log                                    //    (129)
+int 1                                  //    (130)  {COMPILE}
+return                                 //    (131)  def getBalance(user: abi.Account, *, output: abi.Uint64) -> Expr:
+main_l7:                               //           {COMPILE}
+txn OnCompletion                       //    (132)  def deposit(payment: abi.PaymentTransaction, sender: abi.Account) -> Expr:
+int NoOp                               //    (134)
+==                                     //    (135)
+txn ApplicationID                      //    (136)
+int 0                                  //    (138)
+!=                                     //    (139)
+&&                                     //    (140)
+txn OnCompletion                       //    (141)
+int OptIn                              //    (143)
+==                                     //    (144)
+txn ApplicationID                      //    (145)
+int 0                                  //    (147)
+!=                                     //    (148)
+&&                                     //    (149)
+||                                     //    (150)
+assert                                 //    (151)
+txna ApplicationArgs 1                 //    (152)  {COMPILE}
+int 0                                  //    (155)
+getbyte                                //    (156)
+store 1                                //    (157)  def deposit(payment: abi.PaymentTransaction, sender: abi.Account) -> Expr:
+txn GroupIndex                         //    (159)  {COMPILE}
+int 1                                  //    (161)
+-                                      //    (162)
+store 0                                //    (163)  def deposit(payment: abi.PaymentTransaction, sender: abi.Account) -> Expr:
+load 0                                 //    (165)  {COMPILE}
+gtxns TypeEnum                         //    (167)  def deposit(payment: abi.PaymentTransaction, sender: abi.Account) -> Expr:
+int pay                                //    (169)
+==                                     //    (170)
+assert                                 //    (171)
+load 0                                 //    (172)
+load 1                                 //    (174)
+callsub deposit_1                      //    (176)
+int 1                                  //    (179)  {COMPILE}
+return                                 //    (180)  def deposit(payment: abi.PaymentTransaction, sender: abi.Account) -> Expr:
+main_l8:                               //           {COMPILE}
+txn OnCompletion                       //    (181)
+int NoOp                               //    (183)
+==                                     //    (184)
+bnz main_l18                           //    (185)
+txn OnCompletion                       //    (188)
+int OptIn                              //    (190)
+==                                     //    (191)
+bnz main_l17                           //    (192)
+txn OnCompletion                       //    (195)
+int CloseOut                           //    (197)
+==                                     //    (199)
+bnz main_l16                           //    (200)
+txn OnCompletion                       //    (203)
+int UpdateApplication                  //    (205)
+==                                     //    (207)
+bnz main_l15                           //    (208)
+txn OnCompletion                       //    (211)
+int DeleteApplication                  //    (213)
+==                                     //    (215)
+bnz main_l14                           //    (216)
+err                                    //    (219)
+main_l14:                              //
+txn ApplicationID                      //    (220)
+int 0                                  //    (222)
+!=                                     //    (223)
+assert                                 //    (224)
+callsub assertsenderiscreator_0        //    (225)
+int 1                                  //    (228)
+return                                 //    (229)
+main_l15:                              //
+txn ApplicationID                      //    (230)
+int 0                                  //    (232)
+!=                                     //    (233)
+assert                                 //    (234)
+callsub assertsenderiscreator_0        //    (235)
+int 1                                  //    (238)
+return                                 //    (239)
+main_l16:                              //
+txn ApplicationID                      //    (240)
+int 0                                  //    (242)
+!=                                     //    (243)
+assert                                 //    (244)
+byte "lost"                            //    (245)  Bytes('lost')
+byte "lost"                            //    (246)  Bytes('lost')
+app_global_get                         //    (247)  App.globalGet(Bytes('lost'))
+txn Sender                             //    (248)  Txn.sender()
+byte "balance"                         //    (250)  Bytes('balance')
+app_local_get                          //    (251)  App.localGet(Txn.sender(), Bytes('balance'))
++                                      //    (252)  App.globalGet(Bytes('lost')) + App.localGet(Txn.sender(), Bytes('balance'))
+app_global_put                         //    (253)  App.globalPut(Bytes('lost'), App.globalGet(Bytes('lost')) + App.localGet(Txn.sender(), Bytes('balance')))
+int 1                                  //    (254)  {COMPILE}
+return                                 //    (255)
+main_l17:                              //
+int 1                                  //    (256)  Approve()
+return                                 //    (257)
+main_l18:                              //           {COMPILE}
+txn ApplicationID                      //    (258)
+int 0                                  //    (260)
+==                                     //    (261)
+assert                                 //    (262)
+int 1                                  //    (263)  Approve()
+return                                 //    (264)
+                                       //           def assert_sender_is_creator() -> Expr:
+// assert_sender_is_creator            //
+assertsenderiscreator_0:               //
+txn Sender                             //    (265)  Txn.sender()
+global CreatorAddress                  //    (267)  Global.creator_address()
+==                                     //    (269)  Txn.sender() == Global.creator_address()
+assert                                 //    (270)  Assert(Txn.sender() == Global.creator_address())
+retsub                                 //    (271)  def assert_sender_is_creator() -> Expr:
+                                       //           def deposit(payment: abi.PaymentTransaction, sender: abi.Account) -> Expr:
+// deposit                             //
+deposit_1:                             //
+store 6                                //    (272)  {COMPILE}
+store 5                                //    (274)
+load 5                                 //    (276)  payment.get()
+gtxns Sender                           //    (278)  payment.get().sender()
+load 6                                 //    (280)  sender.address()
+txnas Accounts                         //    (282)
+==                                     //    (284)  payment.get().sender() == sender.address()
+assert                                 //    (285)  Assert(payment.get().sender() == sender.address())
+load 5                                 //    (286)  payment.get()
+gtxns Receiver                         //    (288)  payment.get().receiver()
+global CurrentApplicationAddress       //    (290)  Global.current_application_address()
+==                                     //    (292)  payment.get().receiver() == Global.current_application_address()
+assert                                 //    (293)  Assert(payment.get().receiver() == Global.current_application_address())
+load 6                                 //    (294)  sender.address()
+txnas Accounts                         //    (296)
+byte "balance"                         //    (298)  Bytes('balance')
+load 6                                 //    (299)  sender.address()
+txnas Accounts                         //    (301)
+byte "balance"                         //    (303)  Bytes('balance')
+app_local_get                          //    (304)  App.localGet(sender.address(), Bytes('balance'))
+load 5                                 //    (305)  payment.get()
+gtxns Amount                           //    (307)  payment.get().amount()
++                                      //    (309)  App.localGet(sender.address(), Bytes('balance')) + payment.get().amount()
+app_local_put                          //    (310)  App.localPut(sender.address(), Bytes('balance'), App.localGet(sender.address(), Bytes('balance')) + payment.get().amount())
+retsub                                 //    (311)  def deposit(payment: abi.PaymentTransaction, sender: abi.Account) -> Expr:
+                                       //           def getBalance(user: abi.Account, *, output: abi.Uint64) -> Expr:
+// getBalance                          //
+getBalance_2:                          //
+txnas Accounts                         //    (312)  user.address()
+byte "balance"                         //    (314)  Bytes('balance')
+app_local_get                          //    (315)  App.localGet(user.address(), Bytes('balance'))
+retsub                                 //    (316)  def getBalance(user: abi.Account, *, output: abi.Uint64) -> Expr:
+                                       //           def withdraw(amount: abi.Uint64, recipient: abi.Account) -> Expr:
+// withdraw                            //
+withdraw_3:                            //
+store 8                                //    (317)  {COMPILE}
+store 7                                //    (319)
+txn Sender                             //    (321)  Txn.sender()
+byte "balance"                         //    (323)  Bytes('balance')
+txn Sender                             //    (324)  Txn.sender()
+byte "balance"                         //    (326)  Bytes('balance')
+app_local_get                          //    (327)  App.localGet(Txn.sender(), Bytes('balance'))
+load 7                                 //    (328)  amount.get()
+-                                      //    (330)  App.localGet(Txn.sender(), Bytes('balance')) - amount.get()
+app_local_put                          //    (331)  App.localPut(Txn.sender(), Bytes('balance'), App.localGet(Txn.sender(), Bytes('balance')) - amount.get())
+itxn_begin                             //    (332)  InnerTxnBuilder.Begin()
+int pay                                //    (333)  def deposit(payment: abi.PaymentTransaction, sender: abi.Account) -> Expr:
+itxn_field TypeEnum                    //    (334)  {INNERTXN}
+load 8                                 //    (336)  recipient.address()
+txnas Accounts                         //    (338)
+itxn_field Receiver                    //    (340)  {INNERTXN}
+load 7                                 //    (342)  amount.get()
+itxn_field Amount                      //    (344)  {INNERTXN}
+int 0                                  //    (346)  Int(0)
+itxn_field Fee                         //    (347)  {INNERTXN}
+itxn_submit                            //    (349)  InnerTxnBuilder.Submit()
+retsub                                 //    (350)  def withdraw(amount: abi.Uint64, recipient: abi.Account) -> Expr:
\ No newline at end of file
diff --git a/tests/teal/__init__.py b/tests/teal/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/teal/rps.py b/tests/teal/rps.py
new file mode 100644
index 000000000..8b4756de4
--- /dev/null
+++ b/tests/teal/rps.py
@@ -0,0 +1,249 @@
+""" Rock Paper Scissors example taken from https://github.com/gr8h/project_rps (15 Feb 2023)
+"""
+
+from pyteal import *
+from pyteal.ast.bytes import Bytes
+from .rps_helpers import program
+
+
+def approval_program():
+
+    # locals
+    local_opponent = Bytes("opponent")  # byteslice
+    local_bet = Bytes("bet")  # uint64
+    local_private_play = Bytes("private_play")  # byteslice
+    local_play = Bytes("play")  # byteslice
+
+    op_challenge = Bytes("challenge")
+    op_accept = Bytes("accept")
+    op_reveal = Bytes("reveal")
+
+    @Subroutine(TealType.none)
+    def reset(account: Expr):
+        return Seq(
+            App.localPut(account, local_opponent, Bytes("")),
+            App.localPut(account, local_bet, Int(0)),
+            App.localPut(account, local_private_play, Bytes("")),
+            App.localPut(account, local_play, Bytes("")),
+        )
+
+    @Subroutine(TealType.uint64)
+    def is_account_empty(account: Expr):
+        return Return(
+            And(
+                App.localGet(account, local_opponent) == Bytes(""),
+                App.localGet(account, local_bet) == Int(0),
+                App.localGet(account, local_private_play) == Bytes(""),
+                App.localGet(account, local_play) == Bytes(""),
+            )
+        )
+
+    @Subroutine(TealType.uint64)
+    def is_valid_play(play: Expr):
+        first_ch = ScratchVar(TealType.bytes)
+        return Seq(
+            first_ch.store(Substring(play, Int(0), Int(1))),
+            Return(
+                Or(
+                    first_ch.load() == Bytes("r"),
+                    first_ch.load() == Bytes("p"),
+                    first_ch.load() == Bytes("s"),
+                )
+            ),
+        )
+
+    @Subroutine(TealType.uint64)
+    def play_value(p: Expr):
+        first_letter = ScratchVar()
+        return Seq(
+            first_letter.store(Substring(p, Int(0), Int(1))),
+            Return(
+                Cond(
+                    [first_letter.load() == Bytes("r"), Int(0)],
+                    [first_letter.load() == Bytes("p"), Int(1)],
+                    [first_letter.load() == Bytes("s"), Int(2)],
+                )
+            ),
+        )
+
+    @Subroutine(TealType.uint64)
+    def get_winner_account_index(challanger_play: Expr, opponent_play: Expr):
+        return Return(
+            Cond(
+                [challanger_play == opponent_play, Int(2)],  # a tie
+                [
+                    (challanger_play + Int(1)) % Int(3) == opponent_play,
+                    Int(1),
+                ],  # opponent wins
+                [
+                    (opponent_play + Int(1)) % Int(3) == challanger_play,
+                    Int(0),
+                ],  # challanger wins
+            )
+        )
+
+    @Subroutine(TealType.none)
+    def send_reward(account_index: Expr, amount: Expr):
+        return Seq(
+            InnerTxnBuilder.Begin(),
+            InnerTxnBuilder.SetFields(
+                {
+                    TxnField.type_enum: TxnType.Payment,
+                    TxnField.receiver: Txn.accounts[account_index],
+                    TxnField.amount: amount,
+                }
+            ),
+            InnerTxnBuilder.Submit(),
+        )
+
+    @Subroutine(TealType.none)
+    def create_challenge():
+        return Seq(
+            Assert(
+                And(
+                    # check that we have two txn, one for the payment
+                    Global.group_size() == Int(2),
+                    Txn.group_index() == Int(0),
+                    # security checks
+                    Gtxn[1].close_remainder_to() == Global.zero_address(),
+                    Gtxn[0].rekey_to() == Global.zero_address(),
+                    Gtxn[1].rekey_to() == Global.zero_address(),
+                    # check 2nd txn is a payment for the bet
+                    Gtxn[1].type_enum() == TxnType.Payment,
+                    # check the reciver is the application
+                    Gtxn[1].receiver() == Global.current_application_address(),
+                    # check the amount == the amount of the challanger
+                    Gtxn[1].amount() == App.localGet(Txn.accounts[1], local_bet),
+                    # check that the challanger is opted in
+                    App.optedIn(Txn.accounts[1], Global.current_application_id()),
+                    # check that challanger account is the correct account
+                    Txn.sender() == App.localGet(Txn.accounts[1], local_opponent),
+                    # check hashed/private play
+                    Txn.application_args.length() == Int(2),
+                    is_valid_play(Txn.application_args[1]),
+                )
+            ),
+            App.localPut(Txn.sender(), local_opponent, Txn.accounts[1]),
+            App.localPut(Txn.sender(), local_bet, Gtxn[1].amount()),
+            App.localPut(Txn.sender(), local_play, Txn.application_args[1]),
+            Approve(),
+        )
+
+    @Subroutine(TealType.none)
+    def accept_challenge():
+        return Seq(
+            Assert(
+                And(
+                    # check that we have two txn, one for the payment
+                    Global.group_size() == Int(2),
+                    Txn.group_index() == Int(0),
+                    # security checks
+                    Gtxn[1].close_remainder_to() == Global.zero_address(),
+                    Gtxn[0].rekey_to() == Global.zero_address(),
+                    Gtxn[1].rekey_to() == Global.zero_address(),
+                    # check 2nd txn is a payment for the bet
+                    Gtxn[1].type_enum() == TxnType.Payment,
+                    # check the reciver is the application
+                    Gtxn[1].receiver() == Global.current_application_address(),
+                    # check that the opponent is opted in
+                    App.optedIn(Txn.accounts[1], Global.current_application_id()),
+                    # check accounts avilability to play
+                    is_account_empty(Txn.sender()),
+                    is_account_empty(Txn.accounts[1]),
+                    # check hashed/private play
+                    Txn.application_args.length() == Int(2),
+                )
+            ),
+            App.localPut(Txn.sender(), local_opponent, Txn.accounts[1]),
+            App.localPut(Txn.sender(), local_bet, Gtxn[1].amount()),
+            App.localPut(Txn.sender(), local_private_play, Txn.application_args[1]),
+            Approve(),
+        )
+
+    @Subroutine(TealType.none)
+    def reveal():
+        challenger_play = ScratchVar(TealType.uint64)
+        opponent_play = ScratchVar(TealType.uint64)
+        winner_index = ScratchVar(TealType.uint64)
+        bet = ScratchVar(TealType.uint64)
+
+        return Seq(
+            Assert(
+                And(
+                    # check that we have one txn
+                    Global.group_size() == Int(1),
+                    Txn.group_index() == Int(0),
+                    # security checks
+                    Gtxn[0].rekey_to() == Global.zero_address(),
+                    # check accounts are opponents to each other
+                    App.localGet(Txn.sender(), local_opponent) == Txn.accounts[1],
+                    App.localGet(Txn.accounts[1], local_opponent) == Txn.sender(),
+                    # check accounts have the same bet
+                    App.localGet(Txn.sender(), local_bet)
+                    == App.localGet(Txn.accounts[1], local_bet),
+                    # check account has submit a play
+                    App.localGet(Txn.sender(), local_private_play) != Bytes(""),
+                    App.localGet(Txn.accounts[1], local_play) != Bytes(""),
+                    # the private_play is valid
+                    Txn.application_args.length() == Int(2),
+                    Sha256(Txn.application_args[1])
+                    == App.localGet(Int(0), local_private_play),
+                )
+            ),
+            challenger_play.store(play_value(Txn.application_args[1])),
+            opponent_play.store(play_value(App.localGet(Txn.accounts[1], local_play))),
+            bet.store(App.localGet(Txn.sender(), local_bet)),
+            winner_index.store(
+                get_winner_account_index(challenger_play.load(), opponent_play.load())
+            ),
+            If(winner_index.load() == Int(2))
+            .Then(
+                Seq(
+                    send_reward(Txn.sender(), bet.load()),
+                    send_reward(Txn.accounts[1], bet.load()),
+                )
+            )
+            .Else(Seq(send_reward(winner_index.load(), bet.load() * Int(2)))),
+            reset(Txn.sender()),
+            reset(Txn.accounts[1]),
+            Approve(),
+        )
+
+    return program.event(
+        init=Approve(),
+        opt_in=Seq(
+            reset(Int(0)),
+            Approve(),
+        ),
+        no_op=Seq(
+            Cond(
+                [
+                    Txn.application_args[0] == op_challenge,
+                    create_challenge(),
+                ],
+                [
+                    Txn.application_args[0] == op_accept,
+                    accept_challenge(),
+                ],
+                [
+                    Txn.application_args[0] == op_reveal,
+                    reveal(),
+                ],
+            ),
+            Reject(),
+        ),
+    )
+
+
+def clear_state_program():
+    return Int(0)
+
+
+if __name__ == "__main__":
+    with open("vote_approval.teal", "w") as f:
+        compiled = compileTeal(approval_program(), Mode.Application, version=6)
+        f.write(compiled)
+
+    with open("vote_clear_state.teal", "w") as f:
+        compiled = compileTeal(clear_state_program(), Mode.Application, version=6)
+        f.write(compiled)
diff --git a/tests/teal/rps.teal b/tests/teal/rps.teal
new file mode 100644
index 000000000..327d93b68
--- /dev/null
+++ b/tests/teal/rps.teal
@@ -0,0 +1,442 @@
+#pragma version 6
+txn ApplicationID
+int 0
+==
+bnz main_l19
+txn OnCompletion
+int DeleteApplication
+==
+bnz main_l18
+txn OnCompletion
+int UpdateApplication
+==
+bnz main_l17
+txn OnCompletion
+int OptIn
+==
+bnz main_l16
+txn OnCompletion
+int CloseOut
+==
+bnz main_l15
+txn OnCompletion
+int NoOp
+==
+bnz main_l7
+err
+main_l7:
+txna ApplicationArgs 0
+byte "challenge"
+==
+bnz main_l14
+txna ApplicationArgs 0
+byte "accept"
+==
+bnz main_l13
+txna ApplicationArgs 0
+byte "reveal"
+==
+bnz main_l11
+err
+main_l11:
+callsub reveal_8
+main_l12:
+int 0
+return
+main_l13:
+callsub acceptchallenge_7
+b main_l12
+main_l14:
+callsub createchallenge_6
+b main_l12
+main_l15:
+int 0
+return
+main_l16:
+int 0
+callsub reset_0
+int 1
+return
+main_l17:
+int 0
+return
+main_l18:
+int 0
+return
+main_l19:
+int 1
+return
+
+// reset
+reset_0:
+store 0
+load 0
+byte "opponent"
+byte ""
+app_local_put
+load 0
+byte "bet"
+int 0
+app_local_put
+load 0
+byte "private_play"
+byte ""
+app_local_put
+load 0
+byte "play"
+byte ""
+app_local_put
+retsub
+
+// is_account_empty
+isaccountempty_1:
+store 3
+load 3
+byte "opponent"
+app_local_get
+byte ""
+==
+load 3
+byte "bet"
+app_local_get
+int 0
+==
+&&
+load 3
+byte "private_play"
+app_local_get
+byte ""
+==
+&&
+load 3
+byte "play"
+app_local_get
+byte ""
+==
+&&
+retsub
+
+// is_valid_play
+isvalidplay_2:
+store 1
+load 1
+extract 0 1
+store 2
+load 2
+byte "r"
+==
+load 2
+byte "p"
+==
+||
+load 2
+byte "s"
+==
+||
+retsub
+
+// play_value
+playvalue_3:
+store 8
+load 8
+extract 0 1
+store 9
+load 9
+byte "r"
+==
+bnz playvalue_3_l6
+load 9
+byte "p"
+==
+bnz playvalue_3_l5
+load 9
+byte "s"
+==
+bnz playvalue_3_l4
+err
+playvalue_3_l4:
+int 2
+b playvalue_3_l7
+playvalue_3_l5:
+int 1
+b playvalue_3_l7
+playvalue_3_l6:
+int 0
+playvalue_3_l7:
+retsub
+
+// get_winner_account_index
+getwinneraccountindex_4:
+store 11
+store 10
+load 10
+load 11
+==
+bnz getwinneraccountindex_4_l6
+load 10
+int 1
++
+int 3
+%
+load 11
+==
+bnz getwinneraccountindex_4_l5
+load 11
+int 1
++
+int 3
+%
+load 10
+==
+bnz getwinneraccountindex_4_l4
+err
+getwinneraccountindex_4_l4:
+int 0
+b getwinneraccountindex_4_l7
+getwinneraccountindex_4_l5:
+int 1
+b getwinneraccountindex_4_l7
+getwinneraccountindex_4_l6:
+int 2
+getwinneraccountindex_4_l7:
+retsub
+
+// send_reward
+sendreward_5:
+store 13
+store 12
+itxn_begin
+int pay
+itxn_field TypeEnum
+load 12
+txnas Accounts
+itxn_field Receiver
+load 13
+itxn_field Amount
+itxn_submit
+retsub
+
+// create_challenge
+createchallenge_6:
+global GroupSize
+int 2
+==
+txn GroupIndex
+int 0
+==
+&&
+gtxn 1 CloseRemainderTo
+global ZeroAddress
+==
+&&
+gtxn 0 RekeyTo
+global ZeroAddress
+==
+&&
+gtxn 1 RekeyTo
+global ZeroAddress
+==
+&&
+gtxn 1 TypeEnum
+int pay
+==
+&&
+gtxn 1 Receiver
+global CurrentApplicationAddress
+==
+&&
+gtxn 1 Amount
+txna Accounts 1
+byte "bet"
+app_local_get
+==
+&&
+txna Accounts 1
+global CurrentApplicationID
+app_opted_in
+&&
+txn Sender
+txna Accounts 1
+byte "opponent"
+app_local_get
+==
+&&
+txn NumAppArgs
+int 2
+==
+&&
+txna ApplicationArgs 1
+callsub isvalidplay_2
+&&
+assert
+txn Sender
+byte "opponent"
+txna Accounts 1
+app_local_put
+txn Sender
+byte "bet"
+gtxn 1 Amount
+app_local_put
+txn Sender
+byte "play"
+txna ApplicationArgs 1
+app_local_put
+int 1
+return
+
+// accept_challenge
+acceptchallenge_7:
+global GroupSize
+int 2
+==
+txn GroupIndex
+int 0
+==
+&&
+gtxn 1 CloseRemainderTo
+global ZeroAddress
+==
+&&
+gtxn 0 RekeyTo
+global ZeroAddress
+==
+&&
+gtxn 1 RekeyTo
+global ZeroAddress
+==
+&&
+gtxn 1 TypeEnum
+int pay
+==
+&&
+gtxn 1 Receiver
+global CurrentApplicationAddress
+==
+&&
+txna Accounts 1
+global CurrentApplicationID
+app_opted_in
+&&
+txn Sender
+callsub isaccountempty_1
+&&
+txna Accounts 1
+callsub isaccountempty_1
+&&
+txn NumAppArgs
+int 2
+==
+&&
+assert
+txn Sender
+byte "opponent"
+txna Accounts 1
+app_local_put
+txn Sender
+byte "bet"
+gtxn 1 Amount
+app_local_put
+txn Sender
+byte "private_play"
+txna ApplicationArgs 1
+app_local_put
+int 1
+return
+
+// reveal
+reveal_8:
+global GroupSize
+int 1
+==
+txn GroupIndex
+int 0
+==
+&&
+gtxn 0 RekeyTo
+global ZeroAddress
+==
+&&
+txn Sender
+byte "opponent"
+app_local_get
+txna Accounts 1
+==
+&&
+txna Accounts 1
+byte "opponent"
+app_local_get
+txn Sender
+==
+&&
+txn Sender
+byte "bet"
+app_local_get
+txna Accounts 1
+byte "bet"
+app_local_get
+==
+&&
+txn Sender
+byte "private_play"
+app_local_get
+byte ""
+!=
+&&
+txna Accounts 1
+byte "play"
+app_local_get
+byte ""
+!=
+&&
+txn NumAppArgs
+int 2
+==
+&&
+txna ApplicationArgs 1
+sha256
+int 0
+byte "private_play"
+app_local_get
+==
+&&
+assert
+txna ApplicationArgs 1
+callsub playvalue_3
+store 4
+txna Accounts 1
+byte "play"
+app_local_get
+callsub playvalue_3
+store 5
+txn Sender
+byte "bet"
+app_local_get
+store 7
+load 4
+load 5
+callsub getwinneraccountindex_4
+store 6
+load 6
+int 2
+==
+bnz reveal_8_l2
+load 6
+load 7
+int 2
+*
+callsub sendreward_5
+b reveal_8_l3
+reveal_8_l2:
+txn Sender
+load 7
+callsub sendreward_5
+txna Accounts 1
+load 7
+callsub sendreward_5
+reveal_8_l3:
+txn Sender
+callsub reset_0
+txna Accounts 1
+callsub reset_0
+int 1
+return
\ No newline at end of file
diff --git a/tests/teal/rps_annotated.teal b/tests/teal/rps_annotated.teal
new file mode 100644
index 000000000..711f16cda
--- /dev/null
+++ b/tests/teal/rps_annotated.teal
@@ -0,0 +1,443 @@
+// GENERATED TEAL                 //    PYTEAL PATH                        LINE    PYTEAL
+#pragma version 6                 //    tests/unit/sourcemap_rps_test.py   46      Compilation(approval_program(), Mode.Application, version=6).compile(with_sourcemap=True, annotate_teal=True, annotate_teal_headers=True, annotate_teal_concise=False)
+txn ApplicationID                 //    tests/teal/rps_helpers/program.py  22      Txn.application_id()
+int 0                             //                                               Int(0)
+==                                //                                               Txn.application_id() == Int(0)
+bnz main_l19                      //
+txn OnCompletion                  //                                       23      Txn.on_completion()
+int DeleteApplication             //                                               Txn.on_completion() == OnComplete.DeleteApplication
+==                                //
+bnz main_l18                      //
+txn OnCompletion                  //                                       24      Txn.on_completion()
+int UpdateApplication             //                                               Txn.on_completion() == OnComplete.UpdateApplication
+==                                //
+bnz main_l17                      //
+txn OnCompletion                  //                                       25      Txn.on_completion()
+int OptIn                         //                                               Txn.on_completion() == OnComplete.OptIn
+==                                //
+bnz main_l16                      //
+txn OnCompletion                  //                                       26      Txn.on_completion()
+int CloseOut                      //                                               Txn.on_completion() == OnComplete.CloseOut
+==                                //
+bnz main_l15                      //
+txn OnCompletion                  //                                       27      Txn.on_completion()
+int NoOp                          //                                               Txn.on_completion() == OnComplete.NoOp
+==                                //
+bnz main_l7                       //
+err                               //                                       21      Cond([Txn.application_id() == Int(0), init], [Txn.on_completion() == OnComplete.DeleteApplication, delete], [Txn.on_completion() == OnComplete.UpdateApplication, update], [Txn.on_completion() == OnComplete.OptIn, opt_in], [Txn.on_completion() == OnComplete.CloseOut, close_out], [Txn.on_completion() == OnComplete.NoOp, no_op])
+main_l7:                          //    tests/teal/rps.py                  221     Txn.application_args[0] == op_challenge
+txna ApplicationArgs 0            //                                               Txn.application_args[0]
+byte "challenge"                  //                                       17      Bytes('challenge')
+==                                //                                       221     Txn.application_args[0] == op_challenge
+bnz main_l14                      //
+txna ApplicationArgs 0            //                                       225     Txn.application_args[0]
+byte "accept"                     //                                       18      Bytes('accept')
+==                                //                                       225     Txn.application_args[0] == op_accept
+bnz main_l13                      //
+txna ApplicationArgs 0            //                                       229     Txn.application_args[0]
+byte "reveal"                     //                                       19      Bytes('reveal')
+==                                //                                       229     Txn.application_args[0] == op_reveal
+bnz main_l11                      //
+err                               //                                       219     Cond([Txn.application_args[0] == op_challenge, create_challenge()], [Txn.application_args[0] == op_accept, accept_challenge()], [Txn.application_args[0] == op_reveal, reveal()])
+main_l11:                         //                                       229     Txn.application_args[0] == op_reveal
+callsub reveal_8                  //                                       230     reveal()
+main_l12:                         //                                       229     Txn.application_args[0] == op_reveal
+int 0                             //                                       233     Reject()
+return                            //
+main_l13:                         //                                       225     Txn.application_args[0] == op_accept
+callsub acceptchallenge_7         //                                       226     accept_challenge()
+b main_l12                        //    tests/unit/sourcemap_rps_test.py   46      Compilation(approval_program(), Mode.Application, version=6).compile(with_sourcemap=True, annotate_teal=True, annotate_teal_headers=True, annotate_teal_concise=False)
+main_l14:                         //    tests/teal/rps.py                  221     Txn.application_args[0] == op_challenge
+callsub createchallenge_6         //                                       222     create_challenge()
+b main_l12                        //    tests/unit/sourcemap_rps_test.py   46      Compilation(approval_program(), Mode.Application, version=6).compile(with_sourcemap=True, annotate_teal=True, annotate_teal_headers=True, annotate_teal_concise=False)
+main_l15:                         //    tests/teal/rps_helpers/program.py  26      Txn.on_completion() == OnComplete.CloseOut
+int 0                             //                                       18      Reject()
+return                            //
+main_l16:                         //                                       25      Txn.on_completion() == OnComplete.OptIn
+int 0                             //    tests/teal/rps.py                  215     Int(0)
+callsub reset_0                   //                                               reset(Int(0))
+int 1                             //                                       216     Approve()
+return                            //
+main_l17:                         //    tests/teal/rps_helpers/program.py  24      Txn.on_completion() == OnComplete.UpdateApplication
+int 0                             //                                       16      Reject()
+return                            //
+main_l18:                         //                                       23      Txn.on_completion() == OnComplete.DeleteApplication
+int 0                             //                                       15      Reject()
+return                            //
+main_l19:                         //                                       22      Txn.application_id() == Int(0)
+int 1                             //    tests/teal/rps.py                  213     Approve()
+return                            //
+                                  //                                       22      def reset(account: Expr):
+// reset                          //
+reset_0:                          //
+store 0                           //    tests/unit/sourcemap_rps_test.py   46      Compilation(approval_program(), Mode.Application, version=6).compile(with_sourcemap=True, annotate_teal=True, annotate_teal_headers=True, annotate_teal_concise=False)
+load 0                            //
+byte "opponent"                   //    tests/teal/rps.py                  12      Bytes('opponent')
+byte ""                           //                                       24      Bytes('')
+app_local_put                     //                                               App.localPut(account, local_opponent, Bytes(''))
+load 0                            //    tests/unit/sourcemap_rps_test.py   46      Compilation(approval_program(), Mode.Application, version=6).compile(with_sourcemap=True, annotate_teal=True, annotate_teal_headers=True, annotate_teal_concise=False)
+byte "bet"                        //    tests/teal/rps.py                  13      Bytes('bet')
+int 0                             //                                       25      Int(0)
+app_local_put                     //                                               App.localPut(account, local_bet, Int(0))
+load 0                            //    tests/unit/sourcemap_rps_test.py   46      Compilation(approval_program(), Mode.Application, version=6).compile(with_sourcemap=True, annotate_teal=True, annotate_teal_headers=True, annotate_teal_concise=False)
+byte "private_play"               //    tests/teal/rps.py                  14      Bytes('private_play')
+byte ""                           //                                       26      Bytes('')
+app_local_put                     //                                               App.localPut(account, local_private_play, Bytes(''))
+load 0                            //    tests/unit/sourcemap_rps_test.py   46      Compilation(approval_program(), Mode.Application, version=6).compile(with_sourcemap=True, annotate_teal=True, annotate_teal_headers=True, annotate_teal_concise=False)
+byte "play"                       //    tests/teal/rps.py                  15      Bytes('play')
+byte ""                           //                                       27      Bytes('')
+app_local_put                     //                                               App.localPut(account, local_play, Bytes(''))
+retsub                            //                                       22      def reset(account: Expr):
+                                  //                                       31      def is_account_empty(account: Expr):
+// is_account_empty               //
+isaccountempty_1:                 //
+store 3                           //    tests/unit/sourcemap_rps_test.py   46      Compilation(approval_program(), Mode.Application, version=6).compile(with_sourcemap=True, annotate_teal=True, annotate_teal_headers=True, annotate_teal_concise=False)
+load 3                            //
+byte "opponent"                   //    tests/teal/rps.py                  12      Bytes('opponent')
+app_local_get                     //                                       34      App.localGet(account, local_opponent)
+byte ""                           //                                               Bytes('')
+==                                //                                               App.localGet(account, local_opponent) == Bytes('')
+load 3                            //    tests/unit/sourcemap_rps_test.py   46      Compilation(approval_program(), Mode.Application, version=6).compile(with_sourcemap=True, annotate_teal=True, annotate_teal_headers=True, annotate_teal_concise=False)
+byte "bet"                        //    tests/teal/rps.py                  13      Bytes('bet')
+app_local_get                     //                                       35      App.localGet(account, local_bet)
+int 0                             //                                               Int(0)
+==                                //                                               App.localGet(account, local_bet) == Int(0)
+&&                                //                                       33      And(App.localGet(account, local_opponent) == Bytes(''), App.localGet(account, local_bet) == Int(0), App.localGet(account, local_private_play) == Bytes(''), App.localGet(account, local_play) == Bytes(''))
+load 3                            //    tests/unit/sourcemap_rps_test.py   46      Compilation(approval_program(), Mode.Application, version=6).compile(with_sourcemap=True, annotate_teal=True, annotate_teal_headers=True, annotate_teal_concise=False)
+byte "private_play"               //    tests/teal/rps.py                  14      Bytes('private_play')
+app_local_get                     //                                       36      App.localGet(account, local_private_play)
+byte ""                           //                                               Bytes('')
+==                                //                                               App.localGet(account, local_private_play) == Bytes('')
+&&                                //                                       33      And(App.localGet(account, local_opponent) == Bytes(''), App.localGet(account, local_bet) == Int(0), App.localGet(account, local_private_play) == Bytes(''), App.localGet(account, local_play) == Bytes(''))
+load 3                            //    tests/unit/sourcemap_rps_test.py   46      Compilation(approval_program(), Mode.Application, version=6).compile(with_sourcemap=True, annotate_teal=True, annotate_teal_headers=True, annotate_teal_concise=False)
+byte "play"                       //    tests/teal/rps.py                  15      Bytes('play')
+app_local_get                     //                                       37      App.localGet(account, local_play)
+byte ""                           //                                               Bytes('')
+==                                //                                               App.localGet(account, local_play) == Bytes('')
+&&                                //                                       33      And(App.localGet(account, local_opponent) == Bytes(''), App.localGet(account, local_bet) == Int(0), App.localGet(account, local_private_play) == Bytes(''), App.localGet(account, local_play) == Bytes(''))
+retsub                            //                                       31      def is_account_empty(account: Expr):
+                                  //                                       42      def is_valid_play(play: Expr):
+// is_valid_play                  //
+isvalidplay_2:                    //
+store 1                           //    tests/unit/sourcemap_rps_test.py   46      Compilation(approval_program(), Mode.Application, version=6).compile(with_sourcemap=True, annotate_teal=True, annotate_teal_headers=True, annotate_teal_concise=False)
+load 1                            //
+extract 0 1                       //    tests/teal/rps.py                  45      Substring(play, Int(0), Int(1))
+store 2                           //                                               first_ch.store(Substring(play, Int(0), Int(1)))
+load 2                            //                                       48      first_ch.load()
+byte "r"                          //                                               Bytes('r')
+==                                //                                               first_ch.load() == Bytes('r')
+load 2                            //                                       49      first_ch.load()
+byte "p"                          //                                               Bytes('p')
+==                                //                                               first_ch.load() == Bytes('p')
+||                                //                                       47      Or(first_ch.load() == Bytes('r'), first_ch.load() == Bytes('p'), first_ch.load() == Bytes('s'))
+load 2                            //                                       50      first_ch.load()
+byte "s"                          //                                               Bytes('s')
+==                                //                                               first_ch.load() == Bytes('s')
+||                                //                                       47      Or(first_ch.load() == Bytes('r'), first_ch.load() == Bytes('p'), first_ch.load() == Bytes('s'))
+retsub                            //                                       42      def is_valid_play(play: Expr):
+                                  //                                       56      def play_value(p: Expr):
+// play_value                     //
+playvalue_3:                      //
+store 8                           //    tests/unit/sourcemap_rps_test.py   46      Compilation(approval_program(), Mode.Application, version=6).compile(with_sourcemap=True, annotate_teal=True, annotate_teal_headers=True, annotate_teal_concise=False)
+load 8                            //
+extract 0 1                       //    tests/teal/rps.py                  59      Substring(p, Int(0), Int(1))
+store 9                           //                                               first_letter.store(Substring(p, Int(0), Int(1)))
+load 9                            //                                       62      first_letter.load()
+byte "r"                          //                                               Bytes('r')
+==                                //                                               first_letter.load() == Bytes('r')
+bnz playvalue_3_l6                //
+load 9                            //                                       63      first_letter.load()
+byte "p"                          //                                               Bytes('p')
+==                                //                                               first_letter.load() == Bytes('p')
+bnz playvalue_3_l5                //
+load 9                            //                                       64      first_letter.load()
+byte "s"                          //                                               Bytes('s')
+==                                //                                               first_letter.load() == Bytes('s')
+bnz playvalue_3_l4                //
+err                               //                                       61      Cond([first_letter.load() == Bytes('r'), Int(0)], [first_letter.load() == Bytes('p'), Int(1)], [first_letter.load() == Bytes('s'), Int(2)])
+playvalue_3_l4:                   //                                       64      first_letter.load() == Bytes('s')
+int 2                             //                                               Int(2)
+b playvalue_3_l7                  //    tests/unit/sourcemap_rps_test.py   46      Compilation(approval_program(), Mode.Application, version=6).compile(with_sourcemap=True, annotate_teal=True, annotate_teal_headers=True, annotate_teal_concise=False)
+playvalue_3_l5:                   //    tests/teal/rps.py                  63      first_letter.load() == Bytes('p')
+int 1                             //                                               Int(1)
+b playvalue_3_l7                  //    tests/unit/sourcemap_rps_test.py   46      Compilation(approval_program(), Mode.Application, version=6).compile(with_sourcemap=True, annotate_teal=True, annotate_teal_headers=True, annotate_teal_concise=False)
+playvalue_3_l6:                   //    tests/teal/rps.py                  62      first_letter.load() == Bytes('r')
+int 0                             //                                               Int(0)
+playvalue_3_l7:                   //                                               first_letter.load() == Bytes('r')
+retsub                            //                                       56      def play_value(p: Expr):
+                                  //                                       70      def get_winner_account_index(challanger_play: Expr, opponent_play: Expr):
+// get_winner_account_index       //
+getwinneraccountindex_4:          //
+store 11                          //    tests/unit/sourcemap_rps_test.py   46      Compilation(approval_program(), Mode.Application, version=6).compile(with_sourcemap=True, annotate_teal=True, annotate_teal_headers=True, annotate_teal_concise=False)
+store 10                          //
+load 10                           //
+load 11                           //
+==                                //    tests/teal/rps.py                  73      challanger_play == opponent_play
+bnz getwinneraccountindex_4_l6    //
+load 10                           //    tests/unit/sourcemap_rps_test.py   46      Compilation(approval_program(), Mode.Application, version=6).compile(with_sourcemap=True, annotate_teal=True, annotate_teal_headers=True, annotate_teal_concise=False)
+int 1                             //    tests/teal/rps.py                  75      Int(1)
++                                 //                                               challanger_play + Int(1)
+int 3                             //                                               Int(3)
+%                                 //                                               (challanger_play + Int(1)) % Int(3)
+load 11                           //    tests/unit/sourcemap_rps_test.py   46      Compilation(approval_program(), Mode.Application, version=6).compile(with_sourcemap=True, annotate_teal=True, annotate_teal_headers=True, annotate_teal_concise=False)
+==                                //    tests/teal/rps.py                  75      (challanger_play + Int(1)) % Int(3) == opponent_play
+bnz getwinneraccountindex_4_l5    //
+load 11                           //    tests/unit/sourcemap_rps_test.py   46      Compilation(approval_program(), Mode.Application, version=6).compile(with_sourcemap=True, annotate_teal=True, annotate_teal_headers=True, annotate_teal_concise=False)
+int 1                             //    tests/teal/rps.py                  79      Int(1)
++                                 //                                               opponent_play + Int(1)
+int 3                             //                                               Int(3)
+%                                 //                                               (opponent_play + Int(1)) % Int(3)
+load 10                           //    tests/unit/sourcemap_rps_test.py   46      Compilation(approval_program(), Mode.Application, version=6).compile(with_sourcemap=True, annotate_teal=True, annotate_teal_headers=True, annotate_teal_concise=False)
+==                                //    tests/teal/rps.py                  79      (opponent_play + Int(1)) % Int(3) == challanger_play
+bnz getwinneraccountindex_4_l4    //
+err                               //                                       72      Cond([challanger_play == opponent_play, Int(2)], [(challanger_play + Int(1)) % Int(3) == opponent_play, Int(1)], [(opponent_play + Int(1)) % Int(3) == challanger_play, Int(0)])
+getwinneraccountindex_4_l4:       //                                       79      (opponent_play + Int(1)) % Int(3) == challanger_play
+int 0                             //                                       80      Int(0)
+b getwinneraccountindex_4_l7      //    tests/unit/sourcemap_rps_test.py   46      Compilation(approval_program(), Mode.Application, version=6).compile(with_sourcemap=True, annotate_teal=True, annotate_teal_headers=True, annotate_teal_concise=False)
+getwinneraccountindex_4_l5:       //    tests/teal/rps.py                  75      (challanger_play + Int(1)) % Int(3) == opponent_play
+int 1                             //                                       76      Int(1)
+b getwinneraccountindex_4_l7      //    tests/unit/sourcemap_rps_test.py   46      Compilation(approval_program(), Mode.Application, version=6).compile(with_sourcemap=True, annotate_teal=True, annotate_teal_headers=True, annotate_teal_concise=False)
+getwinneraccountindex_4_l6:       //    tests/teal/rps.py                  73      challanger_play == opponent_play
+int 2                             //                                               Int(2)
+getwinneraccountindex_4_l7:       //                                               challanger_play == opponent_play
+retsub                            //                                       70      def get_winner_account_index(challanger_play: Expr, opponent_play: Expr):
+                                  //                                       86      def send_reward(account_index: Expr, amount: Expr):
+// send_reward                    //
+sendreward_5:                     //
+store 13                          //    tests/unit/sourcemap_rps_test.py   46      Compilation(approval_program(), Mode.Application, version=6).compile(with_sourcemap=True, annotate_teal=True, annotate_teal_headers=True, annotate_teal_concise=False)
+store 12                          //
+itxn_begin                        //    tests/teal/rps.py                  88      InnerTxnBuilder.Begin()
+int pay                           //                                       89      InnerTxnBuilder.SetFields({TxnField.type_enum: TxnType.Payment, TxnField.receiver: Txn.accounts[account_index], TxnField.amount: amount})
+itxn_field TypeEnum               //
+load 12                           //    tests/unit/sourcemap_rps_test.py   46      Compilation(approval_program(), Mode.Application, version=6).compile(with_sourcemap=True, annotate_teal=True, annotate_teal_headers=True, annotate_teal_concise=False)
+txnas Accounts                    //    tests/teal/rps.py                  92      Txn.accounts[account_index]
+itxn_field Receiver               //                                       89      InnerTxnBuilder.SetFields({TxnField.type_enum: TxnType.Payment, TxnField.receiver: Txn.accounts[account_index], TxnField.amount: amount})
+load 13                           //    tests/unit/sourcemap_rps_test.py   46      Compilation(approval_program(), Mode.Application, version=6).compile(with_sourcemap=True, annotate_teal=True, annotate_teal_headers=True, annotate_teal_concise=False)
+itxn_field Amount                 //    tests/teal/rps.py                  89      InnerTxnBuilder.SetFields({TxnField.type_enum: TxnType.Payment, TxnField.receiver: Txn.accounts[account_index], TxnField.amount: amount})
+itxn_submit                       //                                       96      InnerTxnBuilder.Submit()
+retsub                            //                                       86      def send_reward(account_index: Expr, amount: Expr):
+                                  //                                       100     def create_challenge():
+// create_challenge               //
+createchallenge_6:                //
+global GroupSize                  //                                       105     Global.group_size()
+int 2                             //                                               Int(2)
+==                                //                                               Global.group_size() == Int(2)
+txn GroupIndex                    //                                       106     Txn.group_index()
+int 0                             //                                               Int(0)
+==                                //                                               Txn.group_index() == Int(0)
+&&                                //                                       103     And(Global.group_size() == Int(2), Txn.group_index() == Int(0), Gtxn[1].close_remainder_to() == Global.zero_address(), Gtxn[0].rekey_to() == Global.zero_address(), Gtxn[1].rekey_to() == Global.zero_address(), Gtxn[1].type_enum() == TxnType.Payment, Gtxn[1].receiver() == Global.current_application_address(), Gtxn[1].amount() == App.localGet(Txn.accounts[1], local_bet), App.optedIn(Txn.accounts[1], Global.current_application_id()), Txn.sender() == App.localGet(Txn.accounts[1], local_opponent), Txn.application_args.length() == Int(2), is_valid_play(Txn.application_args[1]))
+gtxn 1 CloseRemainderTo           //                                       108     Gtxn[1].close_remainder_to()
+global ZeroAddress                //                                               Global.zero_address()
+==                                //                                               Gtxn[1].close_remainder_to() == Global.zero_address()
+&&                                //                                       103     And(Global.group_size() == Int(2), Txn.group_index() == Int(0), Gtxn[1].close_remainder_to() == Global.zero_address(), Gtxn[0].rekey_to() == Global.zero_address(), Gtxn[1].rekey_to() == Global.zero_address(), Gtxn[1].type_enum() == TxnType.Payment, Gtxn[1].receiver() == Global.current_application_address(), Gtxn[1].amount() == App.localGet(Txn.accounts[1], local_bet), App.optedIn(Txn.accounts[1], Global.current_application_id()), Txn.sender() == App.localGet(Txn.accounts[1], local_opponent), Txn.application_args.length() == Int(2), is_valid_play(Txn.application_args[1]))
+gtxn 0 RekeyTo                    //                                       109     Gtxn[0].rekey_to()
+global ZeroAddress                //                                               Global.zero_address()
+==                                //                                               Gtxn[0].rekey_to() == Global.zero_address()
+&&                                //                                       103     And(Global.group_size() == Int(2), Txn.group_index() == Int(0), Gtxn[1].close_remainder_to() == Global.zero_address(), Gtxn[0].rekey_to() == Global.zero_address(), Gtxn[1].rekey_to() == Global.zero_address(), Gtxn[1].type_enum() == TxnType.Payment, Gtxn[1].receiver() == Global.current_application_address(), Gtxn[1].amount() == App.localGet(Txn.accounts[1], local_bet), App.optedIn(Txn.accounts[1], Global.current_application_id()), Txn.sender() == App.localGet(Txn.accounts[1], local_opponent), Txn.application_args.length() == Int(2), is_valid_play(Txn.application_args[1]))
+gtxn 1 RekeyTo                    //                                       110     Gtxn[1].rekey_to()
+global ZeroAddress                //                                               Global.zero_address()
+==                                //                                               Gtxn[1].rekey_to() == Global.zero_address()
+&&                                //                                       103     And(Global.group_size() == Int(2), Txn.group_index() == Int(0), Gtxn[1].close_remainder_to() == Global.zero_address(), Gtxn[0].rekey_to() == Global.zero_address(), Gtxn[1].rekey_to() == Global.zero_address(), Gtxn[1].type_enum() == TxnType.Payment, Gtxn[1].receiver() == Global.current_application_address(), Gtxn[1].amount() == App.localGet(Txn.accounts[1], local_bet), App.optedIn(Txn.accounts[1], Global.current_application_id()), Txn.sender() == App.localGet(Txn.accounts[1], local_opponent), Txn.application_args.length() == Int(2), is_valid_play(Txn.application_args[1]))
+gtxn 1 TypeEnum                   //                                       112     Gtxn[1].type_enum()
+int pay                           //                                               Gtxn[1].type_enum() == TxnType.Payment
+==                                //
+&&                                //                                       103     And(Global.group_size() == Int(2), Txn.group_index() == Int(0), Gtxn[1].close_remainder_to() == Global.zero_address(), Gtxn[0].rekey_to() == Global.zero_address(), Gtxn[1].rekey_to() == Global.zero_address(), Gtxn[1].type_enum() == TxnType.Payment, Gtxn[1].receiver() == Global.current_application_address(), Gtxn[1].amount() == App.localGet(Txn.accounts[1], local_bet), App.optedIn(Txn.accounts[1], Global.current_application_id()), Txn.sender() == App.localGet(Txn.accounts[1], local_opponent), Txn.application_args.length() == Int(2), is_valid_play(Txn.application_args[1]))
+gtxn 1 Receiver                   //                                       114     Gtxn[1].receiver()
+global CurrentApplicationAddress  //                                               Global.current_application_address()
+==                                //                                               Gtxn[1].receiver() == Global.current_application_address()
+&&                                //                                       103     And(Global.group_size() == Int(2), Txn.group_index() == Int(0), Gtxn[1].close_remainder_to() == Global.zero_address(), Gtxn[0].rekey_to() == Global.zero_address(), Gtxn[1].rekey_to() == Global.zero_address(), Gtxn[1].type_enum() == TxnType.Payment, Gtxn[1].receiver() == Global.current_application_address(), Gtxn[1].amount() == App.localGet(Txn.accounts[1], local_bet), App.optedIn(Txn.accounts[1], Global.current_application_id()), Txn.sender() == App.localGet(Txn.accounts[1], local_opponent), Txn.application_args.length() == Int(2), is_valid_play(Txn.application_args[1]))
+gtxn 1 Amount                     //                                       116     Gtxn[1].amount()
+txna Accounts 1                   //                                               Txn.accounts[1]
+byte "bet"                        //                                       13      Bytes('bet')
+app_local_get                     //                                       116     App.localGet(Txn.accounts[1], local_bet)
+==                                //                                               Gtxn[1].amount() == App.localGet(Txn.accounts[1], local_bet)
+&&                                //                                       103     And(Global.group_size() == Int(2), Txn.group_index() == Int(0), Gtxn[1].close_remainder_to() == Global.zero_address(), Gtxn[0].rekey_to() == Global.zero_address(), Gtxn[1].rekey_to() == Global.zero_address(), Gtxn[1].type_enum() == TxnType.Payment, Gtxn[1].receiver() == Global.current_application_address(), Gtxn[1].amount() == App.localGet(Txn.accounts[1], local_bet), App.optedIn(Txn.accounts[1], Global.current_application_id()), Txn.sender() == App.localGet(Txn.accounts[1], local_opponent), Txn.application_args.length() == Int(2), is_valid_play(Txn.application_args[1]))
+txna Accounts 1                   //                                       118     Txn.accounts[1]
+global CurrentApplicationID       //                                               Global.current_application_id()
+app_opted_in                      //                                               App.optedIn(Txn.accounts[1], Global.current_application_id())
+&&                                //                                       103     And(Global.group_size() == Int(2), Txn.group_index() == Int(0), Gtxn[1].close_remainder_to() == Global.zero_address(), Gtxn[0].rekey_to() == Global.zero_address(), Gtxn[1].rekey_to() == Global.zero_address(), Gtxn[1].type_enum() == TxnType.Payment, Gtxn[1].receiver() == Global.current_application_address(), Gtxn[1].amount() == App.localGet(Txn.accounts[1], local_bet), App.optedIn(Txn.accounts[1], Global.current_application_id()), Txn.sender() == App.localGet(Txn.accounts[1], local_opponent), Txn.application_args.length() == Int(2), is_valid_play(Txn.application_args[1]))
+txn Sender                        //                                       120     Txn.sender()
+txna Accounts 1                   //                                               Txn.accounts[1]
+byte "opponent"                   //                                       12      Bytes('opponent')
+app_local_get                     //                                       120     App.localGet(Txn.accounts[1], local_opponent)
+==                                //                                               Txn.sender() == App.localGet(Txn.accounts[1], local_opponent)
+&&                                //                                       103     And(Global.group_size() == Int(2), Txn.group_index() == Int(0), Gtxn[1].close_remainder_to() == Global.zero_address(), Gtxn[0].rekey_to() == Global.zero_address(), Gtxn[1].rekey_to() == Global.zero_address(), Gtxn[1].type_enum() == TxnType.Payment, Gtxn[1].receiver() == Global.current_application_address(), Gtxn[1].amount() == App.localGet(Txn.accounts[1], local_bet), App.optedIn(Txn.accounts[1], Global.current_application_id()), Txn.sender() == App.localGet(Txn.accounts[1], local_opponent), Txn.application_args.length() == Int(2), is_valid_play(Txn.application_args[1]))
+txn NumAppArgs                    //                                       122     Txn.application_args.length()
+int 2                             //                                               Int(2)
+==                                //                                               Txn.application_args.length() == Int(2)
+&&                                //                                       103     And(Global.group_size() == Int(2), Txn.group_index() == Int(0), Gtxn[1].close_remainder_to() == Global.zero_address(), Gtxn[0].rekey_to() == Global.zero_address(), Gtxn[1].rekey_to() == Global.zero_address(), Gtxn[1].type_enum() == TxnType.Payment, Gtxn[1].receiver() == Global.current_application_address(), Gtxn[1].amount() == App.localGet(Txn.accounts[1], local_bet), App.optedIn(Txn.accounts[1], Global.current_application_id()), Txn.sender() == App.localGet(Txn.accounts[1], local_opponent), Txn.application_args.length() == Int(2), is_valid_play(Txn.application_args[1]))
+txna ApplicationArgs 1            //                                       123     Txn.application_args[1]
+callsub isvalidplay_2             //                                               is_valid_play(Txn.application_args[1])
+&&                                //                                       103     And(Global.group_size() == Int(2), Txn.group_index() == Int(0), Gtxn[1].close_remainder_to() == Global.zero_address(), Gtxn[0].rekey_to() == Global.zero_address(), Gtxn[1].rekey_to() == Global.zero_address(), Gtxn[1].type_enum() == TxnType.Payment, Gtxn[1].receiver() == Global.current_application_address(), Gtxn[1].amount() == App.localGet(Txn.accounts[1], local_bet), App.optedIn(Txn.accounts[1], Global.current_application_id()), Txn.sender() == App.localGet(Txn.accounts[1], local_opponent), Txn.application_args.length() == Int(2), is_valid_play(Txn.application_args[1]))
+assert                            //                                       102     Assert(And(Global.group_size() == Int(2), Txn.group_index() == Int(0), Gtxn[1].close_remainder_to() == Global.zero_address(), Gtxn[0].rekey_to() == Global.zero_address(), Gtxn[1].rekey_to() == Global.zero_address(), Gtxn[1].type_enum() == TxnType.Payment, Gtxn[1].receiver() == Global.current_application_address(), Gtxn[1].amount() == App.localGet(Txn.accounts[1], local_bet), App.optedIn(Txn.accounts[1], Global.current_application_id()), Txn.sender() == App.localGet(Txn.accounts[1], local_opponent), Txn.application_args.length() == Int(2), is_valid_play(Txn.application_args[1])))
+txn Sender                        //                                       126     Txn.sender()
+byte "opponent"                   //                                       12      Bytes('opponent')
+txna Accounts 1                   //                                       126     Txn.accounts[1]
+app_local_put                     //                                               App.localPut(Txn.sender(), local_opponent, Txn.accounts[1])
+txn Sender                        //                                       127     Txn.sender()
+byte "bet"                        //                                       13      Bytes('bet')
+gtxn 1 Amount                     //                                       127     Gtxn[1].amount()
+app_local_put                     //                                               App.localPut(Txn.sender(), local_bet, Gtxn[1].amount())
+txn Sender                        //                                       128     Txn.sender()
+byte "play"                       //                                       15      Bytes('play')
+txna ApplicationArgs 1            //                                       128     Txn.application_args[1]
+app_local_put                     //                                               App.localPut(Txn.sender(), local_play, Txn.application_args[1])
+int 1                             //                                       129     Approve()
+return                            //                                       100     def create_challenge():
+                                  //                                       133     def accept_challenge():
+// accept_challenge               //
+acceptchallenge_7:                //
+global GroupSize                  //                                       138     Global.group_size()
+int 2                             //                                               Int(2)
+==                                //                                               Global.group_size() == Int(2)
+txn GroupIndex                    //                                       139     Txn.group_index()
+int 0                             //                                               Int(0)
+==                                //                                               Txn.group_index() == Int(0)
+&&                                //                                       136     And(Global.group_size() == Int(2), Txn.group_index() == Int(0), Gtxn[1].close_remainder_to() == Global.zero_address(), Gtxn[0].rekey_to() == Global.zero_address(), Gtxn[1].rekey_to() == Global.zero_address(), Gtxn[1].type_enum() == TxnType.Payment, Gtxn[1].receiver() == Global.current_application_address(), App.optedIn(Txn.accounts[1], Global.current_application_id()), is_account_empty(Txn.sender()), is_account_empty(Txn.accounts[1]), Txn.application_args.length() == Int(2))
+gtxn 1 CloseRemainderTo           //                                       141     Gtxn[1].close_remainder_to()
+global ZeroAddress                //                                               Global.zero_address()
+==                                //                                               Gtxn[1].close_remainder_to() == Global.zero_address()
+&&                                //                                       136     And(Global.group_size() == Int(2), Txn.group_index() == Int(0), Gtxn[1].close_remainder_to() == Global.zero_address(), Gtxn[0].rekey_to() == Global.zero_address(), Gtxn[1].rekey_to() == Global.zero_address(), Gtxn[1].type_enum() == TxnType.Payment, Gtxn[1].receiver() == Global.current_application_address(), App.optedIn(Txn.accounts[1], Global.current_application_id()), is_account_empty(Txn.sender()), is_account_empty(Txn.accounts[1]), Txn.application_args.length() == Int(2))
+gtxn 0 RekeyTo                    //                                       142     Gtxn[0].rekey_to()
+global ZeroAddress                //                                               Global.zero_address()
+==                                //                                               Gtxn[0].rekey_to() == Global.zero_address()
+&&                                //                                       136     And(Global.group_size() == Int(2), Txn.group_index() == Int(0), Gtxn[1].close_remainder_to() == Global.zero_address(), Gtxn[0].rekey_to() == Global.zero_address(), Gtxn[1].rekey_to() == Global.zero_address(), Gtxn[1].type_enum() == TxnType.Payment, Gtxn[1].receiver() == Global.current_application_address(), App.optedIn(Txn.accounts[1], Global.current_application_id()), is_account_empty(Txn.sender()), is_account_empty(Txn.accounts[1]), Txn.application_args.length() == Int(2))
+gtxn 1 RekeyTo                    //                                       143     Gtxn[1].rekey_to()
+global ZeroAddress                //                                               Global.zero_address()
+==                                //                                               Gtxn[1].rekey_to() == Global.zero_address()
+&&                                //                                       136     And(Global.group_size() == Int(2), Txn.group_index() == Int(0), Gtxn[1].close_remainder_to() == Global.zero_address(), Gtxn[0].rekey_to() == Global.zero_address(), Gtxn[1].rekey_to() == Global.zero_address(), Gtxn[1].type_enum() == TxnType.Payment, Gtxn[1].receiver() == Global.current_application_address(), App.optedIn(Txn.accounts[1], Global.current_application_id()), is_account_empty(Txn.sender()), is_account_empty(Txn.accounts[1]), Txn.application_args.length() == Int(2))
+gtxn 1 TypeEnum                   //                                       145     Gtxn[1].type_enum()
+int pay                           //                                               Gtxn[1].type_enum() == TxnType.Payment
+==                                //
+&&                                //                                       136     And(Global.group_size() == Int(2), Txn.group_index() == Int(0), Gtxn[1].close_remainder_to() == Global.zero_address(), Gtxn[0].rekey_to() == Global.zero_address(), Gtxn[1].rekey_to() == Global.zero_address(), Gtxn[1].type_enum() == TxnType.Payment, Gtxn[1].receiver() == Global.current_application_address(), App.optedIn(Txn.accounts[1], Global.current_application_id()), is_account_empty(Txn.sender()), is_account_empty(Txn.accounts[1]), Txn.application_args.length() == Int(2))
+gtxn 1 Receiver                   //                                       147     Gtxn[1].receiver()
+global CurrentApplicationAddress  //                                               Global.current_application_address()
+==                                //                                               Gtxn[1].receiver() == Global.current_application_address()
+&&                                //                                       136     And(Global.group_size() == Int(2), Txn.group_index() == Int(0), Gtxn[1].close_remainder_to() == Global.zero_address(), Gtxn[0].rekey_to() == Global.zero_address(), Gtxn[1].rekey_to() == Global.zero_address(), Gtxn[1].type_enum() == TxnType.Payment, Gtxn[1].receiver() == Global.current_application_address(), App.optedIn(Txn.accounts[1], Global.current_application_id()), is_account_empty(Txn.sender()), is_account_empty(Txn.accounts[1]), Txn.application_args.length() == Int(2))
+txna Accounts 1                   //                                       149     Txn.accounts[1]
+global CurrentApplicationID       //                                               Global.current_application_id()
+app_opted_in                      //                                               App.optedIn(Txn.accounts[1], Global.current_application_id())
+&&                                //                                       136     And(Global.group_size() == Int(2), Txn.group_index() == Int(0), Gtxn[1].close_remainder_to() == Global.zero_address(), Gtxn[0].rekey_to() == Global.zero_address(), Gtxn[1].rekey_to() == Global.zero_address(), Gtxn[1].type_enum() == TxnType.Payment, Gtxn[1].receiver() == Global.current_application_address(), App.optedIn(Txn.accounts[1], Global.current_application_id()), is_account_empty(Txn.sender()), is_account_empty(Txn.accounts[1]), Txn.application_args.length() == Int(2))
+txn Sender                        //                                       151     Txn.sender()
+callsub isaccountempty_1          //                                               is_account_empty(Txn.sender())
+&&                                //                                       136     And(Global.group_size() == Int(2), Txn.group_index() == Int(0), Gtxn[1].close_remainder_to() == Global.zero_address(), Gtxn[0].rekey_to() == Global.zero_address(), Gtxn[1].rekey_to() == Global.zero_address(), Gtxn[1].type_enum() == TxnType.Payment, Gtxn[1].receiver() == Global.current_application_address(), App.optedIn(Txn.accounts[1], Global.current_application_id()), is_account_empty(Txn.sender()), is_account_empty(Txn.accounts[1]), Txn.application_args.length() == Int(2))
+txna Accounts 1                   //                                       152     Txn.accounts[1]
+callsub isaccountempty_1          //                                               is_account_empty(Txn.accounts[1])
+&&                                //                                       136     And(Global.group_size() == Int(2), Txn.group_index() == Int(0), Gtxn[1].close_remainder_to() == Global.zero_address(), Gtxn[0].rekey_to() == Global.zero_address(), Gtxn[1].rekey_to() == Global.zero_address(), Gtxn[1].type_enum() == TxnType.Payment, Gtxn[1].receiver() == Global.current_application_address(), App.optedIn(Txn.accounts[1], Global.current_application_id()), is_account_empty(Txn.sender()), is_account_empty(Txn.accounts[1]), Txn.application_args.length() == Int(2))
+txn NumAppArgs                    //                                       154     Txn.application_args.length()
+int 2                             //                                               Int(2)
+==                                //                                               Txn.application_args.length() == Int(2)
+&&                                //                                       136     And(Global.group_size() == Int(2), Txn.group_index() == Int(0), Gtxn[1].close_remainder_to() == Global.zero_address(), Gtxn[0].rekey_to() == Global.zero_address(), Gtxn[1].rekey_to() == Global.zero_address(), Gtxn[1].type_enum() == TxnType.Payment, Gtxn[1].receiver() == Global.current_application_address(), App.optedIn(Txn.accounts[1], Global.current_application_id()), is_account_empty(Txn.sender()), is_account_empty(Txn.accounts[1]), Txn.application_args.length() == Int(2))
+assert                            //                                       135     Assert(And(Global.group_size() == Int(2), Txn.group_index() == Int(0), Gtxn[1].close_remainder_to() == Global.zero_address(), Gtxn[0].rekey_to() == Global.zero_address(), Gtxn[1].rekey_to() == Global.zero_address(), Gtxn[1].type_enum() == TxnType.Payment, Gtxn[1].receiver() == Global.current_application_address(), App.optedIn(Txn.accounts[1], Global.current_application_id()), is_account_empty(Txn.sender()), is_account_empty(Txn.accounts[1]), Txn.application_args.length() == Int(2)))
+txn Sender                        //                                       157     Txn.sender()
+byte "opponent"                   //                                       12      Bytes('opponent')
+txna Accounts 1                   //                                       157     Txn.accounts[1]
+app_local_put                     //                                               App.localPut(Txn.sender(), local_opponent, Txn.accounts[1])
+txn Sender                        //                                       158     Txn.sender()
+byte "bet"                        //                                       13      Bytes('bet')
+gtxn 1 Amount                     //                                       158     Gtxn[1].amount()
+app_local_put                     //                                               App.localPut(Txn.sender(), local_bet, Gtxn[1].amount())
+txn Sender                        //                                       159     Txn.sender()
+byte "private_play"               //                                       14      Bytes('private_play')
+txna ApplicationArgs 1            //                                       159     Txn.application_args[1]
+app_local_put                     //                                               App.localPut(Txn.sender(), local_private_play, Txn.application_args[1])
+int 1                             //                                       160     Approve()
+return                            //                                       133     def accept_challenge():
+                                  //                                       164     def reveal():
+// reveal                         //
+reveal_8:                         //
+global GroupSize                  //                                       174     Global.group_size()
+int 1                             //                                               Int(1)
+==                                //                                               Global.group_size() == Int(1)
+txn GroupIndex                    //                                       175     Txn.group_index()
+int 0                             //                                               Int(0)
+==                                //                                               Txn.group_index() == Int(0)
+&&                                //                                       172     And(Global.group_size() == Int(1), Txn.group_index() == Int(0), Gtxn[0].rekey_to() == Global.zero_address(), App.localGet(Txn.sender(), local_opponent) == Txn.accounts[1], App.localGet(Txn.accounts[1], local_opponent) == Txn.sender(), App.localGet(Txn.sender(), local_bet) == App.localGet(Txn.accounts[1], local_bet), App.localGet(Txn.sender(), local_private_play) != Bytes(''), App.localGet(Txn.accounts[1], local_play) != Bytes(''), Txn.application_args.length() == Int(2), Sha256(Txn.application_args[1]) == App.localGet(Int(0), local_private_play))
+gtxn 0 RekeyTo                    //                                       177     Gtxn[0].rekey_to()
+global ZeroAddress                //                                               Global.zero_address()
+==                                //                                               Gtxn[0].rekey_to() == Global.zero_address()
+&&                                //                                       172     And(Global.group_size() == Int(1), Txn.group_index() == Int(0), Gtxn[0].rekey_to() == Global.zero_address(), App.localGet(Txn.sender(), local_opponent) == Txn.accounts[1], App.localGet(Txn.accounts[1], local_opponent) == Txn.sender(), App.localGet(Txn.sender(), local_bet) == App.localGet(Txn.accounts[1], local_bet), App.localGet(Txn.sender(), local_private_play) != Bytes(''), App.localGet(Txn.accounts[1], local_play) != Bytes(''), Txn.application_args.length() == Int(2), Sha256(Txn.application_args[1]) == App.localGet(Int(0), local_private_play))
+txn Sender                        //                                       179     Txn.sender()
+byte "opponent"                   //                                       12      Bytes('opponent')
+app_local_get                     //                                       179     App.localGet(Txn.sender(), local_opponent)
+txna Accounts 1                   //                                               Txn.accounts[1]
+==                                //                                               App.localGet(Txn.sender(), local_opponent) == Txn.accounts[1]
+&&                                //                                       172     And(Global.group_size() == Int(1), Txn.group_index() == Int(0), Gtxn[0].rekey_to() == Global.zero_address(), App.localGet(Txn.sender(), local_opponent) == Txn.accounts[1], App.localGet(Txn.accounts[1], local_opponent) == Txn.sender(), App.localGet(Txn.sender(), local_bet) == App.localGet(Txn.accounts[1], local_bet), App.localGet(Txn.sender(), local_private_play) != Bytes(''), App.localGet(Txn.accounts[1], local_play) != Bytes(''), Txn.application_args.length() == Int(2), Sha256(Txn.application_args[1]) == App.localGet(Int(0), local_private_play))
+txna Accounts 1                   //                                       180     Txn.accounts[1]
+byte "opponent"                   //                                       12      Bytes('opponent')
+app_local_get                     //                                       180     App.localGet(Txn.accounts[1], local_opponent)
+txn Sender                        //                                               Txn.sender()
+==                                //                                               App.localGet(Txn.accounts[1], local_opponent) == Txn.sender()
+&&                                //                                       172     And(Global.group_size() == Int(1), Txn.group_index() == Int(0), Gtxn[0].rekey_to() == Global.zero_address(), App.localGet(Txn.sender(), local_opponent) == Txn.accounts[1], App.localGet(Txn.accounts[1], local_opponent) == Txn.sender(), App.localGet(Txn.sender(), local_bet) == App.localGet(Txn.accounts[1], local_bet), App.localGet(Txn.sender(), local_private_play) != Bytes(''), App.localGet(Txn.accounts[1], local_play) != Bytes(''), Txn.application_args.length() == Int(2), Sha256(Txn.application_args[1]) == App.localGet(Int(0), local_private_play))
+txn Sender                        //                                       182     Txn.sender()
+byte "bet"                        //                                       13      Bytes('bet')
+app_local_get                     //                                       182     App.localGet(Txn.sender(), local_bet)
+txna Accounts 1                   //                                       183     Txn.accounts[1]
+byte "bet"                        //                                       13      Bytes('bet')
+app_local_get                     //                                       183     App.localGet(Txn.accounts[1], local_bet)
+==                                //                                       182     App.localGet(Txn.sender(), local_bet) == App.localGet(Txn.accounts[1], local_bet)
+&&                                //                                       172     And(Global.group_size() == Int(1), Txn.group_index() == Int(0), Gtxn[0].rekey_to() == Global.zero_address(), App.localGet(Txn.sender(), local_opponent) == Txn.accounts[1], App.localGet(Txn.accounts[1], local_opponent) == Txn.sender(), App.localGet(Txn.sender(), local_bet) == App.localGet(Txn.accounts[1], local_bet), App.localGet(Txn.sender(), local_private_play) != Bytes(''), App.localGet(Txn.accounts[1], local_play) != Bytes(''), Txn.application_args.length() == Int(2), Sha256(Txn.application_args[1]) == App.localGet(Int(0), local_private_play))
+txn Sender                        //                                       185     Txn.sender()
+byte "private_play"               //                                       14      Bytes('private_play')
+app_local_get                     //                                       185     App.localGet(Txn.sender(), local_private_play)
+byte ""                           //                                               Bytes('')
+!=                                //                                               App.localGet(Txn.sender(), local_private_play) != Bytes('')
+&&                                //                                       172     And(Global.group_size() == Int(1), Txn.group_index() == Int(0), Gtxn[0].rekey_to() == Global.zero_address(), App.localGet(Txn.sender(), local_opponent) == Txn.accounts[1], App.localGet(Txn.accounts[1], local_opponent) == Txn.sender(), App.localGet(Txn.sender(), local_bet) == App.localGet(Txn.accounts[1], local_bet), App.localGet(Txn.sender(), local_private_play) != Bytes(''), App.localGet(Txn.accounts[1], local_play) != Bytes(''), Txn.application_args.length() == Int(2), Sha256(Txn.application_args[1]) == App.localGet(Int(0), local_private_play))
+txna Accounts 1                   //                                       186     Txn.accounts[1]
+byte "play"                       //                                       15      Bytes('play')
+app_local_get                     //                                       186     App.localGet(Txn.accounts[1], local_play)
+byte ""                           //                                               Bytes('')
+!=                                //                                               App.localGet(Txn.accounts[1], local_play) != Bytes('')
+&&                                //                                       172     And(Global.group_size() == Int(1), Txn.group_index() == Int(0), Gtxn[0].rekey_to() == Global.zero_address(), App.localGet(Txn.sender(), local_opponent) == Txn.accounts[1], App.localGet(Txn.accounts[1], local_opponent) == Txn.sender(), App.localGet(Txn.sender(), local_bet) == App.localGet(Txn.accounts[1], local_bet), App.localGet(Txn.sender(), local_private_play) != Bytes(''), App.localGet(Txn.accounts[1], local_play) != Bytes(''), Txn.application_args.length() == Int(2), Sha256(Txn.application_args[1]) == App.localGet(Int(0), local_private_play))
+txn NumAppArgs                    //                                       188     Txn.application_args.length()
+int 2                             //                                               Int(2)
+==                                //                                               Txn.application_args.length() == Int(2)
+&&                                //                                       172     And(Global.group_size() == Int(1), Txn.group_index() == Int(0), Gtxn[0].rekey_to() == Global.zero_address(), App.localGet(Txn.sender(), local_opponent) == Txn.accounts[1], App.localGet(Txn.accounts[1], local_opponent) == Txn.sender(), App.localGet(Txn.sender(), local_bet) == App.localGet(Txn.accounts[1], local_bet), App.localGet(Txn.sender(), local_private_play) != Bytes(''), App.localGet(Txn.accounts[1], local_play) != Bytes(''), Txn.application_args.length() == Int(2), Sha256(Txn.application_args[1]) == App.localGet(Int(0), local_private_play))
+txna ApplicationArgs 1            //                                       189     Txn.application_args[1]
+sha256                            //                                               Sha256(Txn.application_args[1])
+int 0                             //                                       190     Int(0)
+byte "private_play"               //                                       14      Bytes('private_play')
+app_local_get                     //                                       190     App.localGet(Int(0), local_private_play)
+==                                //                                       189     Sha256(Txn.application_args[1]) == App.localGet(Int(0), local_private_play)
+&&                                //                                       172     And(Global.group_size() == Int(1), Txn.group_index() == Int(0), Gtxn[0].rekey_to() == Global.zero_address(), App.localGet(Txn.sender(), local_opponent) == Txn.accounts[1], App.localGet(Txn.accounts[1], local_opponent) == Txn.sender(), App.localGet(Txn.sender(), local_bet) == App.localGet(Txn.accounts[1], local_bet), App.localGet(Txn.sender(), local_private_play) != Bytes(''), App.localGet(Txn.accounts[1], local_play) != Bytes(''), Txn.application_args.length() == Int(2), Sha256(Txn.application_args[1]) == App.localGet(Int(0), local_private_play))
+assert                            //                                       171     Assert(And(Global.group_size() == Int(1), Txn.group_index() == Int(0), Gtxn[0].rekey_to() == Global.zero_address(), App.localGet(Txn.sender(), local_opponent) == Txn.accounts[1], App.localGet(Txn.accounts[1], local_opponent) == Txn.sender(), App.localGet(Txn.sender(), local_bet) == App.localGet(Txn.accounts[1], local_bet), App.localGet(Txn.sender(), local_private_play) != Bytes(''), App.localGet(Txn.accounts[1], local_play) != Bytes(''), Txn.application_args.length() == Int(2), Sha256(Txn.application_args[1]) == App.localGet(Int(0), local_private_play)))
+txna ApplicationArgs 1            //                                       193     Txn.application_args[1]
+callsub playvalue_3               //                                               play_value(Txn.application_args[1])
+store 4                           //                                               challenger_play.store(play_value(Txn.application_args[1]))
+txna Accounts 1                   //                                       194     Txn.accounts[1]
+byte "play"                       //                                       15      Bytes('play')
+app_local_get                     //                                       194     App.localGet(Txn.accounts[1], local_play)
+callsub playvalue_3               //                                               play_value(App.localGet(Txn.accounts[1], local_play))
+store 5                           //                                               opponent_play.store(play_value(App.localGet(Txn.accounts[1], local_play)))
+txn Sender                        //                                       195     Txn.sender()
+byte "bet"                        //                                       13      Bytes('bet')
+app_local_get                     //                                       195     App.localGet(Txn.sender(), local_bet)
+store 7                           //                                               bet.store(App.localGet(Txn.sender(), local_bet))
+load 4                            //                                       197     challenger_play.load()
+load 5                            //                                               opponent_play.load()
+callsub getwinneraccountindex_4   //                                               get_winner_account_index(challenger_play.load(), opponent_play.load())
+store 6                           //                                       196     winner_index.store(get_winner_account_index(challenger_play.load(), opponent_play.load()))
+load 6                            //                                       199     winner_index.load()
+int 2                             //                                               Int(2)
+==                                //                                               winner_index.load() == Int(2)
+bnz reveal_8_l2                   //                                               If(winner_index.load() == Int(2))
+load 6                            //                                       206     winner_index.load()
+load 7                            //                                               bet.load()
+int 2                             //                                               Int(2)
+*                                 //                                               bet.load() * Int(2)
+callsub sendreward_5              //                                               send_reward(winner_index.load(), bet.load() * Int(2))
+b reveal_8_l3                     //                                       199     If(winner_index.load() == Int(2))
+reveal_8_l2:                      //
+txn Sender                        //                                       202     Txn.sender()
+load 7                            //                                               bet.load()
+callsub sendreward_5              //                                               send_reward(Txn.sender(), bet.load())
+txna Accounts 1                   //                                       203     Txn.accounts[1]
+load 7                            //                                               bet.load()
+callsub sendreward_5              //                                               send_reward(Txn.accounts[1], bet.load())
+reveal_8_l3:                      //                                       199     If(winner_index.load() == Int(2))
+txn Sender                        //                                       207     Txn.sender()
+callsub reset_0                   //                                               reset(Txn.sender())
+txna Accounts 1                   //                                       208     Txn.accounts[1]
+callsub reset_0                   //                                               reset(Txn.accounts[1])
+int 1                             //                                       209     Approve()
+return                            //                                       164     def reveal():
\ No newline at end of file
diff --git a/tests/teal/rps_helpers/__init__.py b/tests/teal/rps_helpers/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/teal/rps_helpers/program.py b/tests/teal/rps_helpers/program.py
new file mode 100644
index 000000000..a6d137e86
--- /dev/null
+++ b/tests/teal/rps_helpers/program.py
@@ -0,0 +1,74 @@
+""" Rock Paper Scissors example taken from https://github.com/gr8h/project_rps (15 Feb 2023)
+"""
+
+from base64 import b64decode
+from dataclasses import dataclass
+from typing import Dict
+
+from algosdk.v2client.algod import AlgodClient
+from pyteal import *
+from pyteal.ast import *
+
+
+def event(
+    init: Expr = Reject(),
+    delete: Expr = Reject(),
+    update: Expr = Reject(),
+    opt_in: Expr = Reject(),
+    close_out: Expr = Reject(),
+    no_op: Expr = Reject(),
+) -> Expr:
+    return Cond(
+        [Txn.application_id() == Int(0), init],
+        [Txn.on_completion() == OnComplete.DeleteApplication, delete],
+        [Txn.on_completion() == OnComplete.UpdateApplication, update],
+        [Txn.on_completion() == OnComplete.OptIn, opt_in],
+        [Txn.on_completion() == OnComplete.CloseOut, close_out],
+        [Txn.on_completion() == OnComplete.NoOp, no_op],
+    )
+
+
+def check_rekey_zero(
+    num_transactions: int,
+):
+    return Assert(
+        And(
+            *[
+                Gtxn[i].rekey_to() == Global.zero_address()
+                for i in range(num_transactions)
+            ]
+        )
+    )
+
+
+def check_self(
+    group_size: Expr = Int(1),
+    group_index: Expr = Int(0),
+):
+    return Assert(
+        And(
+            Global.group_size() == group_size,
+            Txn.group_index() == group_index,
+        )
+    )
+
+
+def application(pyteal: Expr) -> str:
+    return compileTeal(pyteal, mode=Mode.Application, version=MAX_TEAL_VERSION)
+
+
+@dataclass
+class CompiledSignature:
+    address: str
+    bytecode_b64: str
+    teal: str
+
+
+def signature(algod_client: AlgodClient, pyteal: Expr) -> CompiledSignature:
+    teal = compileTeal(pyteal, mode=Mode.Signature, version=MAX_TEAL_VERSION)
+    compilation_result = algod_client.compile(teal)
+    return CompiledSignature(
+        address=compilation_result["hash"],
+        bytecode_b64=compilation_result["result"],
+        teal=teal,
+    )
diff --git a/tests/unit/blackbox_test.py b/tests/unit/blackbox_test.py
index b12b63e5f..5a5a15e65 100644
--- a/tests/unit/blackbox_test.py
+++ b/tests/unit/blackbox_test.py
@@ -1,3 +1,7 @@
+# TODO: add blackbox_test.py to multithreaded tests when following issue has been fixed:
+# 	https://github.com/algorand/pyteal/issues/199
+
+
 from itertools import product
 from pathlib import Path
 from typing import Literal, Optional, Tuple
@@ -213,6 +217,7 @@ def test_abi_blackbox_pyteal(
 @pytest.mark.parametrize(
     "fn, expected_is_abi", ((utest_noop, False), (fn_0arg_uint64_ret, True))
 )
+@pytest.mark.serial
 def test_PyTealBlackboxExecutor_is_abi(
     mode: pt.Mode, fn: BlackboxWrapper, expected_is_abi: bool
 ):
@@ -237,6 +242,7 @@ def test_PyTealBlackboxExecutor_is_abi(
         (fn_2mixed_arg_1ret, 2),
     ),
 )
+@pytest.mark.serial
 def test_PyTealBlackboxExecutor_abi_argument_types(
     mode: pt.Mode, fn: BlackboxWrapper, expected_arg_count: int
 ):
@@ -256,6 +262,7 @@ def test_PyTealBlackboxExecutor_abi_argument_types(
         (fn_2mixed_arg_1ret, True),
     ),
 )
+@pytest.mark.serial
 def test_PyTealBlackboxExecutor_abi_return_type(
     mode: pt.Mode, fn: BlackboxWrapper, expected_does_produce_type: bool
 ):
diff --git a/tests/unit/compile_test.py b/tests/unit/compile_test.py
index 427c7e4c6..aaafab772 100644
--- a/tests/unit/compile_test.py
+++ b/tests/unit/compile_test.py
@@ -6,12 +6,13 @@
 
 
 def test_abi_algobank():
-    from examples.application.abi.algobank import (
-        approval_program,
-        clear_state_program,
-        contract,
-    )
+    from pyteal.compiler.optimizer import OptimizeOptions
 
+    from examples.application.abi.algobank import router
+
+    approval_program, clear_state_program, contract = router.compile_program(
+        version=6, optimize=OptimizeOptions(scratch_slots=True)
+    )
     target_dir = Path.cwd() / "examples" / "application" / "abi"
 
     with open(
diff --git a/tests/unit/sourcemap_constructs311_test.py b/tests/unit/sourcemap_constructs311_test.py
new file mode 100644
index 000000000..9d4468a09
--- /dev/null
+++ b/tests/unit/sourcemap_constructs311_test.py
@@ -0,0 +1,2129 @@
+from configparser import ConfigParser
+from copy import deepcopy
+import sys
+from typing import Literal
+from unittest import mock
+
+import pytest
+
+from tests.unit.sourcemap_monkey_unit_test import router_static_abisubroutine
+
+
+@pytest.fixture
+def mock_ConfigParser():
+    patcher = mock.patch.object(ConfigParser, "getboolean", return_value=True)
+    patcher.start()
+    yield
+    patcher.stop()
+
+
+P = "#pragma version {v}"  # fill the template at runtime
+
+C = "comp._compile_impl(with_sourcemap=True)"
+R = "expr._build_impl(rci)"
+
+BIG_A = "pt.And(pt.Gtxn[0].rekey_to() == pt.Global.zero_address(), pt.Gtxn[1].rekey_to() == pt.Global.zero_address(), pt.Gtxn[2].rekey_to() == pt.Global.zero_address(), pt.Gtxn[3].rekey_to() == pt.Global.zero_address(), pt.Gtxn[4].rekey_to() == pt.Global.zero_address(), pt.Gtxn[0].last_valid() == pt.Gtxn[1].last_valid(), pt.Gtxn[1].last_valid() == pt.Gtxn[2].last_valid(), pt.Gtxn[2].last_valid() == pt.Gtxn[3].last_valid(), pt.Gtxn[3].last_valid() == pt.Gtxn[4].last_valid(), pt.Gtxn[0].type_enum() == pt.TxnType.AssetTransfer, pt.Gtxn[0].xfer_asset() == asset_c, pt.Gtxn[0].receiver() == receiver)"
+BIG_OR = "pt.Or(pt.App.globalGet(pt.Bytes('paused')), pt.App.localGet(pt.Int(0), pt.Bytes('frozen')), pt.App.localGet(pt.Int(1), pt.Bytes('frozen')), pt.App.localGet(pt.Int(0), pt.Bytes('lock until')) >= pt.Global.latest_timestamp(), pt.App.localGet(pt.Int(1), pt.Bytes('lock until')) >= pt.Global.latest_timestamp(), pt.App.globalGet(pt.Concat(pt.Bytes('rule'), pt.Itob(pt.App.localGet(pt.Int(0), pt.Bytes('transfer group'))), pt.Itob(pt.App.localGet(pt.Int(1), pt.Bytes('transfer group'))))))"
+BIG_C = "pt.Cond([pt.Txn.application_id() == pt.Int(0), foo], [pt.Txn.on_completion() == pt.OnComplete.DeleteApplication, pt.Return(is_admin)], [pt.Txn.on_completion() == pt.OnComplete.UpdateApplication, pt.Return(is_admin)], [pt.Txn.on_completion() == pt.OnComplete.CloseOut, foo], [pt.Txn.on_completion() == pt.OnComplete.OptIn, foo], [pt.Txn.application_args[0] == pt.Bytes('set admin'), foo], [pt.Txn.application_args[0] == pt.Bytes('mint'), foo], [pt.Txn.application_args[0] == pt.Bytes('transfer'), foo], [pt.Txn.accounts[4] == pt.Bytes('foo'), foo])"
+BIG_W = "pt.While(i.load() < pt.Global.group_size())"
+BIG_F = "pt.For(i.store(pt.Int(0)), i.load() < pt.Global.group_size(), i.store(i.load() + pt.Int(1)))"
+BIG_A2 = "pt.And(pt.Int(1) - pt.Int(2), pt.Not(pt.Int(3)), pt.Int(4) ^ pt.Int(5), ~pt.Int(6), pt.BytesEq(pt.Bytes('7'), pt.Bytes('8')), pt.GetBit(pt.Int(9), pt.Int(10)), pt.SetBit(pt.Int(11), pt.Int(12), pt.Int(13)), pt.GetByte(pt.Bytes('14'), pt.Int(15)), pt.Btoi(pt.Concat(pt.BytesDiv(pt.Bytes('101'), pt.Bytes('102')), pt.BytesNot(pt.Bytes('103')), pt.BytesZero(pt.Int(10)), pt.SetBit(pt.Bytes('105'), pt.Int(106), pt.Int(107)), pt.SetByte(pt.Bytes('108'), pt.Int(109), pt.Int(110)))))"
+BIG_C2 = "pt.Concat(pt.BytesDiv(pt.Bytes('101'), pt.Bytes('102')), pt.BytesNot(pt.Bytes('103')), pt.BytesZero(pt.Int(10)), pt.SetBit(pt.Bytes('105'), pt.Int(106), pt.Int(107)), pt.SetByte(pt.Bytes('108'), pt.Int(109), pt.Int(110)))"
+A = "def add(a: pt.abi.Uint64, b: pt.abi.Uint64, *, output: pt.abi.Uint64) -> pt.Expr:"
+RA = "router.add_method_handler(Foo.set_foo, 'set_foo', pt.MethodConfig(no_op=pt.CallConfig.CALL), 'Foo the foo')"
+
+
+def abi_named_tuple_example(pt):
+    NUM_OPTIONS = 0
+
+    class PollStatus(pt.abi.NamedTuple):
+        question: pt.abi.Field[pt.abi.String]
+        can_resubmit: pt.abi.Field[pt.abi.Bool]
+        is_open: pt.abi.Field[pt.abi.Bool]
+        results: pt.abi.Field[
+            pt.abi.StaticArray[
+                pt.abi.Tuple2[pt.abi.String, pt.abi.Uint64], Literal[NUM_OPTIONS]  # type: ignore
+            ]
+        ]
+
+    @pt.ABIReturnSubroutine
+    def status(*, output: PollStatus) -> pt.Expr:  # type: ignore
+        """Get the status of this poll.
+
+        Returns:
+            A tuple containing the following information, in order: the question is poll is asking,
+            whether the poll allows resubmission, whether the poll is open, and an array of the poll's
+            current results. This array contains one entry per option, and each entry is a tuple of that
+            option's value and the number of accounts who have voted for it.
+        """
+        question = pt.abi.make(pt.abi.String)
+        can_resubmit = pt.abi.make(pt.abi.Bool)
+        is_open = pt.abi.make(pt.abi.Bool)
+        results = pt.abi.make(pt.abi.StaticArray[pt.abi.Tuple2[pt.abi.String, pt.abi.Uint64], Literal[NUM_OPTIONS]])  # type: ignore
+        return pt.Seq(
+            question.set(pt.App.globalGet(pt.Bytes("1"))),
+            can_resubmit.set(pt.App.globalGet(pt.Bytes("2"))),
+            is_open.set(pt.App.globalGet(pt.Bytes("3"))),
+            results.set([]),
+            output.set(question, can_resubmit, is_open, results),
+        )
+
+    output = PollStatus()
+    return pt.Seq(status().store_into(output), pt.Int(1))  # type: ignore
+
+
+def abi_method_return_example(pt):
+    @pt.ABIReturnSubroutine
+    def abi_sum(
+        toSum: pt.abi.DynamicArray[pt.abi.Uint64], *, output: pt.abi.Uint64  # type: ignore
+    ) -> pt.Expr:  # type: ignore
+        i = pt.ScratchVar(pt.TealType.uint64)
+        valueAtIndex = pt.abi.Uint64()
+        return pt.Seq(
+            output.set(0),
+            pt.For(
+                i.store(pt.Int(0)),
+                i.load() < toSum.length(),
+                i.store(i.load() + pt.Int(1)),
+            ).Do(
+                pt.Seq(
+                    toSum[i.load()].store_into(valueAtIndex),
+                    output.set(output.get() + valueAtIndex.get()),
+                )
+            ),
+        )
+
+    return pt.Seq(
+        (to_sum_arr := pt.abi.make(pt.abi.DynamicArray[pt.abi.Uint64])).decode(
+            pt.Txn.application_args[1]
+        ),
+        (res := pt.abi.Uint64()).set(abi_sum(to_sum_arr)),  # type: ignore
+        pt.abi.MethodReturn(res),
+        pt.Approve(),
+    )
+
+
+def router_method_example(pt):
+    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())
+
+    @router.method
+    def add(a: pt.abi.Uint64, b: pt.abi.Uint64, *, output: pt.abi.Uint64) -> pt.Expr:  # type: ignore
+        return output.set(a.get() + b.get())
+
+    return router
+
+
+def get_fib_example(pt):
+    @pt.Subroutine(pt.TealType.uint64)
+    def fib(n) -> pt.Expr:  # type: ignore
+        return pt.If(
+            n <= pt.Int(1),
+            n,
+            fib(n - pt.Int(1)) + fib(n - pt.Int(2)),
+        )
+
+    return fib
+
+
+def recursive_subroutine_example(pt):
+    fib = get_fib_example(pt)
+    return fib(pt.Int(10))
+
+
+CONSTRUCTS_LATEST_VERSION = 8
+
+
+def test_constructs_handles_latest_pyteal():
+    import pyteal as pt
+
+    assert CONSTRUCTS_LATEST_VERSION == pt.MAX_PROGRAM_VERSION
+
+
+CONSTRUCTS = [
+    (  # 0: Int
+        lambda pt: pt.Return(pt.Int(42)),
+        [[P, C], ["int 42", "pt.Int(42)"], ["return", "pt.Return(pt.Int(42))"]],
+    ),
+    (lambda pt: pt.Int(42), [[P, C], ["int 42", "pt.Int(42)"], ["return", C]]),
+    (  # 2: Bytes
+        lambda pt: pt.Seq(pt.Pop(pt.Bytes("hello world")), pt.Int(1)),
+        [
+            [P, C],
+            ['byte "hello world"', "pt.Bytes('hello world')"],
+            ["pop", "pt.Pop(pt.Bytes('hello world'))"],
+            ["int 1", "pt.Int(1)"],
+            ["return", C],
+        ],
+    ),
+    (  # 3: *
+        lambda pt: pt.Int(2) * pt.Int(3),
+        [
+            [P, C],
+            ["int 2", "pt.Int(2)"],
+            ["int 3", "pt.Int(3)"],
+            ["*", "pt.Int(2) * pt.Int(3)"],
+            ["return", C],
+        ],
+    ),
+    (  # 4: ^
+        lambda pt: pt.Int(2) ^ pt.Int(3),
+        [
+            [P, C],
+            ["int 2", "pt.Int(2)"],
+            ["int 3", "pt.Int(3)"],
+            ["^", "pt.Int(2) ^ pt.Int(3)"],
+            ["return", C],
+        ],
+    ),
+    (  # 5: +*
+        lambda pt: pt.Int(1) + pt.Int(2) * pt.Int(3),
+        [
+            [P, C],
+            ["int 1", "pt.Int(1)"],
+            ["int 2", "pt.Int(2)"],
+            ["int 3", "pt.Int(3)"],
+            ["*", "pt.Int(2) * pt.Int(3)"],
+            ["+", "pt.Int(1) + pt.Int(2) * pt.Int(3)"],
+            ["return", C],
+        ],
+    ),
+    (  # 6: ~
+        lambda pt: ~pt.Int(1),
+        [[P, C], ["int 1", "pt.Int(1)"], ["~", "~pt.Int(1)"], ["return", C]],
+    ),
+    (  # 7: And Or
+        lambda pt: pt.And(
+            pt.Int(1),
+            pt.Int(2),
+            pt.Or(pt.Int(3), pt.Int(4), pt.Or(pt.And(pt.Int(5), pt.Int(6)))),
+        ),
+        [
+            [P, C],
+            ["int 1", "pt.Int(1)"],
+            ["int 2", "pt.Int(2)"],
+            [
+                "&&",
+                "pt.And(pt.Int(1), pt.Int(2), pt.Or(pt.Int(3), pt.Int(4), pt.Or(pt.And(pt.Int(5), pt.Int(6)))))",
+            ],
+            ["int 3", "pt.Int(3)"],
+            ["int 4", "pt.Int(4)"],
+            [
+                "||",
+                "pt.Or(pt.Int(3), pt.Int(4), pt.Or(pt.And(pt.Int(5), pt.Int(6))))",
+            ],
+            ["int 5", "pt.Int(5)"],
+            ["int 6", "pt.Int(6)"],
+            ["&&", "pt.And(pt.Int(5), pt.Int(6))"],
+            [
+                "||",
+                "pt.Or(pt.Int(3), pt.Int(4), pt.Or(pt.And(pt.Int(5), pt.Int(6))))",
+            ],
+            [
+                "&&",
+                "pt.And(pt.Int(1), pt.Int(2), pt.Or(pt.Int(3), pt.Int(4), pt.Or(pt.And(pt.Int(5), pt.Int(6)))))",
+            ],
+            ["return", C],
+        ],
+    ),
+    (  # 8: Btoi BytesAnd
+        lambda pt: pt.Btoi(
+            pt.BytesAnd(pt.Bytes("base16", "0xBEEF"), pt.Bytes("base16", "0x1337"))
+        ),
+        [
+            [P, C],
+            ["byte 0xBEEF", "pt.Bytes('base16', '0xBEEF')"],
+            ["byte 0x1337", "pt.Bytes('base16', '0x1337')"],
+            [
+                "b&",
+                "pt.BytesAnd(pt.Bytes('base16', '0xBEEF'), pt.Bytes('base16', '0x1337'))",
+            ],
+            [
+                "btoi",
+                "pt.Btoi(pt.BytesAnd(pt.Bytes('base16', '0xBEEF'), pt.Bytes('base16', '0x1337')))",
+            ],
+            ["return", C],
+        ],
+        4,
+    ),
+    (  # 9: Btoi BytesZero
+        lambda pt: pt.Btoi(pt.BytesZero(pt.Int(4))),
+        [
+            [P, C],
+            ["int 4", "pt.Int(4)"],
+            ["bzero", "pt.BytesZero(pt.Int(4))"],
+            ["btoi", "pt.Btoi(pt.BytesZero(pt.Int(4)))"],
+            ["return", C],
+        ],
+        4,
+    ),
+    (  # 10: BytesNot
+        lambda pt: pt.Btoi(pt.BytesNot(pt.Bytes("base16", "0xFF00"))),
+        [
+            [P, C],
+            ["byte 0xFF00", "pt.Bytes('base16', '0xFF00')"],
+            ["b~", "pt.BytesNot(pt.Bytes('base16', '0xFF00'))"],
+            ["btoi", "pt.Btoi(pt.BytesNot(pt.Bytes('base16', '0xFF00')))"],
+            ["return", C],
+        ],
+        4,
+    ),
+    (  # 11: Get/SetBit
+        lambda pt: pt.Seq(
+            pt.Pop(pt.SetBit(pt.Bytes("base16", "0x00"), pt.Int(3), pt.Int(1))),
+            pt.GetBit(pt.Int(16), pt.Int(64)),
+        ),
+        [
+            [P, C],
+            ("byte 0x00", "pt.Bytes('base16', '0x00')"),
+            ("int 3", "pt.Int(3)"),
+            ("int 1", "pt.Int(1)"),
+            (
+                "setbit",
+                "pt.SetBit(pt.Bytes('base16', '0x00'), pt.Int(3), pt.Int(1))",
+            ),
+            (
+                "pop",
+                "pt.Pop(pt.SetBit(pt.Bytes('base16', '0x00'), pt.Int(3), pt.Int(1)))",
+            ),
+            ("int 16", "pt.Int(16)"),
+            ("int 64", "pt.Int(64)"),
+            ("getbit", "pt.GetBit(pt.Int(16), pt.Int(64))"),
+            ["return", C],
+        ],
+        3,
+    ),
+    (  # 12: Get/SetByte
+        lambda pt: pt.Seq(
+            pt.Pop(pt.SetByte(pt.Bytes("base16", "0xff00"), pt.Int(0), pt.Int(0))),
+            pt.GetByte(pt.Bytes("abc"), pt.Int(2)),
+        ),
+        [
+            [P, C],
+            ("byte 0xff00", "pt.Bytes('base16', '0xff00')"),
+            ("int 0", "pt.Int(0)"),
+            ("int 0", "pt.Int(0)"),
+            (
+                "setbyte",
+                "pt.SetByte(pt.Bytes('base16', '0xff00'), pt.Int(0), pt.Int(0))",
+            ),
+            (
+                "pop",
+                "pt.Pop(pt.SetByte(pt.Bytes('base16', '0xff00'), pt.Int(0), pt.Int(0)))",
+            ),
+            ('byte "abc"', "pt.Bytes('abc')"),
+            ("int 2", "pt.Int(2)"),
+            ("getbyte", "pt.GetByte(pt.Bytes('abc'), pt.Int(2))"),
+            ["return", C],
+        ],
+        3,
+    ),
+    (  # 13: Concat
+        lambda pt: pt.Btoi(pt.Concat(pt.Bytes("a"), pt.Bytes("b"), pt.Bytes("c"))),
+        [
+            [P, C],
+            ('byte "a"', "pt.Bytes('a')"),
+            ('byte "b"', "pt.Bytes('b')"),
+            ("concat", "pt.Concat(pt.Bytes('a'), pt.Bytes('b'), pt.Bytes('c'))"),
+            ('byte "c"', "pt.Bytes('c')"),
+            ("concat", "pt.Concat(pt.Bytes('a'), pt.Bytes('b'), pt.Bytes('c'))"),
+            (
+                "btoi",
+                "pt.Btoi(pt.Concat(pt.Bytes('a'), pt.Bytes('b'), pt.Bytes('c')))",
+            ),
+            ["return", C],
+        ],
+    ),
+    (  # 14: Substring
+        lambda pt: pt.Btoi(pt.Substring(pt.Bytes("algorand"), pt.Int(2), pt.Int(8))),
+        [
+            [P, C],
+            ('byte "algorand"', "pt.Bytes('algorand')"),
+            (
+                "extract 2 6",
+                "pt.Substring(pt.Bytes('algorand'), pt.Int(2), pt.Int(8))",
+            ),
+            (
+                "btoi",
+                "pt.Btoi(pt.Substring(pt.Bytes('algorand'), pt.Int(2), pt.Int(8)))",
+            ),
+            (
+                "return",
+                C,
+            ),
+        ],
+        5,
+    ),
+    (  # 15: Extract
+        lambda pt: pt.Btoi(pt.Extract(pt.Bytes("algorand"), pt.Int(2), pt.Int(6))),
+        [
+            [P, C],
+            ('byte "algorand"', "pt.Bytes('algorand')"),
+            (
+                "extract 2 6",
+                "pt.Extract(pt.Bytes('algorand'), pt.Int(2), pt.Int(6))",
+            ),
+            (
+                "btoi",
+                "pt.Btoi(pt.Extract(pt.Bytes('algorand'), pt.Int(2), pt.Int(6)))",
+            ),
+            ["return", C],
+        ],
+        5,
+    ),
+    (  # 16: Txn
+        lambda pt: pt.And(
+            pt.Txn.type_enum() == pt.TxnType.Payment,
+            pt.Txn.fee() < pt.Int(100),
+            pt.Txn.first_valid() % pt.Int(50) == pt.Int(0),
+            pt.Txn.last_valid() == pt.Int(5000) + pt.Txn.first_valid(),
+            pt.Txn.lease() == pt.Bytes("base64", "023sdDE2"),
+        ),
+        [
+            [P, C],
+            ("txn TypeEnum", "pt.Txn.type_enum()"),
+            ("int pay", "pt.Txn.type_enum() == pt.TxnType.Payment"),
+            ("==", "pt.Txn.type_enum() == pt.TxnType.Payment"),
+            ("txn Fee", "pt.Txn.fee()"),
+            ("int 100", "pt.Int(100)"),
+            ("<", "pt.Txn.fee() < pt.Int(100)"),
+            (
+                "&&",
+                "pt.And(pt.Txn.type_enum() == pt.TxnType.Payment, pt.Txn.fee() < pt.Int(100), pt.Txn.first_valid() % pt.Int(50) == pt.Int(0), pt.Txn.last_valid() == pt.Int(5000) + pt.Txn.first_valid(), pt.Txn.lease() == pt.Bytes('base64', '023sdDE2'))",
+            ),
+            ("txn FirstValid", "pt.Txn.first_valid()"),
+            ("int 50", "pt.Int(50)"),
+            ("%", "pt.Txn.first_valid() % pt.Int(50)"),
+            ("int 0", "pt.Int(0)"),
+            ("==", "pt.Txn.first_valid() % pt.Int(50) == pt.Int(0)"),
+            (
+                "&&",
+                "pt.And(pt.Txn.type_enum() == pt.TxnType.Payment, pt.Txn.fee() < pt.Int(100), pt.Txn.first_valid() % pt.Int(50) == pt.Int(0), pt.Txn.last_valid() == pt.Int(5000) + pt.Txn.first_valid(), pt.Txn.lease() == pt.Bytes('base64', '023sdDE2'))",
+            ),
+            ("txn LastValid", "pt.Txn.last_valid()"),
+            ("int 5000", "pt.Int(5000)"),
+            ("txn FirstValid", "pt.Txn.first_valid()"),
+            ("+", "pt.Int(5000) + pt.Txn.first_valid()"),
+            ("==", "pt.Txn.last_valid() == pt.Int(5000) + pt.Txn.first_valid()"),
+            (
+                "&&",
+                "pt.And(pt.Txn.type_enum() == pt.TxnType.Payment, pt.Txn.fee() < pt.Int(100), pt.Txn.first_valid() % pt.Int(50) == pt.Int(0), pt.Txn.last_valid() == pt.Int(5000) + pt.Txn.first_valid(), pt.Txn.lease() == pt.Bytes('base64', '023sdDE2'))",
+            ),
+            ("txn Lease", "pt.Txn.lease()"),
+            ("byte base64(023sdDE2)", "pt.Bytes('base64', '023sdDE2')"),
+            ("==", "pt.Txn.lease() == pt.Bytes('base64', '023sdDE2')"),
+            (
+                "&&",
+                "pt.And(pt.Txn.type_enum() == pt.TxnType.Payment, pt.Txn.fee() < pt.Int(100), pt.Txn.first_valid() % pt.Int(50) == pt.Int(0), pt.Txn.last_valid() == pt.Int(5000) + pt.Txn.first_valid(), pt.Txn.lease() == pt.Bytes('base64', '023sdDE2'))",
+            ),
+            ["return", C],
+        ],
+    ),
+    (  # 17: Cond
+        lambda pt: [
+            is_admin := pt.App.localGet(pt.Int(0), pt.Bytes("admin")),
+            foo := pt.Return(pt.Int(1)),
+            pt.Cond(
+                [pt.Txn.application_id() == pt.Int(0), foo],
+                [
+                    pt.Txn.on_completion() == pt.OnComplete.DeleteApplication,
+                    pt.Return(is_admin),
+                ],
+                [
+                    pt.Txn.on_completion() == pt.OnComplete.UpdateApplication,
+                    pt.Return(is_admin),
+                ],
+                [pt.Txn.on_completion() == pt.OnComplete.CloseOut, foo],
+                [pt.Txn.on_completion() == pt.OnComplete.OptIn, foo],
+                [pt.Txn.application_args[0] == pt.Bytes("set admin"), foo],
+                [pt.Txn.application_args[0] == pt.Bytes("mint"), foo],
+                [pt.Txn.application_args[0] == pt.Bytes("transfer"), foo],
+                [pt.Txn.accounts[4] == pt.Bytes("foo"), foo],
+            ),
+        ][-1],
+        [
+            [P, C],
+            ("txn ApplicationID", "pt.Txn.application_id()"),
+            ("int 0", "pt.Int(0)"),
+            ("==", "pt.Txn.application_id() == pt.Int(0)"),
+            ("bnz main_l18", "pt.Txn.application_id() == pt.Int(0)"),
+            ("txn OnCompletion", "pt.Txn.on_completion()"),
+            (
+                "int DeleteApplication",
+                "pt.Txn.on_completion() == pt.OnComplete.DeleteApplication",
+            ),  # source inferencing at work here!!!!
+            ("==", "pt.Txn.on_completion() == pt.OnComplete.DeleteApplication"),
+            (
+                "bnz main_l17",
+                "pt.Txn.on_completion() == pt.OnComplete.DeleteApplication",
+            ),
+            ("txn OnCompletion", "pt.Txn.on_completion()"),
+            (
+                "int UpdateApplication",
+                "pt.Txn.on_completion() == pt.OnComplete.UpdateApplication",
+            ),  # source inferencing
+            ("==", "pt.Txn.on_completion() == pt.OnComplete.UpdateApplication"),
+            (
+                "bnz main_l16",
+                "pt.Txn.on_completion() == pt.OnComplete.UpdateApplication",
+            ),  # yep
+            ("txn OnCompletion", "pt.Txn.on_completion()"),
+            ("int CloseOut", "pt.Txn.on_completion() == pt.OnComplete.CloseOut"),
+            ("==", "pt.Txn.on_completion() == pt.OnComplete.CloseOut"),
+            ("bnz main_l15", "pt.Txn.on_completion() == pt.OnComplete.CloseOut"),
+            ("txn OnCompletion", "pt.Txn.on_completion()"),
+            ("int OptIn", "pt.Txn.on_completion() == pt.OnComplete.OptIn"),
+            ("==", "pt.Txn.on_completion() == pt.OnComplete.OptIn"),
+            ("bnz main_l14", "pt.Txn.on_completion() == pt.OnComplete.OptIn"),
+            ("txna ApplicationArgs 0", "pt.Txn.application_args[0]"),
+            ('byte "set admin"', "pt.Bytes('set admin')"),
+            ("==", "pt.Txn.application_args[0] == pt.Bytes('set admin')"),
+            ("bnz main_l13", "pt.Txn.application_args[0] == pt.Bytes('set admin')"),
+            ("txna ApplicationArgs 0", "pt.Txn.application_args[0]"),
+            ('byte "mint"', "pt.Bytes('mint')"),
+            ("==", "pt.Txn.application_args[0] == pt.Bytes('mint')"),
+            ("bnz main_l12", "pt.Txn.application_args[0] == pt.Bytes('mint')"),
+            ("txna ApplicationArgs 0", "pt.Txn.application_args[0]"),
+            ('byte "transfer"', "pt.Bytes('transfer')"),
+            ("==", "pt.Txn.application_args[0] == pt.Bytes('transfer')"),
+            ("bnz main_l11", "pt.Txn.application_args[0] == pt.Bytes('transfer')"),
+            ("txna Accounts 4", "pt.Txn.accounts[4]"),
+            ('byte "foo"', "pt.Bytes('foo')"),
+            ("==", "pt.Txn.accounts[4] == pt.Bytes('foo')"),
+            ("bnz main_l10", "pt.Txn.accounts[4] == pt.Bytes('foo')"),
+            ("err", BIG_C),
+            ("main_l10:", "pt.Txn.accounts[4] == pt.Bytes('foo')"),
+            ("int 1", "pt.Int(1)"),
+            ("return", "pt.Return(pt.Int(1))"),
+            ("main_l11:", "pt.Txn.application_args[0] == pt.Bytes('transfer')"),
+            ("int 1", "pt.Int(1)"),
+            ("return", "pt.Return(pt.Int(1))"),
+            ("main_l12:", "pt.Txn.application_args[0] == pt.Bytes('mint')"),
+            ("int 1", "pt.Int(1)"),
+            ("return", "pt.Return(pt.Int(1))"),
+            ("main_l13:", "pt.Txn.application_args[0] == pt.Bytes('set admin')"),
+            ("int 1", "pt.Int(1)"),
+            ("return", "pt.Return(pt.Int(1))"),
+            ("main_l14:", "pt.Txn.on_completion() == pt.OnComplete.OptIn"),
+            ("int 1", "pt.Int(1)"),
+            ("return", "pt.Return(pt.Int(1))"),
+            ("main_l15:", "pt.Txn.on_completion() == pt.OnComplete.CloseOut"),
+            ("int 1", "pt.Int(1)"),
+            ("return", "pt.Return(pt.Int(1))"),
+            ("main_l16:", "pt.Txn.on_completion() == pt.OnComplete.UpdateApplication"),
+            ("int 0", "pt.Int(0)"),
+            ('byte "admin"', "pt.Bytes('admin')"),
+            ("app_local_get", "pt.App.localGet(pt.Int(0), pt.Bytes('admin'))"),
+            ("return", "pt.Return(is_admin)"),
+            ("main_l17:", "pt.Txn.on_completion() == pt.OnComplete.DeleteApplication"),
+            ("int 0", "pt.Int(0)"),
+            ('byte "admin"', "pt.Bytes('admin')"),
+            ("app_local_get", "pt.App.localGet(pt.Int(0), pt.Bytes('admin'))"),
+            ("return", "pt.Return(is_admin)"),
+            ("main_l18:", "pt.Txn.application_id() == pt.Int(0)"),
+            ("int 1", "pt.Int(1)"),
+            ("return", "pt.Return(pt.Int(1))"),
+        ],
+        2,
+        "Application",
+    ),
+    (  # 18: Tmpl Gtxn TxnType
+        lambda pt: [
+            asset_c := pt.Tmpl.Int("TMPL_ASSET_C"),
+            receiver := pt.Tmpl.Addr("TMPL_RECEIVER"),
+            pt.And(
+                pt.Gtxn[0].rekey_to() == pt.Global.zero_address(),
+                pt.Gtxn[1].rekey_to() == pt.Global.zero_address(),
+                pt.Gtxn[2].rekey_to() == pt.Global.zero_address(),
+                pt.Gtxn[3].rekey_to() == pt.Global.zero_address(),
+                pt.Gtxn[4].rekey_to() == pt.Global.zero_address(),
+                pt.Gtxn[0].last_valid() == pt.Gtxn[1].last_valid(),
+                pt.Gtxn[1].last_valid() == pt.Gtxn[2].last_valid(),
+                pt.Gtxn[2].last_valid() == pt.Gtxn[3].last_valid(),
+                pt.Gtxn[3].last_valid() == pt.Gtxn[4].last_valid(),
+                pt.Gtxn[0].type_enum() == pt.TxnType.AssetTransfer,
+                pt.Gtxn[0].xfer_asset() == asset_c,
+                pt.Gtxn[0].receiver() == receiver,
+            ),
+        ][-1],
+        [
+            [P, C],
+            ("gtxn 0 RekeyTo", "pt.Gtxn[0].rekey_to()"),
+            ("global ZeroAddress", "pt.Global.zero_address()"),
+            ("==", "pt.Gtxn[0].rekey_to() == pt.Global.zero_address()"),
+            ("gtxn 1 RekeyTo", "pt.Gtxn[1].rekey_to()"),
+            ("global ZeroAddress", "pt.Global.zero_address()"),
+            ("==", "pt.Gtxn[1].rekey_to() == pt.Global.zero_address()"),
+            ("&&", BIG_A),
+            ("gtxn 2 RekeyTo", "pt.Gtxn[2].rekey_to()"),
+            ("global ZeroAddress", "pt.Global.zero_address()"),
+            ("==", "pt.Gtxn[2].rekey_to() == pt.Global.zero_address()"),
+            ("&&", BIG_A),
+            ("gtxn 3 RekeyTo", "pt.Gtxn[3].rekey_to()"),
+            ("global ZeroAddress", "pt.Global.zero_address()"),
+            ("==", "pt.Gtxn[3].rekey_to() == pt.Global.zero_address()"),
+            ("&&", BIG_A),
+            ("gtxn 4 RekeyTo", "pt.Gtxn[4].rekey_to()"),
+            ("global ZeroAddress", "pt.Global.zero_address()"),
+            ("==", "pt.Gtxn[4].rekey_to() == pt.Global.zero_address()"),
+            ("&&", BIG_A),
+            ("gtxn 0 LastValid", "pt.Gtxn[0].last_valid()"),
+            ("gtxn 1 LastValid", "pt.Gtxn[1].last_valid()"),
+            ("==", "pt.Gtxn[0].last_valid() == pt.Gtxn[1].last_valid()"),
+            ("&&", BIG_A),
+            ("gtxn 1 LastValid", "pt.Gtxn[1].last_valid()"),
+            ("gtxn 2 LastValid", "pt.Gtxn[2].last_valid()"),
+            ("==", "pt.Gtxn[1].last_valid() == pt.Gtxn[2].last_valid()"),
+            ("&&", BIG_A),
+            ("gtxn 2 LastValid", "pt.Gtxn[2].last_valid()"),
+            ("gtxn 3 LastValid", "pt.Gtxn[3].last_valid()"),
+            ("==", "pt.Gtxn[2].last_valid() == pt.Gtxn[3].last_valid()"),
+            ("&&", BIG_A),
+            ("gtxn 3 LastValid", "pt.Gtxn[3].last_valid()"),
+            ("gtxn 4 LastValid", "pt.Gtxn[4].last_valid()"),
+            ("==", "pt.Gtxn[3].last_valid() == pt.Gtxn[4].last_valid()"),
+            ("&&", BIG_A),
+            ("gtxn 0 TypeEnum", "pt.Gtxn[0].type_enum()"),
+            (
+                "int axfer",
+                "pt.Gtxn[0].type_enum() == pt.TxnType.AssetTransfer",
+            ),
+            ("==", "pt.Gtxn[0].type_enum() == pt.TxnType.AssetTransfer"),
+            ("&&", BIG_A),
+            ("gtxn 0 XferAsset", "pt.Gtxn[0].xfer_asset()"),
+            ("int TMPL_ASSET_C", "pt.Tmpl.Int('TMPL_ASSET_C')"),
+            ("==", "pt.Gtxn[0].xfer_asset() == asset_c"),
+            ("&&", BIG_A),
+            ("gtxn 0 Receiver", "pt.Gtxn[0].receiver()"),
+            ("addr TMPL_RECEIVER", "pt.Tmpl.Addr('TMPL_RECEIVER')"),
+            ("==", "pt.Gtxn[0].receiver() == receiver"),
+            ("&&", BIG_A),
+            ["return", C],
+        ],
+    ),
+    (  # 19: Txn.application_args
+        lambda pt: pt.And(
+            pt.Txn.application_args.length(),  # get the number of application arguments in the transaction
+            # as of AVM v5, PyTeal expressions can be used to dynamically index into array properties as well
+            pt.Btoi(
+                pt.Txn.application_args[pt.Txn.application_args.length() - pt.Int(1)]
+            ),
+        ),
+        [
+            [P, C],
+            ("txn NumAppArgs", "pt.Txn.application_args.length()"),
+            ("txn NumAppArgs", "pt.Txn.application_args.length()"),
+            ("int 1", "pt.Int(1)"),
+            ("-", "pt.Txn.application_args.length() - pt.Int(1)"),
+            (
+                "txnas ApplicationArgs",
+                "pt.Txn.application_args[pt.Txn.application_args.length() - pt.Int(1)]",
+            ),
+            (
+                "btoi",
+                "pt.Btoi(pt.Txn.application_args[pt.Txn.application_args.length() - pt.Int(1)])",
+            ),
+            (
+                "&&",
+                "pt.And(pt.Txn.application_args.length(), pt.Btoi(pt.Txn.application_args[pt.Txn.application_args.length() - pt.Int(1)]))",
+            ),
+            ["return", C],
+        ],
+        5,
+    ),
+    (  # 20: App
+        lambda pt: pt.Seq(
+            receiver_max_balance := pt.App.localGetEx(  # noqa: F841
+                pt.Int(1), pt.App.id(), pt.Bytes("max balance")
+            ),
+            pt.Or(
+                pt.App.globalGet(pt.Bytes("paused")),
+                pt.App.localGet(pt.Int(0), pt.Bytes("frozen")),
+                pt.App.localGet(pt.Int(1), pt.Bytes("frozen")),
+                pt.App.localGet(pt.Int(0), pt.Bytes("lock until"))
+                >= pt.Global.latest_timestamp(),
+                pt.App.localGet(pt.Int(1), pt.Bytes("lock until"))
+                >= pt.Global.latest_timestamp(),
+                pt.App.globalGet(
+                    pt.Concat(
+                        pt.Bytes("rule"),
+                        pt.Itob(pt.App.localGet(pt.Int(0), pt.Bytes("transfer group"))),
+                        pt.Itob(pt.App.localGet(pt.Int(1), pt.Bytes("transfer group"))),
+                    )
+                ),
+            ),
+        ),
+        [
+            [P, C],
+            ("int 1", "pt.Int(1)"),
+            ("global CurrentApplicationID", "pt.App.id()"),
+            ('byte "max balance"', "pt.Bytes('max balance')"),
+            (
+                "app_local_get_ex",
+                "pt.App.localGetEx(pt.Int(1), pt.App.id(), pt.Bytes('max balance'))",
+            ),
+            (
+                "store 1",
+                "pt.App.localGetEx(pt.Int(1), pt.App.id(), pt.Bytes('max balance'))",
+            ),
+            (
+                "store 0",
+                "pt.App.localGetEx(pt.Int(1), pt.App.id(), pt.Bytes('max balance'))",
+            ),
+            ('byte "paused"', "pt.Bytes('paused')"),
+            ("app_global_get", "pt.App.globalGet(pt.Bytes('paused'))"),
+            ("int 0", "pt.Int(0)"),
+            ('byte "frozen"', "pt.Bytes('frozen')"),
+            ("app_local_get", "pt.App.localGet(pt.Int(0), pt.Bytes('frozen'))"),
+            ("||", BIG_OR),
+            ("int 1", "pt.Int(1)"),
+            ('byte "frozen"', "pt.Bytes('frozen')"),
+            ("app_local_get", "pt.App.localGet(pt.Int(1), pt.Bytes('frozen'))"),
+            ("||", BIG_OR),
+            ("int 0", "pt.Int(0)"),
+            ('byte "lock until"', "pt.Bytes('lock until')"),
+            ("app_local_get", "pt.App.localGet(pt.Int(0), pt.Bytes('lock until'))"),
+            ("global LatestTimestamp", "pt.Global.latest_timestamp()"),
+            (
+                ">=",
+                "pt.App.localGet(pt.Int(0), pt.Bytes('lock until')) >= pt.Global.latest_timestamp()",
+            ),
+            ("||", BIG_OR),
+            ("int 1", "pt.Int(1)"),
+            ('byte "lock until"', "pt.Bytes('lock until')"),
+            ("app_local_get", "pt.App.localGet(pt.Int(1), pt.Bytes('lock until'))"),
+            ("global LatestTimestamp", "pt.Global.latest_timestamp()"),
+            (
+                ">=",
+                "pt.App.localGet(pt.Int(1), pt.Bytes('lock until')) >= pt.Global.latest_timestamp()",
+            ),
+            ("||", BIG_OR),
+            ('byte "rule"', "pt.Bytes('rule')"),
+            ("int 0", "pt.Int(0)"),
+            ('byte "transfer group"', "pt.Bytes('transfer group')"),
+            (
+                "app_local_get",
+                "pt.App.localGet(pt.Int(0), pt.Bytes('transfer group'))",
+            ),
+            (
+                "itob",
+                "pt.Itob(pt.App.localGet(pt.Int(0), pt.Bytes('transfer group')))",
+            ),
+            (
+                "concat",
+                "pt.Concat(pt.Bytes('rule'), pt.Itob(pt.App.localGet(pt.Int(0), pt.Bytes('transfer group'))), pt.Itob(pt.App.localGet(pt.Int(1), pt.Bytes('transfer group'))))",
+            ),
+            ("int 1", "pt.Int(1)"),
+            ('byte "transfer group"', "pt.Bytes('transfer group')"),
+            (
+                "app_local_get",
+                "pt.App.localGet(pt.Int(1), pt.Bytes('transfer group'))",
+            ),
+            ("itob", "pt.Itob(pt.App.localGet(pt.Int(1), pt.Bytes('transfer group')))"),
+            (
+                "concat",
+                "pt.Concat(pt.Bytes('rule'), pt.Itob(pt.App.localGet(pt.Int(0), pt.Bytes('transfer group'))), pt.Itob(pt.App.localGet(pt.Int(1), pt.Bytes('transfer group'))))",
+            ),
+            (
+                "app_global_get",
+                "pt.App.globalGet(pt.Concat(pt.Bytes('rule'), pt.Itob(pt.App.localGet(pt.Int(0), pt.Bytes('transfer group'))), pt.Itob(pt.App.localGet(pt.Int(1), pt.Bytes('transfer group')))))",
+            ),
+            ("||", BIG_OR),
+            ["return", C],
+        ],
+        2,
+        "Application",
+    ),
+    (  # 21: EcdsaCurve Sha512_256
+        lambda pt: pt.EcdsaVerify(
+            pt.EcdsaCurve.Secp256k1,
+            pt.Sha512_256(pt.Bytes("testdata")),
+            pt.Bytes(
+                "base16",
+                "33602297203d2753372cea7794ffe1756a278cbc4907b15a0dd132c9fb82555e",
+            ),
+            pt.Bytes(
+                "base16",
+                "20f112126cf3e2eac6e8d4f97a403d21bab07b8dbb77154511bb7b07c0173195",
+            ),
+            (
+                pt.Bytes(
+                    "base16",
+                    "d6143a58c90c06b594e4414cb788659c2805e0056b1dfceea32c03f59efec517",
+                ),
+                pt.Bytes(
+                    "base16",
+                    "00bd2400c479efe5ea556f37e1dc11ccb20f1e642dbfe00ca346fffeae508298",
+                ),
+            ),
+        ),
+        [
+            [P, C],
+            ['byte "testdata"', "pt.Bytes('testdata')"],
+            ["sha512_256", "pt.Sha512_256(pt.Bytes('testdata'))"],
+            [
+                "byte 0x33602297203d2753372cea7794ffe1756a278cbc4907b15a0dd132c9fb82555e",
+                "pt.Bytes('base16', '33602297203d2753372cea7794ffe1756a278cbc4907b15a0dd132c9fb82555e')",
+            ],
+            [
+                "byte 0x20f112126cf3e2eac6e8d4f97a403d21bab07b8dbb77154511bb7b07c0173195",
+                "pt.Bytes('base16', '20f112126cf3e2eac6e8d4f97a403d21bab07b8dbb77154511bb7b07c0173195')",
+            ],
+            [
+                "byte 0xd6143a58c90c06b594e4414cb788659c2805e0056b1dfceea32c03f59efec517",
+                "pt.Bytes('base16', 'd6143a58c90c06b594e4414cb788659c2805e0056b1dfceea32c03f59efec517')",
+            ],
+            [
+                "byte 0x00bd2400c479efe5ea556f37e1dc11ccb20f1e642dbfe00ca346fffeae508298",
+                "pt.Bytes('base16', '00bd2400c479efe5ea556f37e1dc11ccb20f1e642dbfe00ca346fffeae508298')",
+            ],
+            [
+                "ecdsa_verify Secp256k1",
+                "pt.EcdsaVerify(pt.EcdsaCurve.Secp256k1, pt.Sha512_256(pt.Bytes('testdata')), pt.Bytes('base16', '33602297203d2753372cea7794ffe1756a278cbc4907b15a0dd132c9fb82555e'), pt.Bytes('base16', '20f112126cf3e2eac6e8d4f97a403d21bab07b8dbb77154511bb7b07c0173195'), (pt.Bytes('base16', 'd6143a58c90c06b594e4414cb788659c2805e0056b1dfceea32c03f59efec517'), pt.Bytes('base16', '00bd2400c479efe5ea556f37e1dc11ccb20f1e642dbfe00ca346fffeae508298')))",
+            ],
+            ["return", C],
+        ],
+        5,
+    ),
+    (  # 22: ScratchVar (simple Assert )
+        lambda pt: [
+            myvar := pt.ScratchVar(
+                pt.TealType.uint64
+            ),  # assign a scratch slot in any available slot
+            anotherVar := pt.ScratchVar(
+                pt.TealType.bytes, 4
+            ),  # assign this scratch slot to slot #4
+            pt.Seq(
+                [
+                    myvar.store(pt.Int(5)),
+                    anotherVar.store(pt.Bytes("hello")),
+                    pt.Assert(myvar.load() == pt.Int(5)),
+                    pt.Return(pt.Int(1)),
+                ]
+            ),
+        ][-1],
+        [
+            [P, C],
+            ["int 5", "pt.Int(5)"],
+            ["store 0", "myvar.store(pt.Int(5))"],
+            ['byte "hello"', "pt.Bytes('hello')"],
+            ["store 4", "anotherVar.store(pt.Bytes('hello'))"],
+            ["load 0", "myvar.load()"],
+            ["int 5", "pt.Int(5)"],
+            ["==", "myvar.load() == pt.Int(5)"],
+            ["assert", "pt.Assert(myvar.load() == pt.Int(5))"],
+            ["int 1", "pt.Int(1)"],
+            ["return", "pt.Return(pt.Int(1))"],
+        ],
+        3,
+    ),
+    (  # 23: DynamicScratchVar
+        lambda pt: [
+            s := pt.ScratchVar(pt.TealType.uint64),
+            d := pt.DynamicScratchVar(pt.TealType.uint64),
+            pt.Seq(
+                d.set_index(s),
+                s.store(pt.Int(7)),
+                d.store(d.load() + pt.Int(3)),
+                pt.Assert(s.load() == pt.Int(10)),
+                pt.Int(1),
+            ),
+        ][-1],
+        [
+            [P, C],
+            ["int 0", "d.set_index(s)"],
+            ["store 1", "d.set_index(s)"],
+            ["int 7", "pt.Int(7)"],
+            ["store 0", "s.store(pt.Int(7))"],
+            ["load 1", "d.store(d.load() + pt.Int(3))"],
+            ["load 1", "d.load()"],
+            ["loads", "d.load()"],
+            ["int 3", "pt.Int(3)"],
+            ["+", "d.load() + pt.Int(3)"],
+            ["stores", "d.store(d.load() + pt.Int(3))"],
+            ["load 0", "s.load()"],
+            ["int 10", "pt.Int(10)"],
+            ["==", "s.load() == pt.Int(10)"],
+            ["assert", "pt.Assert(s.load() == pt.Int(10))"],
+            ["int 1", "pt.Int(1)"],
+            ["return", C],
+        ],
+        5,
+    ),
+    (  # 24: If/Then/Else ImportScratchVaplue
+        lambda pt: [  # App is called at transaction index 0
+            greeting := pt.ScratchVar(
+                pt.TealType.bytes, 20
+            ),  # this variable will live in scratch slot 20
+            app1_seq := pt.Seq(
+                [
+                    pt.If(pt.Txn.sender() == pt.App.globalGet(pt.Bytes("creator")))
+                    .Then(greeting.store(pt.Bytes("hi creator!")))
+                    .Else(greeting.store(pt.Bytes("hi user!"))),
+                    pt.Int(1),
+                ]
+            ),
+            greetingFromPreviousApp := pt.ImportScratchValue(
+                0, 20
+            ),  # loading scratch slot 20 from the transaction at index 0
+            app2_seq := pt.Seq(
+                [
+                    # not shown: make sure that the transaction at index 0 is an app call to App A
+                    pt.App.globalPut(
+                        pt.Bytes("greeting from prev app"), greetingFromPreviousApp
+                    ),
+                    pt.Int(1),
+                ]
+            ),
+            pt.And(app1_seq, app2_seq),
+        ][-1],
+        [
+            [P, C],
+            ["txn Sender", "pt.Txn.sender()"],
+            ['byte "creator"', "pt.Bytes('creator')"],
+            ["app_global_get", "pt.App.globalGet(pt.Bytes('creator'))"],
+            ["==", "pt.Txn.sender() == pt.App.globalGet(pt.Bytes('creator'))"],
+            [
+                "bnz main_l2",
+                "pt.If(pt.Txn.sender() == pt.App.globalGet(pt.Bytes('creator')))",
+            ],
+            ['byte "hi user!"', "pt.Bytes('hi user!')"],
+            ["store 20", "greeting.store(pt.Bytes('hi user!'))"],
+            [
+                "b main_l3",
+                "pt.If(pt.Txn.sender() == pt.App.globalGet(pt.Bytes('creator')))",
+            ],
+            [
+                "main_l2:",
+                "pt.If(pt.Txn.sender() == pt.App.globalGet(pt.Bytes('creator')))",
+            ],
+            ['byte "hi creator!"', "pt.Bytes('hi creator!')"],
+            ["store 20", "greeting.store(pt.Bytes('hi creator!'))"],
+            [
+                "main_l3:",
+                "pt.If(pt.Txn.sender() == pt.App.globalGet(pt.Bytes('creator')))",
+            ],  # pt.If(...) etc. sent us over here
+            ["int 1", "pt.Int(1)"],
+            ['byte "greeting from prev app"', "pt.Bytes('greeting from prev app')"],
+            ["gload 0 20", "pt.ImportScratchValue(0, 20)"],
+            [
+                "app_global_put",
+                "pt.App.globalPut(pt.Bytes('greeting from prev app'), greetingFromPreviousApp)",
+            ],
+            ["int 1", "pt.Int(1)"],
+            ["&&", "pt.And(app1_seq, app2_seq)"],
+            ["return", C],
+        ],
+        4,
+        "Application",
+    ),
+    (  # 25: If/Then/ElseIf/ElseIf/Else
+        lambda pt: [
+            arg := pt.Btoi(pt.Arg(1)),
+            pt.If(arg == pt.Int(0))
+            .Then(pt.Reject())
+            .ElseIf(arg == pt.Int(1))
+            .Then(pt.Reject())
+            .ElseIf(arg == pt.Int(2))
+            .Then(pt.Approve())
+            .Else(pt.Reject()),
+        ][-1],
+        [
+            [P, C],
+            ("arg 1", "pt.Arg(1)"),
+            ("btoi", "pt.Btoi(pt.Arg(1))"),
+            ("int 0", "pt.Int(0)"),
+            ("==", "arg == pt.Int(0)"),
+            ("bnz main_l6", "pt.If(arg == pt.Int(0))"),
+            ("arg 1", "pt.Arg(1)"),
+            ("btoi", "pt.Btoi(pt.Arg(1))"),
+            ("int 1", "pt.Int(1)"),
+            ("==", "arg == pt.Int(1)"),
+            ("bnz main_l5", "arg == pt.Int(1)"),
+            ("arg 1", "pt.Arg(1)"),
+            ("btoi", "pt.Btoi(pt.Arg(1))"),
+            ("int 2", "pt.Int(2)"),
+            ("==", "arg == pt.Int(2)"),
+            ("bnz main_l4", "arg == pt.Int(2)"),
+            ("int 0", "pt.Reject()"),
+            ("return", "pt.Reject()"),
+            ("main_l4:", "arg == pt.Int(2)"),
+            ("int 1", "pt.Approve()"),
+            ("return", "pt.Approve()"),
+            ("main_l5:", "arg == pt.Int(1)"),
+            ("int 0", "pt.Reject()"),
+            ("return", "pt.Reject()"),
+            ("main_l6:", "pt.If(arg == pt.Int(0))"),
+            ("int 0", "pt.Reject()"),
+            ("return", "pt.Reject()"),
+        ],
+        2,
+        "Signature",
+    ),
+    (  # 26: While/Do
+        lambda pt: [
+            totalFees := pt.ScratchVar(pt.TealType.uint64),
+            i := pt.ScratchVar(pt.TealType.uint64),
+            pt.Seq(
+                i.store(pt.Int(0)),
+                totalFees.store(pt.Int(0)),
+                pt.While(i.load() < pt.Global.group_size()).Do(
+                    totalFees.store(totalFees.load() + pt.Gtxn[i.load()].fee()),
+                    i.store(i.load() + pt.Int(1)),
+                ),
+                pt.Approve(),
+            ),
+        ][-1],
+        (
+            [P, C],
+            ("int 0", "pt.Int(0)"),
+            ("store 1", "i.store(pt.Int(0))"),
+            ("int 0", "pt.Int(0)"),
+            ("store 0", "totalFees.store(pt.Int(0))"),
+            ("main_l1:", BIG_W),  # yes, this makes sense to be While(...)
+            ("load 1", "i.load()"),
+            ("global GroupSize", "pt.Global.group_size()"),
+            ("<", "i.load() < pt.Global.group_size()"),
+            ("bz main_l3", BIG_W),  # yes, this as well cause we're exiting while
+            ("load 0", "totalFees.load()"),
+            ("load 1", "i.load()"),
+            ("gtxns Fee", "pt.Gtxn[i.load()].fee()"),
+            ("+", "totalFees.load() + pt.Gtxn[i.load()].fee()"),
+            ("store 0", "totalFees.store(totalFees.load() + pt.Gtxn[i.load()].fee())"),
+            ("load 1", "i.load()"),
+            ("int 1", "pt.Int(1)"),
+            ("+", "i.load() + pt.Int(1)"),
+            ("store 1", "i.store(i.load() + pt.Int(1))"),
+            ("b main_l1", BIG_W),  # but the only reason for this is While(...)
+            ("main_l3:", BIG_W),  # and this exit condition as well for the While(...)
+            ("int 1", "pt.Approve()"),
+            ("return", "pt.Approve()"),
+        ),
+        3,
+    ),
+    (  # 27: For/Do
+        lambda pt: [
+            totalFees := pt.ScratchVar(pt.TealType.uint64),
+            i := pt.ScratchVar(pt.TealType.uint64),
+            pt.Seq(
+                totalFees.store(pt.Int(0)),
+                pt.For(
+                    i.store(pt.Int(0)),
+                    i.load() < pt.Global.group_size(),
+                    i.store(i.load() + pt.Int(1)),
+                ).Do(totalFees.store(totalFees.load() + pt.Gtxn[i.load()].fee())),
+                pt.Approve(),
+            ),
+        ][-1],
+        [
+            [P, C],
+            ("int 0", "pt.Int(0)"),
+            ("store 0", "totalFees.store(pt.Int(0))"),
+            ("int 0", "pt.Int(0)"),
+            ("store 1", "i.store(pt.Int(0))"),
+            ("main_l1:", BIG_F),
+            ("load 1", "i.load()"),
+            ("global GroupSize", "pt.Global.group_size()"),
+            ("<", "i.load() < pt.Global.group_size()"),
+            ("bz main_l3", BIG_F),  # .Do(...) seems a bit more appropriate here
+            ("load 0", "totalFees.load()"),
+            ("load 1", "i.load()"),
+            ("gtxns Fee", "pt.Gtxn[i.load()].fee()"),
+            ("+", "totalFees.load() + pt.Gtxn[i.load()].fee()"),
+            ("store 0", "totalFees.store(totalFees.load() + pt.Gtxn[i.load()].fee())"),
+            ("load 1", "i.load()"),
+            ("int 1", "pt.Int(1)"),
+            ("+", "i.load() + pt.Int(1)"),
+            ("store 1", "i.store(i.load() + pt.Int(1))"),
+            ("b main_l1", BIG_F),
+            ("main_l3:", BIG_F),
+            ("int 1", "pt.Approve()"),
+            ("return", "pt.Approve()"),
+        ],
+        3,
+    ),
+    (  # 28: For/Do nested with If/Then/ElseIf/Then + Break/Continue
+        lambda pt: [
+            numPayments := pt.ScratchVar(pt.TealType.uint64),
+            i := pt.ScratchVar(pt.TealType.uint64),
+            pt.Seq(
+                numPayments.store(pt.Int(0)),
+                pt.For(
+                    i.store(pt.Int(0)),
+                    i.load() < pt.Global.group_size(),
+                    i.store(i.load() + pt.Int(1)),
+                ).Do(
+                    pt.If(pt.Gtxn[i.load()].type_enum() != pt.TxnType.Payment)
+                    .Then(pt.Continue())
+                    .ElseIf(pt.Int(42))
+                    .Then(pt.Break()),
+                    numPayments.store(numPayments.load() + pt.Int(1)),
+                ),
+                pt.Approve(),
+            ),
+        ][-1],
+        [
+            [P, C],
+            ("int 0", "pt.Int(0)"),
+            ("store 0", "numPayments.store(pt.Int(0))"),
+            ("int 0", "pt.Int(0)"),
+            ("store 1", "i.store(pt.Int(0))"),
+            (
+                "main_l1:",
+                "pt.For(i.store(pt.Int(0)), i.load() < pt.Global.group_size(), i.store(i.load() + pt.Int(1)))",
+            ),
+            ("load 1", "i.load()"),
+            ("global GroupSize", "pt.Global.group_size()"),
+            ("<", "i.load() < pt.Global.group_size()"),
+            (
+                "bz main_l6",
+                "pt.For(i.store(pt.Int(0)), i.load() < pt.Global.group_size(), i.store(i.load() + pt.Int(1)))",
+            ),
+            ("load 1", "i.load()"),
+            ("gtxns TypeEnum", "pt.Gtxn[i.load()].type_enum()"),
+            ("int pay", "pt.Gtxn[i.load()].type_enum() != pt.TxnType.Payment"),
+            ("!=", "pt.Gtxn[i.load()].type_enum() != pt.TxnType.Payment"),
+            (
+                "bnz main_l5",
+                "pt.If(pt.Gtxn[i.load()].type_enum() != pt.TxnType.Payment)",  # pt.Continue() is better
+            ),
+            ("int 42", "pt.Int(42)"),
+            (
+                "bnz main_l6",
+                "pt.If(pt.Gtxn[i.load()].type_enum() != pt.TxnType.Payment).Then(pt.Continue()).ElseIf(pt.Int(42))",  # pt.Break() would be better
+            ),
+            ("load 0", "numPayments.load()"),
+            ("int 1", "pt.Int(1)"),
+            ("+", "numPayments.load() + pt.Int(1)"),
+            ("store 0", "numPayments.store(numPayments.load() + pt.Int(1))"),
+            (
+                "main_l5:",
+                "pt.For(i.store(pt.Int(0)), i.load() < pt.Global.group_size(), i.store(i.load() + pt.Int(1)))",
+            ),
+            ("load 1", "i.load()"),
+            ("int 1", "pt.Int(1)"),
+            ("+", "i.load() + pt.Int(1)"),
+            ("store 1", "i.store(i.load() + pt.Int(1))"),
+            (
+                "b main_l1",
+                "pt.For(i.store(pt.Int(0)), i.load() < pt.Global.group_size(), i.store(i.load() + pt.Int(1)))",
+            ),
+            (
+                "main_l6:",
+                "pt.For(i.store(pt.Int(0)), i.load() < pt.Global.group_size(), i.store(i.load() + pt.Int(1)))",
+            ),
+            ("int 1", "pt.Approve()"),
+            ("return", "pt.Approve()"),
+        ],
+        3,
+    ),
+    (  # 29: For/Do w embedded Cond + Break/Continue
+        lambda pt: pt.Seq(
+            pt.For(pt.Pop(pt.Int(1)), pt.Int(2), pt.Pop(pt.Int(3))).Do(
+                pt.Seq(
+                    pt.Cond(
+                        [pt.Int(4), pt.Continue()],
+                        [pt.Int(5), pt.Break()],
+                    ),
+                    pt.Pop(pt.Int(6)),
+                )
+            ),
+            pt.Reject(),
+        ),
+        [
+            [P, C],
+            ("int 1", "pt.Int(1)"),
+            ("pop", "pt.Pop(pt.Int(1))"),
+            ("main_l1:", "pt.For(pt.Pop(pt.Int(1)), pt.Int(2), pt.Pop(pt.Int(3)))"),
+            ("int 2", "pt.Int(2)"),
+            ("bz main_l6", "pt.For(pt.Pop(pt.Int(1)), pt.Int(2), pt.Pop(pt.Int(3)))"),
+            ("int 4", "pt.Int(4)"),
+            (
+                "bnz main_l5",
+                "pt.Int(4)",
+            ),
+            ("int 5", "pt.Int(5)"),
+            (
+                "bnz main_l6",
+                "pt.Int(5)",
+            ),
+            ("err", "pt.Cond([pt.Int(4), pt.Continue()], [pt.Int(5), pt.Break()])"),
+            (
+                "main_l5:",
+                "pt.For(pt.Pop(pt.Int(1)), pt.Int(2), pt.Pop(pt.Int(3)))",  # Continue would be better
+            ),
+            ("int 3", "pt.Int(3)"),
+            ("pop", "pt.Pop(pt.Int(3))"),
+            (
+                "b main_l1",
+                "pt.For(pt.Pop(pt.Int(1)), pt.Int(2), pt.Pop(pt.Int(3)))",
+            ),
+            ("main_l6:", "pt.For(pt.Pop(pt.Int(1)), pt.Int(2), pt.Pop(pt.Int(3)))"),
+            ("int 0", "pt.Reject()"),
+            ("return", "pt.Reject()"),
+        ],
+    ),
+    (  # 30: While/Do w empbedded Cond + Break/Continue
+        lambda pt: pt.Seq(
+            pt.Pop(pt.Int(0)),
+            pt.While(pt.Int(1)).Do(
+                pt.Cond(
+                    [pt.Int(2), pt.Continue()],
+                    [pt.Int(3), pt.Break()],
+                    [pt.Int(4), pt.Pop(pt.Int(5))],
+                ),
+                pt.Pop(pt.Int(6)),
+            ),
+            pt.Reject(),
+        ),
+        # This example shows that Continue() and Break() don't receive credit for the labelled targets
+        [
+            [P, C],
+            ("int 0", "pt.Int(0)"),
+            ("pop", "pt.Pop(pt.Int(0))"),
+            ("main_l1:", "pt.While(pt.Int(1))"),
+            ("int 1", "pt.Int(1)"),
+            (
+                "bz main_l7",
+                "pt.While(pt.Int(1))",
+            ),  # makes sense as While determines where to branch
+            ("int 2", "pt.Int(2)"),
+            (
+                "bnz main_l1",  # TODO: this could be improved as Continue() ought to get credit here
+                "pt.Int(2)",
+            ),
+            ("int 3", "pt.Int(3)"),
+            (
+                "bnz main_l7",  # TODO: this could be improved as Break() ought to get credit here
+                "pt.Int(3)",
+            ),
+            ("int 4", "pt.Int(4)"),
+            (
+                "bnz main_l6",  # makes sense
+                "pt.Int(4)",
+            ),
+            (
+                "err",  # makes sense
+                "pt.Cond([pt.Int(2), pt.Continue()], [pt.Int(3), pt.Break()], [pt.Int(4), pt.Pop(pt.Int(5))])",
+            ),
+            (
+                "main_l6:",  # makes sense
+                "pt.While(pt.Int(1))",
+            ),
+            ("int 5", "pt.Int(5)"),
+            ("pop", "pt.Pop(pt.Int(5))"),
+            ("int 6", "pt.Int(6)"),
+            ("pop", "pt.Pop(pt.Int(6))"),
+            (
+                "b main_l1",
+                "pt.While(pt.Int(1))",
+            ),
+            (
+                "main_l7:",
+                "pt.While(pt.Int(1))",
+            ),  # makes sense as this is the exit condition - but it could also have been Break()
+            ("int 0", "pt.Reject()"),
+            ("return", "pt.Reject()"),
+        ],
+    ),
+    (  # 31: Assert (varargs)
+        lambda pt: pt.Seq(
+            foo := pt.App.globalGetEx(pt.Txn.applications[0], pt.Bytes("flo")),
+            pt.Assert(
+                pt.Txn.sender() == pt.App.globalGet(pt.Bytes("alice")),
+                pt.App.globalGet(pt.Bytes("bob")) != pt.Int(0),
+                pt.App.globalGet(pt.Bytes("chiro")) == pt.Bytes("danillo"),
+                pt.Global.latest_timestamp() > pt.App.globalGet(pt.Bytes("enya")),
+                foo.hasValue(),
+            ),
+            pt.Int(42),
+        ),
+        [
+            [P, C],
+            ("txna Applications 0", "pt.Txn.applications[0]"),
+            ('byte "flo"', "pt.Bytes('flo')"),
+            (
+                "app_global_get_ex",
+                "pt.App.globalGetEx(pt.Txn.applications[0], pt.Bytes('flo'))",
+            ),
+            ("store 1", "pt.App.globalGetEx(pt.Txn.applications[0], pt.Bytes('flo'))"),
+            ("store 0", "pt.App.globalGetEx(pt.Txn.applications[0], pt.Bytes('flo'))"),
+            ("txn Sender", "pt.Txn.sender()"),
+            ('byte "alice"', "pt.Bytes('alice')"),
+            ("app_global_get", "pt.App.globalGet(pt.Bytes('alice'))"),
+            ("==", "pt.Txn.sender() == pt.App.globalGet(pt.Bytes('alice'))"),
+            ("assert", "pt.Txn.sender() == pt.App.globalGet(pt.Bytes('alice'))"),
+            ('byte "bob"', "pt.Bytes('bob')"),
+            ("app_global_get", "pt.App.globalGet(pt.Bytes('bob'))"),
+            ("int 0", "pt.Int(0)"),
+            ("!=", "pt.App.globalGet(pt.Bytes('bob')) != pt.Int(0)"),
+            ("assert", "pt.App.globalGet(pt.Bytes('bob')) != pt.Int(0)"),
+            ('byte "chiro"', "pt.Bytes('chiro')"),
+            ("app_global_get", "pt.App.globalGet(pt.Bytes('chiro'))"),
+            ('byte "danillo"', "pt.Bytes('danillo')"),
+            ("==", "pt.App.globalGet(pt.Bytes('chiro')) == pt.Bytes('danillo')"),
+            ("assert", "pt.App.globalGet(pt.Bytes('chiro')) == pt.Bytes('danillo')"),
+            ("global LatestTimestamp", "pt.Global.latest_timestamp()"),
+            ('byte "enya"', "pt.Bytes('enya')"),
+            ("app_global_get", "pt.App.globalGet(pt.Bytes('enya'))"),
+            (">", "pt.Global.latest_timestamp() > pt.App.globalGet(pt.Bytes('enya'))"),
+            (
+                "assert",
+                "pt.Global.latest_timestamp() > pt.App.globalGet(pt.Bytes('enya'))",
+            ),
+            ("load 1", "foo.hasValue()"),
+            ("assert", "foo.hasValue()"),
+            ("int 42", "pt.Int(42)"),
+            ["return", C],
+        ],
+        3,
+        "Application",
+    ),
+    (  # 32 - arithmetic
+        lambda pt: pt.And(
+            pt.Int(1) - pt.Int(2),
+            pt.Not(pt.Int(3)),
+            pt.Int(4) ^ pt.Int(5),
+            ~pt.Int(6),
+            pt.BytesEq(pt.Bytes("7"), pt.Bytes("8")),
+            pt.GetBit(pt.Int(9), pt.Int(10)),
+            pt.SetBit(pt.Int(11), pt.Int(12), pt.Int(13)),
+            pt.GetByte(pt.Bytes("14"), pt.Int(15)),
+            pt.Btoi(
+                pt.Concat(
+                    pt.BytesDiv(pt.Bytes("101"), pt.Bytes("102")),
+                    pt.BytesNot(pt.Bytes("103")),
+                    pt.BytesZero(pt.Int(10)),
+                    pt.SetBit(pt.Bytes("105"), pt.Int(106), pt.Int(107)),
+                    pt.SetByte(pt.Bytes("108"), pt.Int(109), pt.Int(110)),
+                )
+            ),
+        ),
+        [
+            [P, C],
+            ("int 1", "pt.Int(1)"),
+            ("int 2", "pt.Int(2)"),
+            ("-", "pt.Int(1) - pt.Int(2)"),
+            ("int 3", "pt.Int(3)"),
+            ("!", "pt.Not(pt.Int(3))"),
+            ("&&", BIG_A2),
+            ("int 4", "pt.Int(4)"),
+            ("int 5", "pt.Int(5)"),
+            ("^", "pt.Int(4) ^ pt.Int(5)"),
+            ("&&", BIG_A2),
+            ("int 6", "pt.Int(6)"),
+            ("~", "~pt.Int(6)"),
+            ("&&", BIG_A2),
+            ('byte "7"', "pt.Bytes('7')"),
+            ('byte "8"', "pt.Bytes('8')"),
+            ("b==", "pt.BytesEq(pt.Bytes('7'), pt.Bytes('8'))"),
+            ("&&", BIG_A2),
+            ("int 9", "pt.Int(9)"),
+            ("int 10", "pt.Int(10)"),
+            ("getbit", "pt.GetBit(pt.Int(9), pt.Int(10))"),
+            ("&&", BIG_A2),
+            ("int 11", "pt.Int(11)"),
+            ("int 12", "pt.Int(12)"),
+            ("int 13", "pt.Int(13)"),
+            ("setbit", "pt.SetBit(pt.Int(11), pt.Int(12), pt.Int(13))"),
+            ("&&", BIG_A2),
+            ('byte "14"', "pt.Bytes('14')"),
+            ("int 15", "pt.Int(15)"),
+            ("getbyte", "pt.GetByte(pt.Bytes('14'), pt.Int(15))"),
+            ("&&", BIG_A2),
+            ('byte "101"', "pt.Bytes('101')"),
+            ('byte "102"', "pt.Bytes('102')"),
+            ("b/", "pt.BytesDiv(pt.Bytes('101'), pt.Bytes('102'))"),
+            ('byte "103"', "pt.Bytes('103')"),
+            ("b~", "pt.BytesNot(pt.Bytes('103'))"),
+            ("concat", BIG_C2),
+            ("int 10", "pt.Int(10)"),
+            ("bzero", "pt.BytesZero(pt.Int(10))"),
+            ("concat", BIG_C2),
+            ('byte "105"', "pt.Bytes('105')"),
+            ("int 106", "pt.Int(106)"),
+            ("int 107", "pt.Int(107)"),
+            ("setbit", "pt.SetBit(pt.Bytes('105'), pt.Int(106), pt.Int(107))"),
+            ("concat", BIG_C2),
+            ('byte "108"', "pt.Bytes('108')"),
+            ("int 109", "pt.Int(109)"),
+            ("int 110", "pt.Int(110)"),
+            ("setbyte", "pt.SetByte(pt.Bytes('108'), pt.Int(109), pt.Int(110))"),
+            ("concat", BIG_C2),
+            ("btoi", f"pt.Btoi({BIG_C2})"),
+            ("&&", BIG_A2),
+            ["return", C],
+        ],
+        4,
+    ),
+    (  # 33 - ABI Subroutine + NamedTuple (scratch slots)
+        abi_named_tuple_example,
+        [
+            [P, C],
+            ("callsub status_0", "status()"),
+            ("store 0", "status().store_into(output)"),
+            ("int 1", "pt.Int(1)"),
+            ["return", C],
+            ("", "def status(*, output: PollStatus) -> pt.Expr:"),
+            ("// status", "def status(*, output: PollStatus) -> pt.Expr:"),
+            ("status_0:", "def status(*, output: PollStatus) -> pt.Expr:"),
+            ('byte "1"', "pt.Bytes('1')"),
+            ("app_global_get", "pt.App.globalGet(pt.Bytes('1'))"),
+            ("store 2", "question.set(pt.App.globalGet(pt.Bytes('1')))"),
+            ("load 2", "question.set(pt.App.globalGet(pt.Bytes('1')))"),
+            ("len", "question.set(pt.App.globalGet(pt.Bytes('1')))"),
+            ("itob", "question.set(pt.App.globalGet(pt.Bytes('1')))"),
+            ("extract 6 0", "question.set(pt.App.globalGet(pt.Bytes('1')))"),
+            ("load 2", "question.set(pt.App.globalGet(pt.Bytes('1')))"),
+            ("concat", "question.set(pt.App.globalGet(pt.Bytes('1')))"),
+            ("store 2", "question.set(pt.App.globalGet(pt.Bytes('1')))"),
+            ('byte "2"', "pt.Bytes('2')"),
+            ("app_global_get", "pt.App.globalGet(pt.Bytes('2'))"),
+            ("!", "can_resubmit.set(pt.App.globalGet(pt.Bytes('2')))"),
+            ("!", "can_resubmit.set(pt.App.globalGet(pt.Bytes('2')))"),
+            ("store 3", "can_resubmit.set(pt.App.globalGet(pt.Bytes('2')))"),
+            ('byte "3"', "pt.Bytes('3')"),
+            ("app_global_get", "pt.App.globalGet(pt.Bytes('3'))"),
+            ("!", "is_open.set(pt.App.globalGet(pt.Bytes('3')))"),
+            ("!", "is_open.set(pt.App.globalGet(pt.Bytes('3')))"),
+            ("store 4", "is_open.set(pt.App.globalGet(pt.Bytes('3')))"),
+            ('byte ""', "results.set([])"),
+            ("store 5", "results.set([])"),
+            ("load 2", "output.set(question, can_resubmit, is_open, results)"),
+            ("store 9", "output.set(question, can_resubmit, is_open, results)"),
+            ("load 9", "output.set(question, can_resubmit, is_open, results)"),
+            ("store 8", "output.set(question, can_resubmit, is_open, results)"),
+            ("int 5", "output.set(question, can_resubmit, is_open, results)"),
+            ("store 6", "output.set(question, can_resubmit, is_open, results)"),
+            ("load 6", "output.set(question, can_resubmit, is_open, results)"),
+            ("load 9", "output.set(question, can_resubmit, is_open, results)"),
+            ("len", "output.set(question, can_resubmit, is_open, results)"),
+            ("+", "output.set(question, can_resubmit, is_open, results)"),
+            ("store 7", "output.set(question, can_resubmit, is_open, results)"),
+            ("load 7", "output.set(question, can_resubmit, is_open, results)"),
+            ("int 65536", "output.set(question, can_resubmit, is_open, results)"),
+            ("<", "output.set(question, can_resubmit, is_open, results)"),
+            ("assert", "output.set(question, can_resubmit, is_open, results)"),
+            ("load 6", "output.set(question, can_resubmit, is_open, results)"),
+            ("itob", "output.set(question, can_resubmit, is_open, results)"),
+            ("extract 6 0", "output.set(question, can_resubmit, is_open, results)"),
+            ("byte 0x00", "output.set(question, can_resubmit, is_open, results)"),
+            ("int 0", "output.set(question, can_resubmit, is_open, results)"),
+            ("load 3", "output.set(question, can_resubmit, is_open, results)"),
+            ("setbit", "output.set(question, can_resubmit, is_open, results)"),
+            ("int 1", "output.set(question, can_resubmit, is_open, results)"),
+            ("load 4", "output.set(question, can_resubmit, is_open, results)"),
+            ("setbit", "output.set(question, can_resubmit, is_open, results)"),
+            ("concat", "output.set(question, can_resubmit, is_open, results)"),
+            ("load 5", "output.set(question, can_resubmit, is_open, results)"),
+            ("store 9", "output.set(question, can_resubmit, is_open, results)"),
+            ("load 8", "output.set(question, can_resubmit, is_open, results)"),
+            ("load 9", "output.set(question, can_resubmit, is_open, results)"),
+            ("concat", "output.set(question, can_resubmit, is_open, results)"),
+            ("store 8", "output.set(question, can_resubmit, is_open, results)"),
+            ("load 7", "output.set(question, can_resubmit, is_open, results)"),
+            ("store 6", "output.set(question, can_resubmit, is_open, results)"),
+            ("load 6", "output.set(question, can_resubmit, is_open, results)"),
+            ("itob", "output.set(question, can_resubmit, is_open, results)"),
+            ("extract 6 0", "output.set(question, can_resubmit, is_open, results)"),
+            ("concat", "output.set(question, can_resubmit, is_open, results)"),
+            ("load 8", "output.set(question, can_resubmit, is_open, results)"),
+            ("concat", "output.set(question, can_resubmit, is_open, results)"),
+            ("store 1", "output.set(question, can_resubmit, is_open, results)"),
+            ("load 1", "status().store_into(output)"),
+            ("retsub", "def status(*, output: PollStatus) -> pt.Expr:"),
+        ],
+        5,
+        "Application",
+        dict(frame_pointers=False),
+    ),
+    (  # 34 - ABI Subroutine + NamedTuple (frame pointers)
+        abi_named_tuple_example,
+        [
+            [P, C],
+            ("callsub status_0", "status()"),
+            ("store 0", "status().store_into(output)"),
+            ("int 1", "pt.Int(1)"),
+            ["return", C],
+            ("", "def status(*, output: PollStatus) -> pt.Expr:"),
+            ("// status", "def status(*, output: PollStatus) -> pt.Expr:"),
+            ("status_0:", "def status(*, output: PollStatus) -> pt.Expr:"),
+            ("proto 0 1", "def status(*, output: PollStatus) -> pt.Expr:"),
+            ('byte ""', "def status(*, output: PollStatus) -> pt.Expr:"),
+            ("dup", "def status(*, output: PollStatus) -> pt.Expr:"),
+            ("int 0", "def status(*, output: PollStatus) -> pt.Expr:"),
+            ("dup", "def status(*, output: PollStatus) -> pt.Expr:"),
+            ('byte ""', "def status(*, output: PollStatus) -> pt.Expr:"),
+            ("int 0", "def status(*, output: PollStatus) -> pt.Expr:"),
+            ("dup", "def status(*, output: PollStatus) -> pt.Expr:"),
+            ('byte ""', "def status(*, output: PollStatus) -> pt.Expr:"),
+            ("dup", "def status(*, output: PollStatus) -> pt.Expr:"),
+            ("int 0", "def status(*, output: PollStatus) -> pt.Expr:"),
+            ("dup", "def status(*, output: PollStatus) -> pt.Expr:"),
+            ('byte ""', "def status(*, output: PollStatus) -> pt.Expr:"),
+            ("dup", "def status(*, output: PollStatus) -> pt.Expr:"),
+            ('byte "1"', "pt.Bytes('1')"),
+            ("app_global_get", "pt.App.globalGet(pt.Bytes('1'))"),
+            ("frame_bury 1", "question.set(pt.App.globalGet(pt.Bytes('1')))"),
+            ("frame_dig 1", "question.set(pt.App.globalGet(pt.Bytes('1')))"),
+            ("len", "question.set(pt.App.globalGet(pt.Bytes('1')))"),
+            ("itob", "question.set(pt.App.globalGet(pt.Bytes('1')))"),
+            ("extract 6 0", "question.set(pt.App.globalGet(pt.Bytes('1')))"),
+            ("frame_dig 1", "question.set(pt.App.globalGet(pt.Bytes('1')))"),
+            ("concat", "question.set(pt.App.globalGet(pt.Bytes('1')))"),
+            ("frame_bury 1", "question.set(pt.App.globalGet(pt.Bytes('1')))"),
+            ('byte "2"', "pt.Bytes('2')"),
+            ("app_global_get", "pt.App.globalGet(pt.Bytes('2'))"),
+            ("!", "can_resubmit.set(pt.App.globalGet(pt.Bytes('2')))"),
+            ("!", "can_resubmit.set(pt.App.globalGet(pt.Bytes('2')))"),
+            ("frame_bury 2", "can_resubmit.set(pt.App.globalGet(pt.Bytes('2')))"),
+            ('byte "3"', "pt.Bytes('3')"),
+            ("app_global_get", "pt.App.globalGet(pt.Bytes('3'))"),
+            ("!", "is_open.set(pt.App.globalGet(pt.Bytes('3')))"),
+            ("!", "is_open.set(pt.App.globalGet(pt.Bytes('3')))"),
+            ("frame_bury 3", "is_open.set(pt.App.globalGet(pt.Bytes('3')))"),
+            ('byte ""', "results.set([])"),
+            ("frame_bury 4", "results.set([])"),
+            ("frame_dig 1", "output.set(question, can_resubmit, is_open, results)"),
+            ("frame_bury 12", "output.set(question, can_resubmit, is_open, results)"),
+            ("frame_dig 12", "output.set(question, can_resubmit, is_open, results)"),
+            ("frame_bury 11", "output.set(question, can_resubmit, is_open, results)"),
+            ("int 5", "output.set(question, can_resubmit, is_open, results)"),
+            ("frame_bury 9", "output.set(question, can_resubmit, is_open, results)"),
+            ("frame_dig 9", "output.set(question, can_resubmit, is_open, results)"),
+            ("frame_dig 12", "output.set(question, can_resubmit, is_open, results)"),
+            ("len", "output.set(question, can_resubmit, is_open, results)"),
+            ("+", "output.set(question, can_resubmit, is_open, results)"),
+            ("frame_bury 10", "output.set(question, can_resubmit, is_open, results)"),
+            ("frame_dig 10", "output.set(question, can_resubmit, is_open, results)"),
+            ("int 65536", "output.set(question, can_resubmit, is_open, results)"),
+            ("<", "output.set(question, can_resubmit, is_open, results)"),
+            ("assert", "output.set(question, can_resubmit, is_open, results)"),
+            ("frame_dig 9", "output.set(question, can_resubmit, is_open, results)"),
+            ("itob", "output.set(question, can_resubmit, is_open, results)"),
+            ("extract 6 0", "output.set(question, can_resubmit, is_open, results)"),
+            ("byte 0x00", "output.set(question, can_resubmit, is_open, results)"),
+            ("int 0", "output.set(question, can_resubmit, is_open, results)"),
+            ("frame_dig 2", "output.set(question, can_resubmit, is_open, results)"),
+            ("setbit", "output.set(question, can_resubmit, is_open, results)"),
+            ("int 1", "output.set(question, can_resubmit, is_open, results)"),
+            ("frame_dig 3", "output.set(question, can_resubmit, is_open, results)"),
+            ("setbit", "output.set(question, can_resubmit, is_open, results)"),
+            ("concat", "output.set(question, can_resubmit, is_open, results)"),
+            ("frame_dig 4", "output.set(question, can_resubmit, is_open, results)"),
+            ("frame_bury 12", "output.set(question, can_resubmit, is_open, results)"),
+            ("frame_dig 11", "output.set(question, can_resubmit, is_open, results)"),
+            ("frame_dig 12", "output.set(question, can_resubmit, is_open, results)"),
+            ("concat", "output.set(question, can_resubmit, is_open, results)"),
+            ("frame_bury 11", "output.set(question, can_resubmit, is_open, results)"),
+            ("frame_dig 10", "output.set(question, can_resubmit, is_open, results)"),
+            ("frame_bury 9", "output.set(question, can_resubmit, is_open, results)"),
+            ("frame_dig 9", "output.set(question, can_resubmit, is_open, results)"),
+            ("itob", "output.set(question, can_resubmit, is_open, results)"),
+            ("extract 6 0", "output.set(question, can_resubmit, is_open, results)"),
+            ("concat", "output.set(question, can_resubmit, is_open, results)"),
+            ("frame_dig 11", "output.set(question, can_resubmit, is_open, results)"),
+            ("concat", "output.set(question, can_resubmit, is_open, results)"),
+            ("frame_bury 0", "output.set(question, can_resubmit, is_open, results)"),
+            ("retsub", "def status(*, output: PollStatus) -> pt.Expr:"),
+        ],
+        8,
+        "Application",
+        dict(frame_pointers=True),
+    ),
+    (  # 35 - ABI Subroutine + MethodReturn (scratch slots)
+        abi_method_return_example,
+        [
+            [P, C],
+            ("txna ApplicationArgs 1", "pt.Txn.application_args[1]"),
+            (
+                "store 0",
+                "(to_sum_arr := pt.abi.make(pt.abi.DynamicArray[pt.abi.Uint64])).decode(pt.Txn.application_args[1])",
+            ),
+            (
+                "load 0",
+                "def abi_sum(toSum: pt.abi.DynamicArray[pt.abi.Uint64], *, output: pt.abi.Uint64) -> pt.Expr:",
+            ),
+            ("callsub abisum_0", "abi_sum(to_sum_arr)"),
+            ("store 1", "(res := pt.abi.Uint64()).set(abi_sum(to_sum_arr))"),
+            ("byte 0x151f7c75", "pt.abi.MethodReturn(res)"),
+            ("load 1", "pt.abi.MethodReturn(res)"),
+            ("itob", "pt.abi.MethodReturn(res)"),
+            ("concat", "pt.abi.MethodReturn(res)"),
+            ("log", "pt.abi.MethodReturn(res)"),
+            ("int 1", "pt.Approve()"),
+            ("return", "pt.Approve()"),
+            (
+                "",
+                "def abi_sum(toSum: pt.abi.DynamicArray[pt.abi.Uint64], *, output: pt.abi.Uint64) -> pt.Expr:",
+            ),
+            (
+                "// abi_sum",
+                "def abi_sum(toSum: pt.abi.DynamicArray[pt.abi.Uint64], *, output: pt.abi.Uint64) -> pt.Expr:",
+            ),
+            (
+                "abisum_0:",
+                "def abi_sum(toSum: pt.abi.DynamicArray[pt.abi.Uint64], *, output: pt.abi.Uint64) -> pt.Expr:",
+            ),
+            ("store 2", "(res := pt.abi.Uint64()).set(abi_sum(to_sum_arr))"),
+            ("int 0", "output.set(0)"),
+            ("store 3", "output.set(0)"),
+            ("int 0", "pt.Int(0)"),
+            ("store 4", "i.store(pt.Int(0))"),
+            (
+                "abisum_0_l1:",
+                "pt.For(i.store(pt.Int(0)), i.load() < toSum.length(), i.store(i.load() + pt.Int(1)))",
+            ),
+            ("load 4", "i.load()"),
+            ("load 2", "toSum.length()"),
+            ("int 0", "toSum.length()"),
+            ("extract_uint16", "toSum.length()"),
+            ("store 6", "toSum.length()"),
+            ("load 6", "toSum.length()"),
+            ("<", "i.load() < toSum.length()"),
+            (
+                "bz abisum_0_l3",
+                "pt.For(i.store(pt.Int(0)), i.load() < toSum.length(), i.store(i.load() + pt.Int(1)))",
+            ),
+            ("load 2", "toSum[i.load()].store_into(valueAtIndex)"),
+            ("int 8", "toSum[i.load()].store_into(valueAtIndex)"),
+            ("load 4", "i.load()"),
+            ("*", "toSum[i.load()].store_into(valueAtIndex)"),
+            ("int 2", "toSum[i.load()].store_into(valueAtIndex)"),
+            ("+", "toSum[i.load()].store_into(valueAtIndex)"),
+            ("extract_uint64", "toSum[i.load()].store_into(valueAtIndex)"),
+            ("store 5", "toSum[i.load()].store_into(valueAtIndex)"),
+            ("load 3", "output.get()"),
+            ("load 5", "valueAtIndex.get()"),
+            ("+", "output.get() + valueAtIndex.get()"),
+            ("store 3", "output.set(output.get() + valueAtIndex.get())"),
+            ("load 4", "i.load()"),
+            ("int 1", "pt.Int(1)"),
+            ("+", "i.load() + pt.Int(1)"),
+            ("store 4", "i.store(i.load() + pt.Int(1))"),
+            (
+                "b abisum_0_l1",
+                "pt.For(i.store(pt.Int(0)), i.load() < toSum.length(), i.store(i.load() + pt.Int(1)))",
+            ),
+            (
+                "abisum_0_l3:",
+                "pt.For(i.store(pt.Int(0)), i.load() < toSum.length(), i.store(i.load() + pt.Int(1)))",
+            ),
+            ("load 3", "(res := pt.abi.Uint64()).set(abi_sum(to_sum_arr))"),
+            (
+                "retsub",
+                "def abi_sum(toSum: pt.abi.DynamicArray[pt.abi.Uint64], *, output: pt.abi.Uint64) -> pt.Expr:",
+            ),
+        ],
+        5,
+        "Application",
+        dict(frame_pointers=False),
+    ),
+    (  # 36 - ABI Subroutine + MethodReturn (frame pointers)
+        abi_method_return_example,
+        [
+            [P, C],
+            ("txna ApplicationArgs 1", "pt.Txn.application_args[1]"),
+            (
+                "store 0",
+                "(to_sum_arr := pt.abi.make(pt.abi.DynamicArray[pt.abi.Uint64])).decode(pt.Txn.application_args[1])",
+            ),
+            (
+                "load 0",
+                "def abi_sum(toSum: pt.abi.DynamicArray[pt.abi.Uint64], *, output: pt.abi.Uint64) -> pt.Expr:",
+            ),
+            ("callsub abisum_0", "abi_sum(to_sum_arr)"),
+            ("store 1", "(res := pt.abi.Uint64()).set(abi_sum(to_sum_arr))"),
+            ("byte 0x151f7c75", "pt.abi.MethodReturn(res)"),
+            ("load 1", "pt.abi.MethodReturn(res)"),
+            ("itob", "pt.abi.MethodReturn(res)"),
+            ("concat", "pt.abi.MethodReturn(res)"),
+            ("log", "pt.abi.MethodReturn(res)"),
+            ("int 1", "pt.Approve()"),
+            ("return", "pt.Approve()"),
+            (
+                "",
+                "def abi_sum(toSum: pt.abi.DynamicArray[pt.abi.Uint64], *, output: pt.abi.Uint64) -> pt.Expr:",
+            ),
+            (
+                "// abi_sum",
+                "def abi_sum(toSum: pt.abi.DynamicArray[pt.abi.Uint64], *, output: pt.abi.Uint64) -> pt.Expr:",
+            ),
+            (
+                "abisum_0:",
+                "def abi_sum(toSum: pt.abi.DynamicArray[pt.abi.Uint64], *, output: pt.abi.Uint64) -> pt.Expr:",
+            ),
+            (
+                "proto 1 1",
+                "def abi_sum(toSum: pt.abi.DynamicArray[pt.abi.Uint64], *, output: pt.abi.Uint64) -> pt.Expr:",
+            ),
+            (
+                "int 0",
+                "def abi_sum(toSum: pt.abi.DynamicArray[pt.abi.Uint64], *, output: pt.abi.Uint64) -> pt.Expr:",
+            ),
+            (
+                "dupn 3",
+                "def abi_sum(toSum: pt.abi.DynamicArray[pt.abi.Uint64], *, output: pt.abi.Uint64) -> pt.Expr:",
+            ),
+            ("int 0", "output.set(0)"),
+            ("frame_bury 0", "output.set(0)"),
+            ("int 0", "pt.Int(0)"),
+            ("store 2", "i.store(pt.Int(0))"),
+            (
+                "abisum_0_l1:",
+                "pt.For(i.store(pt.Int(0)), i.load() < toSum.length(), i.store(i.load() + pt.Int(1)))",
+            ),
+            ("load 2", "i.load()"),
+            ("frame_dig -1", "toSum.length()"),
+            ("int 0", "toSum.length()"),
+            ("extract_uint16", "toSum.length()"),
+            ("frame_bury 2", "toSum.length()"),
+            ("frame_dig 2", "toSum.length()"),
+            ("<", "i.load() < toSum.length()"),
+            (
+                "bz abisum_0_l3",
+                "pt.For(i.store(pt.Int(0)), i.load() < toSum.length(), i.store(i.load() + pt.Int(1)))",
+            ),
+            ("frame_dig -1", "toSum[i.load()].store_into(valueAtIndex)"),
+            ("int 8", "toSum[i.load()].store_into(valueAtIndex)"),
+            ("load 2", "i.load()"),
+            ("*", "toSum[i.load()].store_into(valueAtIndex)"),
+            ("int 2", "toSum[i.load()].store_into(valueAtIndex)"),
+            ("+", "toSum[i.load()].store_into(valueAtIndex)"),
+            ("extract_uint64", "toSum[i.load()].store_into(valueAtIndex)"),
+            ("frame_bury 1", "toSum[i.load()].store_into(valueAtIndex)"),
+            ("frame_dig 0", "output.get()"),
+            ("frame_dig 1", "valueAtIndex.get()"),
+            ("+", "output.get() + valueAtIndex.get()"),
+            ("frame_bury 0", "output.set(output.get() + valueAtIndex.get())"),
+            ("load 2", "i.load()"),
+            ("int 1", "pt.Int(1)"),
+            ("+", "i.load() + pt.Int(1)"),
+            ("store 2", "i.store(i.load() + pt.Int(1))"),
+            (
+                "b abisum_0_l1",
+                "pt.For(i.store(pt.Int(0)), i.load() < toSum.length(), i.store(i.load() + pt.Int(1)))",
+            ),
+            (
+                "abisum_0_l3:",
+                "pt.For(i.store(pt.Int(0)), i.load() < toSum.length(), i.store(i.load() + pt.Int(1)))",
+            ),
+            (
+                "retsub",
+                "def abi_sum(toSum: pt.abi.DynamicArray[pt.abi.Uint64], *, output: pt.abi.Uint64) -> pt.Expr:",
+            ),
+        ],
+        8,
+        "Application",
+        dict(frame_pointers=True),
+    ),
+    (  # 37 - Router (scratch slots)
+        router_method_example,
+        [
+            [P, R],
+            ("txn NumAppArgs", R),
+            ("int 0", R),
+            ("==", R),
+            ("bnz main_l4", R),
+            ("txna ApplicationArgs 0", R),
+            ('method "add(uint64,uint64)uint64"', R),
+            ("==", R),
+            ("bnz main_l3", R),
+            ("err", R),
+            ("main_l3:", R),
+            ("txn OnCompletion", A),
+            ("int NoOp", A),
+            ("==", A),
+            ("txn ApplicationID", A),
+            ("int 0", A),
+            ("!=", A),
+            ("&&", A),
+            ("assert", A),
+            ("txna ApplicationArgs 1", R),
+            ("btoi", R),
+            ("store 0", A),
+            ("txna ApplicationArgs 2", R),
+            ("btoi", R),
+            ("store 1", A),
+            ("load 0", A),
+            ("load 1", A),
+            ("callsub add_0", R),
+            ("store 2", A),
+            ("byte 0x151f7c75", A),
+            ("load 2", A),
+            ("itob", A),
+            ("concat", A),
+            ("log", A),
+            ("int 1", R),
+            ("return", A),
+            ("main_l4:", R),
+            ("txn OnCompletion", R),
+            ("int OptIn", R),
+            ("==", R),
+            ("bnz main_l6", R),
+            ("err", R),
+            ("main_l6:", R),
+            ("txn ApplicationID", R),
+            ("int 0", R),
+            ("!=", R),
+            ("assert", R),
+            ('byte "optin call"', "pt.Bytes('optin call')"),
+            ("log", "pt.Log(pt.Bytes('optin call'))"),
+            ("int 1", R),
+            ("return", R),
+            ("", A),
+            ("// add", A),
+            ("add_0:", A),
+            ("store 4", R),
+            ("store 3", R),
+            ("load 3", "a.get()"),
+            ("load 4", "b.get()"),
+            ("+", "a.get() + b.get()"),
+            ("store 5", "output.set(a.get() + b.get())"),
+            ("load 5", R),
+            ("retsub", A),
+        ],
+        5,
+        "Application",
+        dict(frame_pointers=False),
+    ),
+    (  # 38 - Router (frame pointers)
+        router_method_example,
+        [
+            [P, R],
+            ("txn NumAppArgs", R),
+            ("int 0", R),
+            ("==", R),
+            ("bnz main_l4", R),
+            ("txna ApplicationArgs 0", R),
+            ('method "add(uint64,uint64)uint64"', R),
+            ("==", R),
+            ("bnz main_l3", R),
+            ("err", R),
+            ("main_l3:", R),
+            ("txn OnCompletion", A),
+            ("int NoOp", A),
+            ("==", A),
+            ("txn ApplicationID", A),
+            ("int 0", A),
+            ("!=", A),
+            ("&&", A),
+            ("assert", A),
+            ("callsub addcaster_1", A),
+            ("int 1", R),
+            ("return", A),
+            ("main_l4:", R),
+            ("txn OnCompletion", R),
+            ("int OptIn", R),
+            ("==", R),
+            ("bnz main_l6", R),
+            ("err", R),
+            ("main_l6:", R),
+            ("txn ApplicationID", R),
+            ("int 0", R),
+            ("!=", R),
+            ("assert", R),
+            ('byte "optin call"', "pt.Bytes('optin call')"),
+            ("log", "pt.Log(pt.Bytes('optin call'))"),
+            ("int 1", R),
+            ("return", R),
+            ("", A),
+            ("// add", A),
+            ("add_0:", A),
+            ("proto 2 1", A),
+            ("int 0", A),
+            ("frame_dig -2", "a.get()"),
+            ("frame_dig -1", "b.get()"),
+            ("+", "a.get() + b.get()"),
+            ("frame_bury 0", "output.set(a.get() + b.get())"),
+            ("retsub", A),
+            ("", R),
+            ("// add_caster", R),
+            ("addcaster_1:", R),
+            ("proto 0 0", R),
+            ("int 0", R),
+            ("dupn 2", R),
+            ("txna ApplicationArgs 1", R),
+            ("btoi", R),
+            ("frame_bury 1", R),
+            ("txna ApplicationArgs 2", R),
+            ("btoi", R),
+            ("frame_bury 2", R),
+            ("frame_dig 1", A),
+            ("frame_dig 2", A),
+            ("callsub add_0", R),
+            ("frame_bury 0", R),
+            ("byte 0x151f7c75", R),
+            ("frame_dig 0", R),
+            ("itob", R),
+            ("concat", R),
+            ("log", R),
+            ("retsub", R),
+        ],
+        8,
+        "Application",
+        dict(frame_pointers=True),
+    ),
+    (  # 39 - Router static subroutine (scratch slots)
+        router_static_abisubroutine,
+        [
+            [P, R],
+            ("txna ApplicationArgs 0", R),
+            ('method "set_foo(uint64)void"', R),
+            ("==", R),
+            ("bnz main_l2", R),
+            ("err", R),
+            ("main_l2:", R),
+            ("txn OnCompletion", RA),
+            ("int NoOp", RA),
+            ("==", RA),
+            ("txn ApplicationID", RA),
+            ("int 0", RA),
+            ("!=", RA),
+            ("&&", RA),
+            ("assert", RA),
+            ("txna ApplicationArgs 1", R),
+            ("btoi", R),
+            ("store 1", RA),
+            ("load 1", "def set_foo(_app_id: AppId) -> pt.Expr:"),
+            ("callsub setfoo_0", RA),
+            ("int 1", R),
+            ("return", RA),
+            ("", "def set_foo(_app_id: AppId) -> pt.Expr:"),
+            ("// set_foo", "def set_foo(_app_id: AppId) -> pt.Expr:"),
+            ("setfoo_0:", "def set_foo(_app_id: AppId) -> pt.Expr:"),
+            ("store 2", R),
+            ("load 2", "Foo.app_id.set(_app_id)"),
+            ("store 0", "Foo.app_id.set(_app_id)"),
+            ("retsub", "def set_foo(_app_id: AppId) -> pt.Expr:"),
+        ],
+        5,
+        "Application",
+        dict(frame_pointers=False),
+    ),
+    (  # 40 - Router static subroutine (frame pointers)
+        router_static_abisubroutine,
+        [
+            [P, R],
+            ("txna ApplicationArgs 0", R),
+            ('method "set_foo(uint64)void"', R),
+            ("==", R),
+            ("bnz main_l2", R),
+            ("err", R),
+            ("main_l2:", R),
+            ("txn OnCompletion", RA),
+            ("int NoOp", RA),
+            ("==", RA),
+            ("txn ApplicationID", RA),
+            ("int 0", RA),
+            ("!=", RA),
+            ("&&", RA),
+            ("assert", RA),
+            ("callsub setfoocaster_1", RA),
+            ("int 1", R),
+            ("return", RA),
+            ("", "def set_foo(_app_id: AppId) -> pt.Expr:"),
+            ("// set_foo", "def set_foo(_app_id: AppId) -> pt.Expr:"),
+            ("setfoo_0:", "def set_foo(_app_id: AppId) -> pt.Expr:"),
+            ("proto 1 0", "def set_foo(_app_id: AppId) -> pt.Expr:"),
+            ("frame_dig -1", "Foo.app_id.set(_app_id)"),
+            ("store 0", "Foo.app_id.set(_app_id)"),
+            ("retsub", "def set_foo(_app_id: AppId) -> pt.Expr:"),
+            ("", R),
+            ("// set_foo_caster", R),
+            ("setfoocaster_1:", R),
+            ("proto 0 0", R),
+            ("int 0", R),
+            ("txna ApplicationArgs 1", R),
+            ("btoi", R),
+            ("frame_bury 0", R),
+            ("frame_dig 0", "def set_foo(_app_id: AppId) -> pt.Expr:"),
+            ("callsub setfoo_0", R),
+            ("retsub", R),
+        ],
+        8,
+        "Application",
+        dict(frame_pointers=True),
+    ),
+    (  # 41 - Recursive Subroutine (scratch slots)
+        recursive_subroutine_example,
+        [
+            [P, C],
+            ("int 10", "pt.Int(10)"),
+            ("callsub fib_0", "fib(pt.Int(10))"),
+            ("return", C),
+            ("", "def fib(n) -> pt.Expr:"),
+            ("// fib", "def fib(n) -> pt.Expr:"),
+            ("fib_0:", "def fib(n) -> pt.Expr:"),
+            ("store 0", C),
+            ("load 0", C),
+            ("int 1", "pt.Int(1)"),
+            ("<=", "n <= pt.Int(1)"),
+            (
+                "bnz fib_0_l2",
+                "pt.If(n <= pt.Int(1), n, fib(n - pt.Int(1)) + fib(n - pt.Int(2)))",
+            ),
+            ("load 0", C),
+            ("int 1", "pt.Int(1)"),
+            ("-", "n - pt.Int(1)"),
+            ("load 0", C),
+            ("swap", C),
+            ("callsub fib_0", "fib(n - pt.Int(1))"),
+            ("swap", C),
+            ("store 0", C),
+            ("load 0", C),
+            ("int 2", "pt.Int(2)"),
+            ("-", "n - pt.Int(2)"),
+            ("load 0", C),
+            ("swap", C),
+            ("callsub fib_0", "fib(n - pt.Int(2))"),
+            ("swap", C),
+            ("store 0", C),
+            ("+", "fib(n - pt.Int(1)) + fib(n - pt.Int(2))"),
+            (
+                "b fib_0_l3",
+                "pt.If(n <= pt.Int(1), n, fib(n - pt.Int(1)) + fib(n - pt.Int(2)))",
+            ),
+            (
+                "fib_0_l2:",
+                "pt.If(n <= pt.Int(1), n, fib(n - pt.Int(1)) + fib(n - pt.Int(2)))",
+            ),
+            ("load 0", C),
+            (
+                "fib_0_l3:",
+                "pt.If(n <= pt.Int(1), n, fib(n - pt.Int(1)) + fib(n - pt.Int(2)))",
+            ),
+            ("retsub", "def fib(n) -> pt.Expr:"),
+        ],
+        5,
+        "Application",
+        dict(frame_pointers=False),
+    ),
+    (  # 42 - Recursive Subroutine (frame pointers)
+        recursive_subroutine_example,
+        [
+            [P, C],
+            ("int 10", "pt.Int(10)"),
+            ("callsub fib_0", "fib(pt.Int(10))"),
+            ("return", C),
+            ("", "def fib(n) -> pt.Expr:"),
+            ("// fib", "def fib(n) -> pt.Expr:"),
+            ("fib_0:", "def fib(n) -> pt.Expr:"),
+            ("proto 1 1", "def fib(n) -> pt.Expr:"),
+            ("frame_dig -1", C),
+            ("int 1", "pt.Int(1)"),
+            ("<=", "n <= pt.Int(1)"),
+            (
+                "bnz fib_0_l2",
+                "pt.If(n <= pt.Int(1), n, fib(n - pt.Int(1)) + fib(n - pt.Int(2)))",
+            ),
+            ("frame_dig -1", C),
+            ("int 1", "pt.Int(1)"),
+            ("-", "n - pt.Int(1)"),
+            ("callsub fib_0", "fib(n - pt.Int(1))"),
+            ("frame_dig -1", C),
+            ("int 2", "pt.Int(2)"),
+            ("-", "n - pt.Int(2)"),
+            ("callsub fib_0", "fib(n - pt.Int(2))"),
+            ("+", "fib(n - pt.Int(1)) + fib(n - pt.Int(2))"),
+            (
+                "b fib_0_l3",
+                "pt.If(n <= pt.Int(1), n, fib(n - pt.Int(1)) + fib(n - pt.Int(2)))",
+            ),
+            (
+                "fib_0_l2:",
+                "pt.If(n <= pt.Int(1), n, fib(n - pt.Int(1)) + fib(n - pt.Int(2)))",
+            ),
+            ("frame_dig -1", C),
+            (
+                "fib_0_l3:",
+                "pt.If(n <= pt.Int(1), n, fib(n - pt.Int(1)) + fib(n - pt.Int(2)))",
+            ),
+            ("retsub", "def fib(n) -> pt.Expr:"),
+        ],
+        8,
+        "Application",
+        dict(frame_pointers=True),
+    ),
+]
+
+
+@pytest.mark.slow
+@pytest.mark.serial
+@pytest.mark.skipif(sys.version_info < (3, 11), reason="Too slow in Python 3.10")
+@pytest.mark.parametrize("i, test_case", enumerate(CONSTRUCTS))
+@pytest.mark.parametrize("mode", ["Application", "Signature"])
+@pytest.mark.parametrize("version", range(2, CONSTRUCTS_LATEST_VERSION + 1))
+def test_constructs_311(mock_ConfigParser, i, test_case, mode, version):
+    constructs_test(i, test_case, mode, version)
+
+
+def constructs_test(i, test_case, mode, version):
+    import pyteal as pt
+
+    expr, line2unparsed = test_case[:2]
+    line2unparsed = deepcopy(line2unparsed)
+    # fill the pragma template:
+    line2unparsed[0][0] = line2unparsed[0][0].format(v=version)
+
+    expr = expr(pt)
+    if len(test_case) > 2:
+        min_version = test_case[2]
+        if version < min_version:
+            return
+    if len(test_case) > 3:
+        fixed_mode = test_case[3]
+        if mode != fixed_mode:
+            return
+    optimize = None
+    if len(test_case) > 4:
+        optimize = pt.OptimizeOptions(**test_case[4])
+
+    mode = getattr(pt.Mode, mode)
+    if isinstance(expr, pt.Router):
+        assert len(test_case) > 3 and fixed_mode == "Application"  # type: ignore
+
+        rci = pt.ast.router._RouterCompileInput(
+            version=version,
+            assemble_constants=False,
+            optimize=optimize,
+            with_sourcemaps=True,
+        )
+        sourcemap = expr._build_impl(rci).approval_sourcemapper
+    else:
+        comp = pt.Compilation(
+            expr, mode, version=version, assemble_constants=False, optimize=optimize
+        )
+        bundle = comp._compile_impl(with_sourcemap=True)
+        sourcemap = bundle.sourcemapper
+
+    msg = f"[CASE #{i}]: {expr=}"
+
+    assert sourcemap, msg
+
+    tmis = sourcemap.as_list()
+    N = len(tmis)
+
+    teal_linenos = [tmi.teal_lineno for tmi in tmis]
+    assert list(range(1, N + 1)) == teal_linenos
+
+    teal_lines = [tmi.teal_line for tmi in tmis]
+    assert teal_lines == [
+        l for chunk in sourcemap.teal_chunks for l in chunk.splitlines()
+    ]
+
+    unparsed = [tmi.hybrid_unparsed() for tmi in tmis]  # type: ignore
+    msg = f"{msg}, {tmis=}"
+    FORCE_FAIL_FOR_CASE_CREATION = False
+    if FORCE_FAIL_FOR_CASE_CREATION:
+        ouch = [(t, unparsed[i]) for i, t in enumerate(teal_lines)]
+        print(ouch)
+        x = "DEBUG" * 100
+        assert not x, ouch
+
+    expected_lines, expected_unparsed = list(zip(*line2unparsed))
+
+    assert list(expected_lines) == teal_lines, msg
+    assert list(expected_unparsed) == unparsed, msg
diff --git a/tests/unit/sourcemap_constructs_allpy_test.py b/tests/unit/sourcemap_constructs_allpy_test.py
new file mode 100644
index 000000000..a76691480
--- /dev/null
+++ b/tests/unit/sourcemap_constructs_allpy_test.py
@@ -0,0 +1,27 @@
+from configparser import ConfigParser
+from unittest import mock
+
+import pytest
+
+from tests.unit.sourcemap_constructs311_test import (
+    CONSTRUCTS,
+    CONSTRUCTS_LATEST_VERSION,
+    constructs_test,
+)
+
+
+@pytest.fixture
+def mock_ConfigParser():
+    patcher = mock.patch.object(ConfigParser, "getboolean", return_value=True)
+    patcher.start()
+    yield
+    patcher.stop()
+
+
+@pytest.mark.slow
+@pytest.mark.serial
+@pytest.mark.parametrize("i, test_case", enumerate(CONSTRUCTS))
+@pytest.mark.parametrize("mode", ["Application", "Signature"])
+@pytest.mark.parametrize("version", range(2, CONSTRUCTS_LATEST_VERSION + 1))
+def test_constructs_very_slow(mock_ConfigParser, i, test_case, mode, version):
+    constructs_test(i, test_case, mode, version)
diff --git a/tests/unit/sourcemap_monkey_raises_test.py b/tests/unit/sourcemap_monkey_raises_test.py
new file mode 100644
index 000000000..20a0d1d05
--- /dev/null
+++ b/tests/unit/sourcemap_monkey_raises_test.py
@@ -0,0 +1,31 @@
+from configparser import ConfigParser
+from unittest import mock
+
+import pytest
+
+
+@pytest.fixture
+def mock_ConfigParser():
+    patcher = mock.patch.object(
+        ConfigParser, "getboolean", side_effect=Exception("1337")
+    )
+    patcher.start()
+    yield
+    patcher.stop()
+
+
+@pytest.mark.serial
+def test_sourcemap_fails_elegantly_when_no_ini(mock_ConfigParser):
+    from examples.application.abi.algobank import router
+    from pyteal import OptimizeOptions
+    from pyteal.errors import SourceMapDisabledError
+
+    with pytest.raises(SourceMapDisabledError) as smde:
+        router.compile(
+            version=6,
+            optimize=OptimizeOptions(scratch_slots=True),
+            with_sourcemaps=True,
+        )
+
+    assert "pyteal.ini" in str(smde.value)
+    assert "1337" not in str(smde.value)
diff --git a/tests/unit/sourcemap_monkey_unit_test.py b/tests/unit/sourcemap_monkey_unit_test.py
new file mode 100644
index 000000000..cdbd07421
--- /dev/null
+++ b/tests/unit/sourcemap_monkey_unit_test.py
@@ -0,0 +1,817 @@
+"""Includeds monkey-patches of ConfigParser
+"""
+
+from ast import FunctionDef
+from contextlib import contextmanager
+from configparser import ConfigParser
+from pathlib import Path
+import pytest
+import sys
+from unittest import mock
+
+
+ALGOBANK = Path.cwd() / "examples" / "application" / "abi"
+
+FIXTURES = Path.cwd() / "tests" / "unit" / "sourcemaps"
+
+
+@pytest.fixture
+def mock_ConfigParser():
+    patcher = mock.patch.object(ConfigParser, "getboolean", return_value=True)
+    patcher.start()
+    yield
+    patcher.stop()
+
+
+@pytest.fixture
+def context_StackFrame_keep_all_debugging():
+    from pyteal.stack_frame import NatalStackFrame
+
+    NatalStackFrame._keep_all_debugging = True
+    yield
+    NatalStackFrame._keep_all_debugging = False
+
+
+@pytest.mark.skipif(
+    sys.version_info < (3, 11),
+    reason="Currently, this test only works in python 3.11 and above",
+)
+@pytest.mark.serial
+def test_r3sourcemap(mock_ConfigParser):
+    from examples.application.abi.algobank import router
+    from pyteal.ast.router import _RouterCompileInput
+    from pyteal import OptimizeOptions
+    from pyteal.compiler.sourcemap import R3SourceMap
+
+    filename = "dummy filename"
+    rci = _RouterCompileInput(
+        version=6,
+        assemble_constants=False,
+        optimize=OptimizeOptions(scratch_slots=True),
+        approval_filename=filename,
+        with_sourcemaps=True,
+    )
+    compile_bundle = router._build_impl(rci)
+
+    ptsm = compile_bundle.approval_sourcemapper
+    assert ptsm
+
+    actual_unparsed = [x._hybrid_w_offset() for x in ptsm._cached_tmis]
+    assert_algobank_unparsed_as_expected(actual_unparsed)
+
+    r3sm = ptsm._cached_r3sourcemap
+    assert r3sm
+
+    assert filename == r3sm.filename
+    assert str(r3sm.source_root).startswith(str(Path.cwd()))
+    assert list(range(len(r3sm.entries))) == [line for line, _ in r3sm.entries]
+    assert all(c == 0 for _, c in r3sm.entries)
+    assert all(x == (0,) for x in r3sm.index)
+    assert len(r3sm.entries) == len(r3sm.index)
+
+    this_file = __file__.split("/")[-1]
+    expected_source_files = [
+        "examples/application/abi/algobank.py",
+        f"tests/unit/{this_file}",
+    ]
+    assert expected_source_files == r3sm.source_files
+
+    r3sm_json = r3sm.to_json()
+
+    assert "mappings" in r3sm_json
+    assert (
+        "AAqDqB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;ACwBrB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;ADxBqB;AAAA;ACwBrB;ADxBqB;AAAA;AAAA;ACwBrB;AAAA;AAAA;AAAA;ADxBqB;ACwBrB;ADxBqB;ACWrB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;ADXqB;AAAA;AAAA;AAAA;ACWrB;AAAA;AAAA;AAAA;AAAA;AAAA;ADXqB;ACWrB;ADXqB;ACbrB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;ADaqB;AAAA;AAAA;ACbrB;ADaqB;AAAA;AAAA;ACbrB;ADaqB;ACbrB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;ADaqB;ACbrB;ADaqB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;ACzCjB;AACc;AAAd;AAA4C;AAAc;AAA3B;AAA/B;AAFuB;AD0CN;AAAA;AAAA;AC/BkB;AAAA;AD+BlB;AAAA;AAAA;AAAA;AAAA;ACjCiB;AAAA;AAdtC;AAAA;AAAA;AACkB;AAAgB;AAAhB;AAAP;AADX;AAkCA;AAAA;AAAA;ADaqB;AAAA;ACAN;AAAA;AAA0B;AAAA;AAA1B;AAAP;AACO;AAAA;AAA4B;AAA5B;AAAP;AAEI;AAAA;AACA;AACa;AAAA;AAAkB;AAA/B;AAAmD;AAAA;AAAnD;AAHJ;AAfR;AAwBA;AAAA;AAAA;AASmC;AAAgB;AAA7B;AATtB;AAaA;AAAA;AAAA;ADxBqB;AAAA;AC4CT;AACA;AACa;AAAc;AAA3B;AAA+C;AAA/C;AAHJ;AAKA;AA7DR;AA8DQ;AAG2B;AAAA;AAH3B;AAIyB;AAJzB;AAKsB;AALtB;AAQA;AAjCR"
+        == r3sm_json["mappings"]
+    )
+
+    assert "file" in r3sm_json
+    assert filename == r3sm_json["file"]
+
+    assert "sources" in r3sm_json
+
+    # jsonizing creates it's own separate order based on first seen and defaultdict with autoindex
+    expected_json_source_files = [
+        f"tests/unit/{this_file}",
+        "examples/application/abi/algobank.py",
+    ]
+    assert set(expected_json_source_files) == set(r3sm_json["sources"])
+
+    assert "sourceRoot" in r3sm_json
+    assert r3sm.source_root == r3sm_json["sourceRoot"]
+
+    target = "\n".join(r.target_extract for r in r3sm.entries.values())  # type: ignore
+    round_trip = R3SourceMap.from_json(r3sm_json, target=target)
+
+    assert r3sm_json == round_trip.to_json()
+
+
+@pytest.mark.serial
+def test_reconstruct(mock_ConfigParser):
+    from examples.application.abi.algobank import router
+    from pyteal.ast.router import _RouterCompileInput
+    from pyteal import OptimizeOptions
+
+    rci = _RouterCompileInput(
+        version=6,
+        assemble_constants=False,
+        optimize=OptimizeOptions(scratch_slots=True),
+        with_sourcemaps=True,
+    )
+    compile_bundle = router._build_impl(rci)
+
+    assert compile_bundle.approval_sourcemapper
+    assert compile_bundle.clear_sourcemapper
+
+    def compare_and_assert(file, sourcemap):
+        with open(ALGOBANK / file, "r") as f:
+            expected_lines = f.read().splitlines()
+            actual_lines = sourcemap.pure_teal().splitlines()
+            assert len(expected_lines) == len(actual_lines)
+            assert expected_lines == actual_lines
+
+    compare_and_assert("algobank_approval.teal", compile_bundle.approval_sourcemapper)
+    compare_and_assert("algobank_clear_state.teal", compile_bundle.clear_sourcemapper)
+
+
+@pytest.mark.serial
+def test_mocked_config_for_frames(mock_ConfigParser):
+    config = ConfigParser()
+    assert config.getboolean("pyteal-source-mapper", "enabled") is True
+    from pyteal.stack_frame import NatalStackFrame
+
+    assert NatalStackFrame.sourcemapping_is_off() is False
+    assert NatalStackFrame.sourcemapping_is_off(_force_refresh=True) is False
+
+
+def make(x, y, z):
+    import pyteal as pt
+
+    return pt.Int(x) + pt.Int(y) + pt.Int(z)
+
+
+@pytest.mark.serial
+def test_lots_o_indirection(mock_ConfigParser):
+    import pyteal as pt
+
+    e1 = pt.Seq(pt.Pop(make(1, 2, 3)), pt.Pop(make(4, 5, 6)), make(7, 8, 9))
+
+    @pt.Subroutine(pt.TealType.uint64)
+    def foo(x):
+        return pt.Seq(pt.Pop(e1), e1)
+
+    pt.Compilation(foo(pt.Int(42)), pt.Mode.Application, version=6)._compile_impl(
+        with_sourcemap=True
+    )
+
+
+@pytest.mark.serial
+def test_frame_info_is_right_before_core_last_drop_idx(
+    context_StackFrame_keep_all_debugging,
+):
+    import pyteal as pt
+    from pyteal.stack_frame import StackFrame
+
+    e1 = pt.Seq(pt.Pop(make(1, 2, 3)), pt.Pop(make(4, 5, 6)), make(7, 8, 9))
+
+    frame_infos = e1.stack_frames._frames
+    last_drop_idx = 1
+    assert StackFrame._frame_info_is_right_before_core(
+        frame_infos[last_drop_idx].frame_info
+    ), "Uh oh! Something about NatalStackFrame as changes which puts in jeopardy Source Map functionality"
+
+
+def router_static_abisubroutine(pt):
+    AppId = pt.abi.Uint64
+
+    class Foo:
+        """Some class docstring"""
+
+        page_size = pt.Int(128 - 1)
+
+        account = pt.abi.Address()
+        app_id = pt.abi.Uint64()
+
+        @staticmethod
+        @pt.ABIReturnSubroutine
+        def set_foo(
+            _app_id: AppId,
+        ) -> pt.Expr:  # type: ignore
+            "Some docstring"
+            return Foo.app_id.set(_app_id)
+
+    router = pt.Router("foo")
+    router.add_method_handler(
+        Foo.set_foo,
+        "set_foo",
+        pt.MethodConfig(no_op=pt.CallConfig.CALL),
+        "Foo the foo",
+    )
+    return router
+
+
+SUCCESSFUL_SUBROUTINE_LINENO = 196
+
+
+@pytest.mark.serial
+def test_hybrid_w_offset(mock_ConfigParser):
+    import pyteal as pt
+
+    router = router_static_abisubroutine(pt)
+    rci = pt.ast.router._RouterCompileInput(
+        version=7,
+        assemble_constants=True,
+        with_sourcemaps=True,
+    )
+
+    sourcemap = router._build_impl(rci).approval_sourcemapper
+
+    # expected:
+    etarget = "def set_foo(_app_id: AppId) -> pt.Expr:"
+    func_source = f'@staticmethod\n@pt.ABIReturnSubroutine\n{etarget}\n    """Some docstring"""\n    return Foo.app_id.set(_app_id)'
+
+    # actual:
+    lbf = sourcemap._best_frames[-1]
+    hwo = lbf._hybrid_w_offset()
+    nsource = lbf.node_source()
+    raw_code = lbf.raw_code()
+    naive_line = lbf.frame_info.lineno
+
+    # consistent across versions:
+    assert etarget == hwo[0]
+    assert func_source == nsource
+
+    # inconsistent between 3.10 and 3.11:
+    if sys.version_info[:2] <= (3, 10):
+        assert 0 == hwo[1]
+        assert "def set_foo(" == raw_code
+        assert SUCCESSFUL_SUBROUTINE_LINENO == naive_line
+    else:
+        assert 1 == hwo[1]
+        assert "@pt.ABIReturnSubroutine" == raw_code
+        assert SUCCESSFUL_SUBROUTINE_LINENO - 1 == naive_line
+
+
+PyTealFrame_CASES = [
+    (
+        "some code",
+        False,
+        "some chunk",
+        ("some chunk", 0),
+    ),
+    (
+        "some code",
+        True,
+        "some chunk",
+        ("some chunk", 0),
+    ),
+    (
+        "first line",
+        True,
+        "first line and more\nsecond line",
+        ("first line and more", 0),
+    ),
+    (
+        "first line",
+        True,
+        "first line and more\ndef second line",
+        ("def second line", 1),
+    ),
+    (
+        "first line",
+        False,
+        None,
+        ("first line", 0),
+    ),
+    (
+        "first line",
+        True,
+        None,
+        ("first line", 0),
+    ),
+]
+
+
+@contextmanager
+def patch_pt_frame(code, is_funcdef, pt_chunk):
+    from pyteal.stack_frame import PyTealFrame
+
+    node = None
+    if is_funcdef:
+        node = FunctionDef(name="foo", body=[], decorator_list=[], returns=None)
+
+    frame = PyTealFrame(
+        frame_info="dummy frame_info", node=node, creator=None, full_stack=None
+    )
+    with mock.patch.object(frame, "raw_code", return_value=code), mock.patch.object(
+        frame, "node_source", return_value=pt_chunk
+    ):
+
+        yield frame
+
+
+@pytest.mark.parametrize("code, is_funcdef, pt_chunk, expected", PyTealFrame_CASES)
+def test_mock_hybrid_w_offset(code, is_funcdef, pt_chunk, expected):
+    with patch_pt_frame(code, is_funcdef, pt_chunk) as pt_frame:
+        assert expected == pt_frame._hybrid_w_offset()
+
+
+def test_tabulate_args_can_be_dictified():
+    from pyteal.compiler.sourcemap import _PyTealSourceMapper, TealMapItem
+
+    tmi = TealMapItem(
+        pt_frame=mock.MagicMock(),
+        teal_lineno=13,
+        teal_line="some teal line",
+        teal_component="some teal component",
+    )
+    all_cols = {v: v for v in _PyTealSourceMapper._tabulate_param_defaults.values()}
+    full_dict = tmi.asdict(**all_cols)
+    assert set(all_cols.keys()) == set(full_dict.keys())
+
+
+def assert_algobank_unparsed_as_expected(actual):
+    expected = [
+        (0, ("router._build_impl(rci)", 0)),
+        (1, ("router._build_impl(rci)", 0)),
+        (2, ("router._build_impl(rci)", 0)),
+        (3, ("router._build_impl(rci)", 0)),
+        (4, ("router._build_impl(rci)", 0)),
+        (5, ("router._build_impl(rci)", 0)),
+        (6, ("router._build_impl(rci)", 0)),
+        (7, ("router._build_impl(rci)", 0)),
+        (8, ("router._build_impl(rci)", 0)),
+        (9, ("router._build_impl(rci)", 0)),
+        (10, ("router._build_impl(rci)", 0)),
+        (11, ("router._build_impl(rci)", 0)),
+        (12, ("router._build_impl(rci)", 0)),
+        (13, ("router._build_impl(rci)", 0)),
+        (14, ("router._build_impl(rci)", 0)),
+        (15, ("router._build_impl(rci)", 0)),
+        (16, ("router._build_impl(rci)", 0)),
+        (17, ("router._build_impl(rci)", 0)),
+        (18, ("router._build_impl(rci)", 0)),
+        (19, ("def withdraw(amount: abi.Uint64, recipient: abi.Account) -> Expr:", 1)),
+        (20, ("def withdraw(amount: abi.Uint64, recipient: abi.Account) -> Expr:", 1)),
+        (21, ("def withdraw(amount: abi.Uint64, recipient: abi.Account) -> Expr:", 1)),
+        (22, ("def withdraw(amount: abi.Uint64, recipient: abi.Account) -> Expr:", 1)),
+        (23, ("def withdraw(amount: abi.Uint64, recipient: abi.Account) -> Expr:", 1)),
+        (24, ("def withdraw(amount: abi.Uint64, recipient: abi.Account) -> Expr:", 1)),
+        (25, ("def withdraw(amount: abi.Uint64, recipient: abi.Account) -> Expr:", 1)),
+        (26, ("def withdraw(amount: abi.Uint64, recipient: abi.Account) -> Expr:", 1)),
+        (27, ("router._build_impl(rci)", 0)),
+        (28, ("router._build_impl(rci)", 0)),
+        (29, ("def withdraw(amount: abi.Uint64, recipient: abi.Account) -> Expr:", 1)),
+        (30, ("router._build_impl(rci)", 0)),
+        (31, ("router._build_impl(rci)", 0)),
+        (32, ("router._build_impl(rci)", 0)),
+        (33, ("def withdraw(amount: abi.Uint64, recipient: abi.Account) -> Expr:", 1)),
+        (34, ("def withdraw(amount: abi.Uint64, recipient: abi.Account) -> Expr:", 1)),
+        (35, ("def withdraw(amount: abi.Uint64, recipient: abi.Account) -> Expr:", 1)),
+        (36, ("def withdraw(amount: abi.Uint64, recipient: abi.Account) -> Expr:", 1)),
+        (37, ("router._build_impl(rci)", 0)),
+        (38, ("def withdraw(amount: abi.Uint64, recipient: abi.Account) -> Expr:", 1)),
+        (39, ("router._build_impl(rci)", 0)),
+        (40, ("def getBalance(user: abi.Account, *, output: abi.Uint64) -> Expr:", 1)),
+        (41, ("def getBalance(user: abi.Account, *, output: abi.Uint64) -> Expr:", 1)),
+        (42, ("def getBalance(user: abi.Account, *, output: abi.Uint64) -> Expr:", 1)),
+        (43, ("def getBalance(user: abi.Account, *, output: abi.Uint64) -> Expr:", 1)),
+        (44, ("def getBalance(user: abi.Account, *, output: abi.Uint64) -> Expr:", 1)),
+        (45, ("def getBalance(user: abi.Account, *, output: abi.Uint64) -> Expr:", 1)),
+        (46, ("def getBalance(user: abi.Account, *, output: abi.Uint64) -> Expr:", 1)),
+        (47, ("def getBalance(user: abi.Account, *, output: abi.Uint64) -> Expr:", 1)),
+        (48, ("router._build_impl(rci)", 0)),
+        (49, ("router._build_impl(rci)", 0)),
+        (50, ("router._build_impl(rci)", 0)),
+        (51, ("router._build_impl(rci)", 0)),
+        (52, ("def getBalance(user: abi.Account, *, output: abi.Uint64) -> Expr:", 1)),
+        (53, ("def getBalance(user: abi.Account, *, output: abi.Uint64) -> Expr:", 1)),
+        (54, ("def getBalance(user: abi.Account, *, output: abi.Uint64) -> Expr:", 1)),
+        (55, ("def getBalance(user: abi.Account, *, output: abi.Uint64) -> Expr:", 1)),
+        (56, ("def getBalance(user: abi.Account, *, output: abi.Uint64) -> Expr:", 1)),
+        (57, ("def getBalance(user: abi.Account, *, output: abi.Uint64) -> Expr:", 1)),
+        (58, ("router._build_impl(rci)", 0)),
+        (59, ("def getBalance(user: abi.Account, *, output: abi.Uint64) -> Expr:", 1)),
+        (60, ("router._build_impl(rci)", 0)),
+        (
+            61,
+            (
+                "def deposit(payment: abi.PaymentTransaction, sender: abi.Account) -> Expr:",
+                1,
+            ),
+        ),
+        (
+            62,
+            (
+                "def deposit(payment: abi.PaymentTransaction, sender: abi.Account) -> Expr:",
+                1,
+            ),
+        ),
+        (
+            63,
+            (
+                "def deposit(payment: abi.PaymentTransaction, sender: abi.Account) -> Expr:",
+                1,
+            ),
+        ),
+        (
+            64,
+            (
+                "def deposit(payment: abi.PaymentTransaction, sender: abi.Account) -> Expr:",
+                1,
+            ),
+        ),
+        (
+            65,
+            (
+                "def deposit(payment: abi.PaymentTransaction, sender: abi.Account) -> Expr:",
+                1,
+            ),
+        ),
+        (
+            66,
+            (
+                "def deposit(payment: abi.PaymentTransaction, sender: abi.Account) -> Expr:",
+                1,
+            ),
+        ),
+        (
+            67,
+            (
+                "def deposit(payment: abi.PaymentTransaction, sender: abi.Account) -> Expr:",
+                1,
+            ),
+        ),
+        (
+            68,
+            (
+                "def deposit(payment: abi.PaymentTransaction, sender: abi.Account) -> Expr:",
+                1,
+            ),
+        ),
+        (
+            69,
+            (
+                "def deposit(payment: abi.PaymentTransaction, sender: abi.Account) -> Expr:",
+                1,
+            ),
+        ),
+        (
+            70,
+            (
+                "def deposit(payment: abi.PaymentTransaction, sender: abi.Account) -> Expr:",
+                1,
+            ),
+        ),
+        (
+            71,
+            (
+                "def deposit(payment: abi.PaymentTransaction, sender: abi.Account) -> Expr:",
+                1,
+            ),
+        ),
+        (
+            72,
+            (
+                "def deposit(payment: abi.PaymentTransaction, sender: abi.Account) -> Expr:",
+                1,
+            ),
+        ),
+        (
+            73,
+            (
+                "def deposit(payment: abi.PaymentTransaction, sender: abi.Account) -> Expr:",
+                1,
+            ),
+        ),
+        (
+            74,
+            (
+                "def deposit(payment: abi.PaymentTransaction, sender: abi.Account) -> Expr:",
+                1,
+            ),
+        ),
+        (
+            75,
+            (
+                "def deposit(payment: abi.PaymentTransaction, sender: abi.Account) -> Expr:",
+                1,
+            ),
+        ),
+        (
+            76,
+            (
+                "def deposit(payment: abi.PaymentTransaction, sender: abi.Account) -> Expr:",
+                1,
+            ),
+        ),
+        (77, ("router._build_impl(rci)", 0)),
+        (78, ("router._build_impl(rci)", 0)),
+        (79, ("router._build_impl(rci)", 0)),
+        (
+            80,
+            (
+                "def deposit(payment: abi.PaymentTransaction, sender: abi.Account) -> Expr:",
+                1,
+            ),
+        ),
+        (81, ("router._build_impl(rci)", 0)),
+        (82, ("router._build_impl(rci)", 0)),
+        (83, ("router._build_impl(rci)", 0)),
+        (
+            84,
+            (
+                "def deposit(payment: abi.PaymentTransaction, sender: abi.Account) -> Expr:",
+                1,
+            ),
+        ),
+        (85, ("router._build_impl(rci)", 0)),
+        (
+            86,
+            (
+                "def deposit(payment: abi.PaymentTransaction, sender: abi.Account) -> Expr:",
+                1,
+            ),
+        ),
+        (
+            87,
+            (
+                "def deposit(payment: abi.PaymentTransaction, sender: abi.Account) -> Expr:",
+                1,
+            ),
+        ),
+        (
+            88,
+            (
+                "def deposit(payment: abi.PaymentTransaction, sender: abi.Account) -> Expr:",
+                1,
+            ),
+        ),
+        (
+            89,
+            (
+                "def deposit(payment: abi.PaymentTransaction, sender: abi.Account) -> Expr:",
+                1,
+            ),
+        ),
+        (
+            90,
+            (
+                "def deposit(payment: abi.PaymentTransaction, sender: abi.Account) -> Expr:",
+                1,
+            ),
+        ),
+        (
+            91,
+            (
+                "def deposit(payment: abi.PaymentTransaction, sender: abi.Account) -> Expr:",
+                1,
+            ),
+        ),
+        (
+            92,
+            (
+                "def deposit(payment: abi.PaymentTransaction, sender: abi.Account) -> Expr:",
+                1,
+            ),
+        ),
+        (93, ("router._build_impl(rci)", 0)),
+        (
+            94,
+            (
+                "def deposit(payment: abi.PaymentTransaction, sender: abi.Account) -> Expr:",
+                1,
+            ),
+        ),
+        (95, ("router._build_impl(rci)", 0)),
+        (96, ("router._build_impl(rci)", 0)),
+        (97, ("router._build_impl(rci)", 0)),
+        (98, ("router._build_impl(rci)", 0)),
+        (99, ("router._build_impl(rci)", 0)),
+        (100, ("router._build_impl(rci)", 0)),
+        (101, ("router._build_impl(rci)", 0)),
+        (102, ("router._build_impl(rci)", 0)),
+        (103, ("router._build_impl(rci)", 0)),
+        (104, ("router._build_impl(rci)", 0)),
+        (105, ("router._build_impl(rci)", 0)),
+        (106, ("router._build_impl(rci)", 0)),
+        (107, ("router._build_impl(rci)", 0)),
+        (108, ("router._build_impl(rci)", 0)),
+        (109, ("router._build_impl(rci)", 0)),
+        (110, ("router._build_impl(rci)", 0)),
+        (111, ("router._build_impl(rci)", 0)),
+        (112, ("router._build_impl(rci)", 0)),
+        (113, ("router._build_impl(rci)", 0)),
+        (114, ("router._build_impl(rci)", 0)),
+        (115, ("router._build_impl(rci)", 0)),
+        (116, ("router._build_impl(rci)", 0)),
+        (117, ("router._build_impl(rci)", 0)),
+        (118, ("router._build_impl(rci)", 0)),
+        (119, ("router._build_impl(rci)", 0)),
+        (120, ("router._build_impl(rci)", 0)),
+        (121, ("router._build_impl(rci)", 0)),
+        (122, ("router._build_impl(rci)", 0)),
+        (123, ("router._build_impl(rci)", 0)),
+        (124, ("router._build_impl(rci)", 0)),
+        (125, ("router._build_impl(rci)", 0)),
+        (126, ("router._build_impl(rci)", 0)),
+        (127, ("router._build_impl(rci)", 0)),
+        (128, ("router._build_impl(rci)", 0)),
+        (129, ("router._build_impl(rci)", 0)),
+        (130, ("router._build_impl(rci)", 0)),
+        (131, ("router._build_impl(rci)", 0)),
+        (132, ("router._build_impl(rci)", 0)),
+        (133, ("router._build_impl(rci)", 0)),
+        (134, ("router._build_impl(rci)", 0)),
+        (135, ("router._build_impl(rci)", 0)),
+        (136, ("router._build_impl(rci)", 0)),
+        (137, ("router._build_impl(rci)", 0)),
+        (138, ("Bytes('lost')", 0)),
+        (139, ("Bytes('lost')", 0)),
+        (140, ("App.globalGet(Bytes('lost'))", 0)),
+        (141, ("Txn.sender()", 0)),
+        (142, ("Bytes('balance')", 0)),
+        (143, ("App.localGet(Txn.sender(), Bytes('balance'))", 0)),
+        (
+            144,
+            (
+                "App.globalGet(Bytes('lost')) + App.localGet(Txn.sender(), Bytes('balance'))",
+                0,
+            ),
+        ),
+        (
+            145,
+            (
+                "App.globalPut(Bytes('lost'), App.globalGet(Bytes('lost')) + App.localGet(Txn.sender(), Bytes('balance')))",
+                0,
+            ),
+        ),
+        (146, ("router._build_impl(rci)", 0)),
+        (147, ("router._build_impl(rci)", 0)),
+        (148, ("router._build_impl(rci)", 0)),
+        (149, ("Approve()", 0)),
+        (150, ("Approve()", 0)),
+        (151, ("router._build_impl(rci)", 0)),
+        (152, ("router._build_impl(rci)", 0)),
+        (153, ("router._build_impl(rci)", 0)),
+        (154, ("router._build_impl(rci)", 0)),
+        (155, ("router._build_impl(rci)", 0)),
+        (156, ("Approve()", 0)),
+        (157, ("Approve()", 0)),
+        (158, ("def assert_sender_is_creator() -> Expr:", 1)),
+        (159, ("def assert_sender_is_creator() -> Expr:", 1)),
+        (160, ("def assert_sender_is_creator() -> Expr:", 1)),
+        (161, ("Txn.sender()", 0)),
+        (162, ("Global.creator_address()", 0)),
+        (163, ("Txn.sender() == Global.creator_address()", 0)),
+        (164, ("Assert(Txn.sender() == Global.creator_address())", 0)),
+        (165, ("def assert_sender_is_creator() -> Expr:", 1)),
+        (
+            166,
+            (
+                "def deposit(payment: abi.PaymentTransaction, sender: abi.Account) -> Expr:",
+                1,
+            ),
+        ),
+        (
+            167,
+            (
+                "def deposit(payment: abi.PaymentTransaction, sender: abi.Account) -> Expr:",
+                1,
+            ),
+        ),
+        (
+            168,
+            (
+                "def deposit(payment: abi.PaymentTransaction, sender: abi.Account) -> Expr:",
+                1,
+            ),
+        ),
+        (169, ("router._build_impl(rci)", 0)),
+        (170, ("router._build_impl(rci)", 0)),
+        (171, ("payment.get()", 0)),
+        (172, ("payment.get().sender()", 0)),
+        (173, ("sender.address()", 0)),
+        (174, ("sender.address()", 0)),
+        (175, ("payment.get().sender() == sender.address()", 0)),
+        (176, ("Assert(payment.get().sender() == sender.address())", 0)),
+        (177, ("payment.get()", 0)),
+        (178, ("payment.get().receiver()", 0)),
+        (179, ("Global.current_application_address()", 0)),
+        (180, ("payment.get().receiver() == Global.current_application_address()", 0)),
+        (
+            181,
+            (
+                "Assert(payment.get().receiver() == Global.current_application_address())",
+                0,
+            ),
+        ),
+        (182, ("sender.address()", 0)),
+        (183, ("sender.address()", 0)),
+        (184, ("Bytes('balance')", 0)),
+        (185, ("sender.address()", 0)),
+        (186, ("sender.address()", 0)),
+        (187, ("Bytes('balance')", 0)),
+        (188, ("App.localGet(sender.address(), Bytes('balance'))", 0)),
+        (189, ("payment.get()", 0)),
+        (190, ("payment.get().amount()", 0)),
+        (
+            191,
+            (
+                "App.localGet(sender.address(), Bytes('balance')) + payment.get().amount()",
+                0,
+            ),
+        ),
+        (
+            192,
+            (
+                "App.localPut(sender.address(), Bytes('balance'), App.localGet(sender.address(), Bytes('balance')) + payment.get().amount())",
+                0,
+            ),
+        ),
+        (
+            193,
+            (
+                "def deposit(payment: abi.PaymentTransaction, sender: abi.Account) -> Expr:",
+                1,
+            ),
+        ),
+        (194, ("def getBalance(user: abi.Account, *, output: abi.Uint64) -> Expr:", 1)),
+        (195, ("def getBalance(user: abi.Account, *, output: abi.Uint64) -> Expr:", 1)),
+        (196, ("def getBalance(user: abi.Account, *, output: abi.Uint64) -> Expr:", 1)),
+        (197, ("user.address()", 0)),
+        (198, ("Bytes('balance')", 0)),
+        (199, ("App.localGet(user.address(), Bytes('balance'))", 0)),
+        (200, ("def getBalance(user: abi.Account, *, output: abi.Uint64) -> Expr:", 1)),
+        (201, ("def withdraw(amount: abi.Uint64, recipient: abi.Account) -> Expr:", 1)),
+        (202, ("def withdraw(amount: abi.Uint64, recipient: abi.Account) -> Expr:", 1)),
+        (203, ("def withdraw(amount: abi.Uint64, recipient: abi.Account) -> Expr:", 1)),
+        (204, ("router._build_impl(rci)", 0)),
+        (205, ("router._build_impl(rci)", 0)),
+        (206, ("Txn.sender()", 0)),
+        (207, ("Bytes('balance')", 0)),
+        (208, ("Txn.sender()", 0)),
+        (209, ("Bytes('balance')", 0)),
+        (210, ("App.localGet(Txn.sender(), Bytes('balance'))", 0)),
+        (211, ("amount.get()", 0)),
+        (212, ("App.localGet(Txn.sender(), Bytes('balance')) - amount.get()", 0)),
+        (
+            213,
+            (
+                "App.localPut(Txn.sender(), Bytes('balance'), App.localGet(Txn.sender(), Bytes('balance')) - amount.get())",
+                0,
+            ),
+        ),
+        (214, ("InnerTxnBuilder.Begin()", 0)),
+        (
+            215,
+            (
+                "def deposit(payment: abi.PaymentTransaction, sender: abi.Account) -> Expr:",
+                1,
+            ),
+        ),
+        (
+            216,
+            (
+                "InnerTxnBuilder.SetFields({TxnField.type_enum: TxnType.Payment, TxnField.receiver: recipient.address(), TxnField.amount: amount.get(), TxnField.fee: Int(0)})",
+                0,
+            ),
+        ),
+        (217, ("recipient.address()", 0)),
+        (218, ("recipient.address()", 0)),
+        (
+            219,
+            (
+                "InnerTxnBuilder.SetFields({TxnField.type_enum: TxnType.Payment, TxnField.receiver: recipient.address(), TxnField.amount: amount.get(), TxnField.fee: Int(0)})",
+                0,
+            ),
+        ),
+        (220, ("amount.get()", 0)),
+        (
+            221,
+            (
+                "InnerTxnBuilder.SetFields({TxnField.type_enum: TxnType.Payment, TxnField.receiver: recipient.address(), TxnField.amount: amount.get(), TxnField.fee: Int(0)})",
+                0,
+            ),
+        ),
+        (222, ("Int(0)", 0)),
+        (
+            223,
+            (
+                "InnerTxnBuilder.SetFields({TxnField.type_enum: TxnType.Payment, TxnField.receiver: recipient.address(), TxnField.amount: amount.get(), TxnField.fee: Int(0)})",
+                0,
+            ),
+        ),
+        (224, ("InnerTxnBuilder.Submit()", 0)),
+        (225, ("def withdraw(amount: abi.Uint64, recipient: abi.Account) -> Expr:", 1)),
+    ]
+    assert len(expected) == len(actual)
+    print(list(enumerate(actual)))
+
+    for i, a in enumerate(actual):
+        assert (
+            e := expected[i][1]
+        ) == a, f"""discrepancy at index {i=} 
+expected:
+{e}
+!= actual:
+{a}"""
diff --git a/tests/unit/sourcemap_rps_test.py b/tests/unit/sourcemap_rps_test.py
new file mode 100644
index 000000000..e662ed8a1
--- /dev/null
+++ b/tests/unit/sourcemap_rps_test.py
@@ -0,0 +1,87 @@
+from configparser import ConfigParser
+from pathlib import Path
+from unittest import mock
+
+import pytest
+
+RPS = Path.cwd() / "tests" / "teal"
+
+
+@pytest.fixture
+def mock_ConfigParser():
+    patcher = mock.patch.object(ConfigParser, "getboolean", return_value=True)
+    patcher.start()
+    yield
+    patcher.stop()
+
+
+def compare_and_assert(file, actual):
+    with open(file, "r") as f:
+        expected_lines = f.read().splitlines()
+        actual_lines = actual.splitlines()
+        assert len(expected_lines) == len(actual_lines)
+        assert expected_lines == actual_lines
+
+
+def no_regressions_rps():
+    from pyteal import compileTeal, Mode
+    from tests.teal.rps import approval_program
+
+    compiled = compileTeal(approval_program(), Mode.Application, version=6)
+    compare_and_assert(RPS / "rps.teal", compiled)
+
+
+@pytest.mark.serial
+def test_annotated_rps(mock_ConfigParser):
+    from pyteal import Compilation, Mode
+    from pyteal.compiler.sourcemap import _PyTealSourceMapper
+    from tests.teal.rps import approval_program
+
+    with open(RPS / "rps.teal", "r") as f:
+        expected_teal = f.read()
+
+    with open(RPS / "rps_annotated.teal", "r") as f:
+        expected_annotated = f.read()
+
+    cbundle = Compilation(approval_program(), Mode.Application, version=6).compile(
+        with_sourcemap=True,
+        annotate_teal=True,
+        annotate_teal_headers=True,
+        annotate_teal_concise=False,
+    )
+    teal, annotated = cbundle.teal, cbundle.sourcemap.annotated_teal
+
+    assert expected_teal == teal
+    assert expected_annotated == annotated
+    _PyTealSourceMapper._validate_annotated(
+        False, teal.splitlines(), annotated.splitlines()
+    )
+
+
+@pytest.mark.serial
+def test_no_regression_with_sourcemap_as_configured_rps():
+    no_regressions_rps()
+
+
+@pytest.mark.serial
+def test_no_regression_with_sourcemap_enabled_rps():
+    from pyteal.stack_frame import NatalStackFrame
+
+    originally = NatalStackFrame._no_stackframes
+    NatalStackFrame._no_stackframes = False
+
+    no_regressions_rps()
+
+    NatalStackFrame._no_stackframes = originally
+
+
+@pytest.mark.serial
+def test_no_regression_with_sourcemap_disabled_rps():
+    from pyteal.stack_frame import NatalStackFrame
+
+    originally = NatalStackFrame._no_stackframes
+    NatalStackFrame._no_stackframes = True
+
+    no_regressions_rps()
+
+    NatalStackFrame._no_stackframes = originally
diff --git a/tests/unit/sourcemap_test.py b/tests/unit/sourcemap_test.py
new file mode 100644
index 000000000..b9a1d570b
--- /dev/null
+++ b/tests/unit/sourcemap_test.py
@@ -0,0 +1,541 @@
+"""
+This test would typically reside right next to `pyteal/compiler/sourcemap.py`.
+However, since the path `pyteal/compiler` is on the StackFrame._internal_paths
+blacklist, we need to move the test elsewhere to get reliable results.
+"""
+
+import ast
+import json
+import time
+from configparser import ConfigParser
+from pathlib import Path
+from unittest import mock
+
+import pytest
+
+from pyteal.compiler.sourcemap import R3SourceMap, R3SourceMapJSON
+
+ALGOBANK = Path.cwd() / "examples" / "application" / "abi"
+
+
+@pytest.fixture
+def context_StackFrame_keep_all_debugging():
+    from pyteal.stack_frame import NatalStackFrame
+
+    NatalStackFrame._keep_all_debugging = True
+    yield
+    NatalStackFrame._keep_all_debugging = False
+
+
+@pytest.mark.serial
+def test_frames(context_StackFrame_keep_all_debugging):
+    from pyteal.stack_frame import NatalStackFrame
+
+    originally = NatalStackFrame._no_stackframes
+    NatalStackFrame._no_stackframes = False
+
+    this_file, this_func = "sourcemap_test.py", "test_frames"
+    this_lineno, this_frame = 38, NatalStackFrame()._frames[1]
+    code = (
+        f"    this_lineno, this_frame = {this_lineno}, NatalStackFrame()._frames[1]\n"
+    )
+    this_col_offset, this_end_col_offset = 34, 51
+    frame_info, node = this_frame.frame_info, this_frame.node
+
+    assert frame_info.filename.endswith(this_file)
+    assert this_func == frame_info.function
+    assert frame_info.code_context
+    assert len(frame_info.code_context) == 1
+    assert code == frame_info.code_context[0]
+    assert this_lineno == frame_info.lineno
+
+    assert node
+    assert this_lineno == node.lineno == node.end_lineno
+    assert this_col_offset == node.col_offset
+    assert this_end_col_offset == node.end_col_offset
+    assert isinstance(node, ast.Call)
+    assert isinstance(node.parent, ast.Attribute)  # type: ignore
+    assert isinstance(node.parent.parent, ast.Subscript)  # type: ignore
+
+    NatalStackFrame._no_stackframes = originally
+
+
+@pytest.mark.serial
+def test_TealMapItem_source_mapping():
+    from pyteal.stack_frame import NatalStackFrame
+
+    originally = NatalStackFrame._no_stackframes
+
+    NatalStackFrame._no_stackframes = False
+
+    import pyteal as pt
+    from pyteal.compiler.sourcemap import TealMapItem
+
+    expr = pt.Int(0) + pt.Int(1)
+    expr_line_offset, expr_str = 50, "expr = pt.Int(0) + pt.Int(1)"
+
+    def mock_teal(ops):
+        return [f"{i+1}. {op}" for i, op in enumerate(ops)]
+
+    components = []
+    b = expr.__teal__(pt.CompileOptions())[0]
+    while b:
+        components.extend(b.ops)
+        b = b.nextBlock  # type: ignore
+
+    teals = mock_teal(components)
+    tmis = [
+        TealMapItem(op.expr.stack_frames._frames[0].as_pyteal_frame(), i, teals[i], op)
+        for i, op in enumerate(components)
+    ]
+
+    mock_source_lines = [""] * 500
+    mock_source_lines[expr_line_offset] = expr_str
+    source_files = ["sourcemap_test.py"]
+    r3sm = R3SourceMap(
+        filename="dohhh.teal",
+        source_root="~",
+        entries={(i, 0): tmi.source_mapping() for i, tmi in enumerate(tmis)},
+        index=[(0,) for _ in range(3)],
+        file_lines=list(map(lambda x: x.teal_line, tmis)),
+        source_files=source_files,
+        source_files_lines=[mock_source_lines],
+    )
+    expected_json = '{"version": 3, "sources": ["tests/unit/sourcemap_test.py"], "names": [], "mappings": "AAyEW;AAAY;AAAZ", "file": "dohhh.teal", "sourceRoot": "~"}'
+
+    assert expected_json == json.dumps(r3sm.to_json())
+
+    r3sm_unmarshalled = R3SourceMap.from_json(
+        R3SourceMapJSON(**json.loads(expected_json)),  # type: ignore
+        sources_content_override=["\n".join(mock_source_lines)],
+        target="\n".join(teals),
+    )
+
+    # TODO: test various properties of r3sm_unmarshalled
+
+    assert expected_json == json.dumps(r3sm_unmarshalled.to_json())
+
+    NatalStackFrame._no_stackframes = originally
+
+
+def compare_and_assert(file, actual):
+    with open(file, "r") as f:
+        expected_lines = f.read().splitlines()
+        actual_lines = actual.splitlines()
+        assert len(expected_lines) == len(actual_lines)
+        assert expected_lines == actual_lines
+
+
+def no_regressions_algobank():
+    from examples.application.abi.algobank import router
+    from pyteal import OptimizeOptions
+
+    approval, clear, contract = router.compile_program(
+        version=6, optimize=OptimizeOptions(scratch_slots=True)
+    )
+
+    compare_and_assert(
+        ALGOBANK / "algobank.json", json.dumps(contract.dictify(), indent=4)
+    )
+    compare_and_assert(ALGOBANK / "algobank_clear_state.teal", clear)
+    compare_and_assert(ALGOBANK / "algobank_approval.teal", approval)
+
+
+@pytest.mark.serial
+def test_no_regression_with_sourcemap_as_configured_algobank():
+    no_regressions_algobank()
+
+
+@pytest.mark.serial
+def test_no_regression_with_sourcemap_enabled_algobank():
+    from pyteal.stack_frame import NatalStackFrame
+
+    originally = NatalStackFrame._no_stackframes
+    NatalStackFrame._no_stackframes = False
+
+    no_regressions_algobank()
+
+    NatalStackFrame._no_stackframes = originally
+
+
+@pytest.mark.serial
+def test_no_regression_with_sourcemap_disabled_algobank():
+    from pyteal.stack_frame import NatalStackFrame
+
+    originally = NatalStackFrame._no_stackframes
+    NatalStackFrame._no_stackframes = True
+
+    no_regressions_algobank()
+
+    NatalStackFrame._no_stackframes = originally
+
+
+@pytest.mark.serial
+def test_sourcemap_fails_because_unconfigured():
+    from examples.application.abi.algobank import router
+    from pyteal import OptimizeOptions
+    from pyteal.errors import SourceMapDisabledError
+
+    with pytest.raises(SourceMapDisabledError) as smde:
+        router.compile(
+            version=6,
+            optimize=OptimizeOptions(scratch_slots=True),
+            with_sourcemaps=True,
+        )
+
+    assert "pyteal.ini" in str(smde.value)
+
+
+@pytest.mark.serial
+def test_config():
+    from pyteal.stack_frame import NatalStackFrame
+
+    config = ConfigParser()
+    config.read([".flake8", "mypy.ini", "pyteal.ini"])
+
+    assert [
+        "flake8",
+        "mypy",
+        "mypy-semantic_version.*",
+        "mypy-pytest.*",
+        "mypy-algosdk.*",
+        "pyteal",
+        "pyteal-source-mapper",
+    ] == config.sections()
+
+    assert ["ignore", "per-file-ignores", "ban-relative-imports"] == config.options(
+        "flake8"
+    )
+
+    assert ["enabled", "debug"] == config.options("pyteal-source-mapper")
+
+    assert config.getboolean("pyteal-source-mapper", "enabled") is False
+    assert NatalStackFrame.sourcemapping_is_off() is True
+
+    originally = NatalStackFrame._no_stackframes
+    NatalStackFrame._no_stackframes = False
+
+    NatalStackFrame._no_stackframes = False
+    assert NatalStackFrame.sourcemapping_is_off() is False
+    assert NatalStackFrame.sourcemapping_is_off(_force_refresh=True) is True
+
+    NatalStackFrame._no_stackframes = originally
+
+
+def test_PyTealSourceMapper_validate_build_annotate():
+    from pyteal import TealInternalError
+    from pyteal.compiler.sourcemap import _PyTealSourceMapper
+
+    # --- CONSTRUCTOR VALIDATIONS --- #
+    match = "Please provide non-empty teal_chunks"
+    with pytest.raises(TealInternalError, match=match):
+        _PyTealSourceMapper([], [], build=False, annotate_teal=False)
+
+    match = "Please provide non-empty components"
+    with pytest.raises(TealInternalError, match=match):
+        _PyTealSourceMapper(["a chunk"], [], build=False, annotate_teal=False)
+
+    # --- BUILD VALIDATIONS --- #
+    ptsm = _PyTealSourceMapper(
+        ["a chunk"], ["a component", "another"], build=False, annotate_teal=False
+    )
+
+    def full_match(s):
+        return f"""{s}
+        {_PyTealSourceMapper.UNEXPECTED_ERROR_SUFFIX}"""
+
+    match = full_match(
+        r"expected same number of teal chunks \(1\) and components \(2\)"
+    )
+    with pytest.raises(TealInternalError, match=match):
+        ptsm.build()
+
+    ptsm.teal_chunks = ptsm.components = []
+    match = full_match("cannot generate empty source map: no components")
+    with pytest.raises(TealInternalError, match=match):
+        ptsm.build()
+
+    teal_chunks = ["first line\nsecond line", "third line\nfourth line\nfifth line"]
+    teal = [
+        "first line",
+        "second line",
+        "third line",
+        "fourth line",
+        "fifth line",
+    ]
+    i = 0
+    for chunk in teal_chunks:
+        for line in chunk.splitlines():
+            assert teal[i] == line
+            i += 1
+
+    def mock_TealMapItem(s):
+        tmi = mock.Mock()
+        tmi.teal_line = s
+        return tmi
+
+    def mock_R3SourceMap(lines):
+        r3sm = mock.Mock()
+        r3sm.file_lines = lines
+        return r3sm
+
+    ptsm.teal_chunks = teal_chunks
+    ptsm._cached_tmis = [mock_TealMapItem(s) for s in teal]
+    ptsm._cached_r3sourcemap = mock_R3SourceMap(teal)
+
+    ptsm._validate_build()
+    ptsm.teal_chunks.append("sixth line")
+    match = full_match(
+        r"teal chunks has 6 teal lines which doesn't match the number of cached TealMapItem's \(5\)"
+    )
+    with pytest.raises(TealInternalError, match=match):
+        ptsm._validate_build()
+
+    ptsm._cached_tmis.append(mock_TealMapItem("sixth line"))
+    ptsm._cached_r3sourcemap.file_lines.append("sixth line")
+    ptsm._validate_build()
+
+    match = full_match(
+        r"teal chunks has 6 teal lines which doesn't match the number of cached TealMapItem's \(7\)"
+    )
+    ptsm._cached_tmis.append(mock_TealMapItem("seventh line"))
+    with pytest.raises(TealInternalError, match=match):
+        ptsm._validate_build()
+
+    del ptsm._cached_tmis[-1]
+    ptsm._validate_build()
+
+    ptsm._cached_tmis[-1] = mock_TealMapItem("NOT the sixth line")
+    match = full_match(
+        r"teal chunk lines don't match TealMapItem's at index 5. \('sixth line' v. 'NOT the sixth line'\)"
+    )
+    with pytest.raises(TealInternalError, match=match):
+        ptsm._validate_build()
+
+    ptsm._cached_tmis[-1] = mock_TealMapItem("sixth line")
+    ptsm._validate_build()
+
+    ptsm._cached_r3sourcemap.file_lines.append("seventh line")
+    match = full_match(
+        r"there are 6 TealMapItem's which doesn't match the number of file_lines in the cached R3SourceMap \(7\)"
+    )
+    with pytest.raises(TealInternalError, match=match):
+        ptsm._validate_build()
+
+    del ptsm._cached_r3sourcemap.file_lines[-1]
+    ptsm._validate_build()
+
+    ptsm._cached_r3sourcemap.file_lines[-1] = "NOT the sixth line"
+    match = full_match(
+        r"TealMapItem's don't match R3SourceMap.file_lines at index 5. \('sixth line' v. 'NOT the sixth line'\)"
+    )
+    with pytest.raises(TealInternalError, match=match):
+        ptsm._validate_build()
+
+    ptsm._cached_r3sourcemap.file_lines[-1] = "sixth line"
+    ptsm._validate_build()
+
+    # --- ANNOTATE VALIDATIONS --- #
+    annotated = [f"{teal}   // some other stuff{i}" for i, teal in enumerate(teal)]
+    omit_headers = True
+    ptsm._validate_annotated(omit_headers, teal, annotated)
+
+    omit_headers = False
+    match = full_match(
+        r"mismatch between count of teal_lines \(6\) and annotated_lines \(6\) for the case omit_headers=False"
+    )
+    with pytest.raises(TealInternalError, match=match):
+        ptsm._validate_annotated(omit_headers, teal, annotated)
+
+    annotated_w_headers = ["// some header"] + annotated
+    ptsm._validate_annotated(omit_headers, teal, annotated_w_headers)
+
+    omit_headers = True
+    match = full_match(
+        r"mismatch between count of teal_lines \(6\) and annotated_lines \(7\) for the case omit_headers=True"
+    )
+    with pytest.raises(TealInternalError, match=match):
+        ptsm._validate_annotated(omit_headers, teal, annotated_w_headers)
+
+    annotated_3 = annotated[3]
+    annotated[3] = "doesn't begin with the teal line"
+    match = full_match(
+        r"annotated teal ought to begin exactly with the teal line but line 4 \[doesn't begin with the teal line\] doesn't start with \[fourth line\]"
+    )
+    with pytest.raises(TealInternalError, match=match):
+        ptsm._validate_annotated(omit_headers, teal, annotated)
+
+    annotated[3] = annotated_3
+    ptsm._validate_annotated(omit_headers, teal, annotated)
+
+    annotated_w_headers[4] = "doesn't begin with the teal line"
+    omit_headers = False
+    with pytest.raises(TealInternalError, match=match):
+        ptsm._validate_annotated(omit_headers, teal, annotated_w_headers)
+
+    annotated_w_headers[4] = annotated_3
+    ptsm._validate_annotated(omit_headers, teal, annotated_w_headers)
+
+    omit_headers = True
+    annotated_2 = annotated[2]
+    annotated[2] = f"{teal[2]}   some other stuff not all // commented out"
+    match = full_match(
+        rf"annotated teal ought to begin exactly with the teal line followed by annotation in comments but line 3 \[{annotated[2]}\] has non-commented out annotations"
+    )
+    with pytest.raises(TealInternalError, match=match):
+        ptsm._validate_annotated(omit_headers, teal, annotated)
+
+    annotated[2] = annotated_2
+    ptsm._validate_annotated(omit_headers, teal, annotated)
+
+    omit_headers = False
+    annotated_w_headers[3] = f"{teal[2]}   some other stuff not all // commented out"
+    with pytest.raises(TealInternalError, match=match):
+        ptsm._validate_annotated(omit_headers, teal, annotated_w_headers)
+
+    annotated_w_headers[3] = annotated_2
+    ptsm._validate_annotated(omit_headers, teal, annotated_w_headers)
+
+    # --- ANNOTATE VALIDATIONS - SPECIAL CASE --- #
+    meth_sig = "deposit(pay,account)void"
+    special = f"method {meth_sig}      //    (30)"
+    teal.append(special)
+    annotated_w_headers.append(special)
+    ptsm._validate_annotated(omit_headers, teal, annotated_w_headers)
+
+
+@pytest.mark.skip(
+    reason="""Supressing this flaky test as 
+router_test::test_router_compile_program_idempotence is similar in its goals
+and we expect flakiness to persist until https://github.com/algorand/pyteal/issues/199
+is finally addressed """
+)
+@pytest.mark.serial
+def test_idempotent():
+    # make sure we get clean up properly and therefore get idempotent results
+    from examples.application.abi.algobank import router
+    from pyteal import OptimizeOptions
+
+    approval1, clear1, contract1 = (
+        func := lambda: router.compile_program(
+            version=6, optimize=OptimizeOptions(scratch_slots=True)
+        )
+    )()
+    approval2, clear2, contract2 = func()
+
+    assert contract1.dictify() == contract2.dictify()
+    assert len(clear1.splitlines()) == len(clear2.splitlines())
+    assert clear1 == clear2
+    assert len(approval1.splitlines()) == len(approval2.splitlines())
+    assert approval1 == approval2
+
+
+# ---- BENCHMARKS - SKIPPED BY DEFAULT ---- #
+
+
+def time_for_n_secs(f, n):
+    start = time.time()
+
+    def since():
+        return time.time() - start
+
+    total_time = 0.0
+    snapshots = [0.0]
+    while total_time < n:
+        f()
+        total_time = since()
+        snapshots.append(total_time)
+
+    trials = [snapshots[i + 1] - s for i, s in enumerate(snapshots[:-1])]
+    return trials, total_time
+
+
+def simple_compilation():
+    from examples.application.abi.algobank import router
+    from pyteal import OptimizeOptions
+
+    router.compile_program(version=6, optimize=OptimizeOptions(scratch_slots=True))
+
+
+def source_map_compilation():
+    from examples.application.abi.algobank import router
+    from pyteal import OptimizeOptions
+
+    router.compile(
+        version=6,
+        optimize=OptimizeOptions(scratch_slots=True),
+        with_sourcemaps=True,
+    )
+
+
+def annotated_teal():
+    from examples.application.abi.algobank import router
+    from pyteal import OptimizeOptions
+
+    compilation = router.compile(
+        version=6,
+        optimize=OptimizeOptions(scratch_slots=True),
+        with_sourcemaps=True,
+    )
+
+    assert compilation.approval_sourcemapper
+
+    return compilation.approval_sourcemapper.annotated_teal()
+
+
+summaries_only = True
+
+
+def trial(func):
+    trials, tot = time_for_n_secs(simple_compilation, 10)
+    avg = tot / len(trials)
+    N = len(trials)
+    trials = "" if summaries_only else f"{trials=}"
+    print(
+        f"""
+{func.__name__}: {avg=}, {N=}
+{trials}"""
+    )
+
+
+@pytest.mark.skip(reason="Benchmarks are too slow to run every time")
+@pytest.mark.serial
+def test_time_benchmark_under_config():
+    from pyteal.stack_frame import NatalStackFrame
+
+    print(f"{NatalStackFrame.sourcemapping_is_off()=}")
+
+    trial(simple_compilation)
+    trial(simple_compilation)
+
+    assert False
+
+
+@pytest.mark.skip(reason="Benchmarks are too slow to run every time")
+@mock.patch.object(ConfigParser, "getboolean", return_value=True)
+@pytest.mark.serial
+def test_time_benchmark_sourcemap_enabled(_):
+    """
+    UPSHOT: expect deterioration of (5 to 15)X when enabling source maps.
+    """
+    from pyteal.stack_frame import NatalStackFrame
+
+    print(f"{NatalStackFrame.sourcemapping_is_off()=}")
+    print(
+        """
+keep_all: bool = True,
+stop_after_first_pyteal: bool = True,
+keep_one_frame_only: bool = True,
+"""
+    )
+
+    trial(simple_compilation)
+    trial(simple_compilation)
+
+    trial(source_map_compilation)
+    trial(source_map_compilation)
+
+    trial(annotated_teal)
+    trial(annotated_teal)
+
+    assert False