Repository: crytic/tealer (language: python)

Issue:
Improve readme + Improve regex tool
Update readme, and make the regex tool not crash if there is no match :) 


Discussion / hints:

Fix patch:
diff --git a/.github/workflows/darglint.yml b/.github/workflows/darglint.yml
new file mode 100644
index 0000000..7aa8947
--- /dev/null
+++ b/.github/workflows/darglint.yml
@@ -0,0 +1,26 @@
+---
+name: Darglint
+
+on:
+  push:
+    branches:
+      - main
+      - dev
+  pull_request:
+
+concurrency:
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: true
+
+jobs:
+  tests:
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@v3
+    - name: Set up Python 3.8
+      uses: actions/setup-python@v4
+      with:
+        python-version: 3.8
+    - name: Run Tests
+      run: |
+        bash scripts/ci_darglint.sh
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 898212f..b330d1a 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -7,7 +7,7 @@ If you're unsure where to start, we recommend our [`good first issue`](https://g
 Bug reports and feature suggestions can be submitted to our issue tracker. For bug reports, attaching the contract that caused the bug will help us in debugging and resolving the issue quickly. If you find a security vulnerability, do not open an issue; email opensource@trailofbits.com instead.
 
 ## Questions
-Questions can be submitted to the issue tracker, but you may get a faster response if you ask in our [chat room](https://empireslacking.herokuapp.com/) (in the #ethereum channel).
+Questions can be submitted to the issue tracker, but you may get a faster response if you ask in our [chat room](https://slack.empirehacking.nyc/) (in the #ethereum channel).
 
 ## One punch editable developement version
 Do something like this to clone the repository and install all development dependencies in a python virtual environment
diff --git a/README.md b/README.md
index baed0da..ccdd0e4 100644
--- a/README.md
+++ b/README.md
@@ -3,8 +3,11 @@
 Tealer is a static analyzer for [Teal](https://developer.algorand.org/docs/features/asc1/) code. It parses the Teal program, and builds its CFG. The analyzer comes with a set of vulnerabilities detectors and printers allowing to quickly review the contracts.
 
 - [Features](#features)
+  - [Detectors](#detectors): Vulnerabilities detectors
+  - [Printers](#printers): Visual information
+  - [Regular expression](#Regular expression): Regular expression engine
 - [How to install](#how-to-install)
-- [How to run](#how-to-run)
+- [Group configuration](#group-configuration)
 
 ## Features
 
@@ -14,21 +17,25 @@ Run Tealer on a Teal contract:
 tealer program.teal
 ```
 
+
 For additional configuration, see the [Usage](https://github.com/crytic/tealer/wiki/Usage) documentation.
 
 ### Detectors
 
-Num | Detectors | What it Detects | Applies To | Impact | Confidence |
---- | --- | --- | --- | --- | --- |
-1 | `is-deletable` | [Deletable Applications](https://github.com/crytic/tealer/wiki/Detector-Documentation#deletable-application) | Stateful | High | High
-2 | `is-updatable` | [Upgradable Applications](https://github.com/crytic/tealer/wiki/Detector-Documentation#upgradable-application) | Stateful | High | High
-3 | `unprotected-deletable` | [Unprotected Deletable Applications](https://github.com/crytic/tealer/wiki/Detector-Documentation#unprotected-deletable-application) | Stateful | High | High
-4 | `unprotected-updatable` | [Unprotected Upgradable Applications](https://github.com/crytic/tealer/wiki/Detector-Documentation#unprotected-updatable-application) | Stateful | High | High
-5 | `group-size-check` | [Usage of absolute indexes without validating GroupSize](https://github.com/crytic/tealer/wiki/Detector-Documentation#missing-groupsize-validation) | Stateless, Stateful | High | High
-6 | `can-close-account` | [Missing CloseRemainderTo field Validation](https://github.com/crytic/tealer/wiki/Detector-Documentation#missing-closeremainderto-field-validation) | Stateless | High | High
-7 | `can-close-asset` | [Missing AssetCloseTo Field Validation](https://github.com/crytic/tealer/wiki/Detector-Documentation#missing-assetcloseto-field-validation) | Stateless | High | High
-8 | `missing-fee-check` | [Missing Fee Field Validation](https://github.com/crytic/tealer/wiki/Detector-Documentation#missing-fee-field-validation) | Stateless | High | High
-9 | `rekey-to` | [Rekeyable Logic Signatures](https://github.com/crytic/tealer/wiki/Detector-Documentation#rekeyable-logicsig) | Stateless | High | High
+| Num | Detector                | What it detects                                                                                                                                     | Applies To          | Impact       | Confidence |
+|-----|-------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------|---------------------|--------------|------------|
+| 1   | `is-deletable`          | [Deletable Applications](https://github.com/crytic/tealer/wiki/Detector-Documentation#deletable-application)                                        | Stateful            | High         | High       |
+| 2   | `is-updatable`          | [Upgradable Applications](https://github.com/crytic/tealer/wiki/Detector-Documentation#upgradable-application)                                      | Stateful            | High         | High       |
+| 3   | `unprotected-deletable` | [Unprotected Deletable Applications](https://github.com/crytic/tealer/wiki/Detector-Documentation#unprotected-deletable-application)                | Stateful            | High         | High       |
+| 4   | `unprotected-updatable` | [Unprotected Upgradable Applications](https://github.com/crytic/tealer/wiki/Detector-Documentation#unprotected-updatable-application)               | Stateful            | High         | High       |
+| 5   | `group-size-check`      | [Usage of absolute indexes without validating GroupSize](https://github.com/crytic/tealer/wiki/Detector-Documentation#missing-groupsize-validation) | Stateless, Stateful | High         | High       |
+| 6   | `can-close-account`     | [Missing CloseRemainderTo field Validation](https://github.com/crytic/tealer/wiki/Detector-Documentation#missing-closeremainderto-field-validation) | Stateless           | High         | High       |
+| 7   | `can-close-asset`       | [Missing AssetCloseTo Field Validation](https://github.com/crytic/tealer/wiki/Detector-Documentation#missing-assetcloseto-field-validation)         | Stateless           | High         | High       |
+| 8   | `missing-fee-check`     | [Missing Fee Field Validation](https://github.com/crytic/tealer/wiki/Detector-Documentation#missing-fee-field-validation)                           | Stateless           | High         | High       |
+| 9   | `rekey-to`              | [Rekeyable Logic Signatures](https://github.com/crytic/tealer/wiki/Detector-Documentation#rekeyable-logicsig)                                       | Stateless           | High         | High       |
+| 10  | `constant-gtxn`         | [Unoptimized Gtxn](https://github.com/crytic/tealer/wiki/Detector-Documentation#Unoptimized-Gtxn)                                                   | Stateless           | Optimization | High       |
+| 11  | `self-access`           | [Unoptimized self access](https://github.com/crytic/tealer/wiki/Detector-Documentation#Unoptimized-self-access)                                     | Stateless           | Optimization | High       |
+| 12  | `sender-access`         | [Unoptimized Gtxn](https://github.com/crytic/tealer/wiki/Detector-Documentation#Unoptimized-sender-access)                                          | Stateless           | Optimization | High       |
 
 
 For more information, see
@@ -38,14 +45,37 @@ For more information, see
 
 ### Printers
 
-- Print CFG (`--print-cfg`): Export the CFG of the contract to a dot file.
-- `human-summary`: Print a human-readable summary of the contract.
-- `function-cfg`: Export the CFG of each subroutine in the contract, works for contracts written in version 4 or greater.
-- `call-graph`: Export the call-graph of the contract to a dot file, works for contracts written in version 4 or greater.
+| Num | Printer               | What it prints                                    |
+|-----|-----------------------|---------------------------------------------------|
+| 1   | `call-graph`          | Export the call graph of contract to a dot file   |
+| 2   | `cfg`                 | Export the CFG of entire contract                 |
+| 3   | `human-summary`       | Print a human-readable summary of the contract    |
+| 4   | `subroutine-cfg`      | Export the CFG of each subroutine                 |
+| 5   | `transaction-context` | Output possible values of GroupIndices, GroupSize |
+
 
 Printers output [`dot`](https://graphviz.org/) files.
 Use `xdot` to open the files  (`sudo apt install xdot`).
 
+### Regular expression
+
+Tealer can detect if there is a path between a given label and a set of instruction using the `--regex` flag: `tealer --contract file.teal --regex regex.txt`.
+
+The Regular expression file must be on the form:
+```txt
+label =>
+  ins1
+  ins2
+```
+
+If there is a match, tealer will generate a DOT file with the graph.
+
+For an example, run `tealer --contract tests/regex/vote_approval.teal --regex tests/regex/regex.txt`, with:
+- [tests/regex/regex.txt](./tests/regex/regex.txt)
+- [tests/regex/vote_approval.teal](./tests/regex/vote_approval.teal)
+
+Which will generate `regex_result.dot`.
+
 ## How to install
 
 ### Using Git
@@ -58,4 +88,14 @@ python3 setup.py install
 We recommend to install the tool in a [virtualenv](https://virtualenvwrapper.readthedocs.io/en/latest/).
 
 
-## TODO: Add License
\ No newline at end of file
+## Group configuration
+
+To help tealer reasons about applications that are meant to be run in a group of transaction, the user can provide the group information through a configuration file:
+- See the [ANS configuration](tests/group_transactions/ans/ans_config.yaml) example
+- See [Lightweight group information specification](https://forum.algorand.org/t/lightweight-group-information-specification/9735) discussion.
+
+The file format is still in development, and it is likely to evolve in the future
+
+## License
+
+Slither is licensed and distributed under the AGPLv3 license. [Contact us](opensource@trailofbits.com) if you're looking for an exception to the terms.
\ No newline at end of file
diff --git a/plugin_example/rekey_plugin/tealer_rekey_plugin/detectors/rekeyto_stateless.py b/plugin_example/rekey_plugin/tealer_rekey_plugin/detectors/rekeyto_stateless.py
index d5a9fa0..8931c1d 100644
--- a/plugin_example/rekey_plugin/tealer_rekey_plugin/detectors/rekeyto_stateless.py
+++ b/plugin_example/rekey_plugin/tealer_rekey_plugin/detectors/rekeyto_stateless.py
@@ -1,95 +1,103 @@
-from typing import List, TYPE_CHECKING
+"""Detector for finding execution paths missing RekeyTo check."""
+
+from typing import List, TYPE_CHECKING, Tuple
 
 from tealer.detectors.abstract_detector import (
     AbstractDetector,
     DetectorClassification,
     DetectorType,
 )
-from tealer.teal.global_field import ZeroAddress
-from tealer.teal.instructions.instructions import Addr, Eq, Global, Int, Neq, Return, Txn
-from tealer.teal.instructions.transaction_field import RekeyTo
+from tealer.detectors.utils import detect_missing_tx_field_validations_group
+from tealer.utils.output import ExecutionPaths
+
 
 if TYPE_CHECKING:
     from tealer.teal.basic_blocks import BasicBlock
-    from tealer.teal.instructions.instructions import Instruction
-    from tealer.utils.output import SupportedOutput
-
-
-def _is_rekey_check(ins1: "Instruction", ins2: "Instruction") -> bool:
-    """check if ins1 is txn RekeyTo and ins2 is global ZeroAddress or addr ..."""
-    if isinstance(ins1, Txn) and isinstance(ins1.field, RekeyTo):
-        if isinstance(ins2, Global) and isinstance(ins2.field, ZeroAddress):
-            return True
-        if isinstance(ins2, Addr):
-            return True
-    return False
+    from tealer.utils.output import ListOutput
+    from tealer.teal.context.block_transaction_context import BlockTransactionContext
+    from tealer.teal.teal import Teal
 
 
 class CanRekey(AbstractDetector):  # pylint: disable=too-few-public-methods
-
-    NAME = "rekeyTo-stateless"
-    DESCRIPTION = "Detect paths with a missing RekeyTo check"
+    """Detector to find execution paths missing RekeyTo check.
+
+    TEAL, from version 2 onwards supports rekeying of accounts.
+    An account can be rekeyed to a different address. Once rekeyed,
+    rekeyed address has entire authority over the account. Contract
+    Accounts can also be rekeyed. If RekeyTo field of the transaction
+    is set to malicious actor's address, then they can control the account
+    funds, assets directly bypassing the contract's restrictions.
+
+    This detector tries to find execution paths that approve the algorand
+    transaction("return 1") and doesn't check the RekeyTo transaction field.
+    Additional to checking rekeying of it's own contract, detector also finds
+    execution paths that doesn't check RekeyTo field of other transactions
+    in the atomic group.
+    """
+
+    NAME = "rekey-to"
+    DESCRIPTION = "Rekeyable Logic Signatures"
     TYPE = DetectorType.STATELESS
 
     IMPACT = DetectorClassification.HIGH
     CONFIDENCE = DetectorClassification.HIGH
 
-    WIKI_TITLE = "Can Rekey Contract"
-    WIKI_DESCRIPTION = "Detect paths with a missing RekeyTo check"
+    WIKI_URL = "https://github.com/crytic/tealer/wiki/Detector-Documentation#rekeyable-logicsig"
+    WIKI_TITLE = "Rekeyable LogicSig"
+    WIKI_DESCRIPTION = (
+        "Logic signature does not validate `RekeyTo` field."
+        " Attacker can submit a transaction with `RekeyTo` field set to their address and take control over the account."
+        " More at [building-secure-contracts/not-so-smart-contracts/algorand/rekeying]"
+        "(https://github.com/crytic/building-secure-contracts/tree/master/not-so-smart-contracts/algorand/rekeying)"
+    )
     WIKI_EXPLOIT_SCENARIO = """
-Rekeying is an Algorand feature which enables an account holder to give authorization of their account to different address, whereby users can maintain a single static public address while updating the key controlling the assets.
-Rekeying is done by using *rekey-to* transaction which is a payment transaction with `rekey-to` parameter set to new authorized address.
-
-if a stateless contract, approves a payment transaction without checking the `rekey-to` parameter then one can set the authorization address to the contract account and withdraw funds directly bypassing all the checks.
-
-Attacker creates a payment transaction using the contract with `rekey-to` set to their address. After rekeying, attacker transfer's the assets using their private key bypassing the conditions defined in the contract.
+```py
+def withdraw(...) -> Expr:
+    return Seq(
+        [
+            Assert(
+                And(
+                    Txn.type_enum() == TxnType.Payment,
+                    Txn.first_valid() % period == Int(0),
+                    Txn.last_valid() == Txn.first_valid() + duration,
+                    Txn.receiver() == receiver,
+                    Txn.amount() == amount,
+                    Txn.first_valid() < timeout,
+                )
+            ),
+            Approve(),
+        ]
+    )
+```
+
+Alice signs the logic-sig to allow recurring payments to Bob.\
+ Eve uses the logic-sig and submits a valid transaction with `RekeyTo` field set to her address.\
+ Eve takes over Alice's account.
 """
 
     WIKI_RECOMMENDATION = """
-Add a check in the contract code verifying that `RekeyTo` property of any transaction is set to `ZeroAddress`.
+Validate `RekeyTo` field in the LogicSig.
 """
 
-    def _check_rekey_to(
-        self,
-        bb: "BasicBlock",
-        current_path: List["BasicBlock"],
-        paths_without_check: List[List["BasicBlock"]],
-    ) -> None:
-        if bb in current_path:
-            return
-
-        current_path = current_path + [bb]
-
-        stack: List["Instruction"] = []
-
-        for ins in bb.instructions:
-            if isinstance(ins, Return):
-                if len(ins.prev) == 1:
-                    prev = ins.prev[0]
-                    if isinstance(prev, Int) and prev.value == 0:
-                        return
-
-                paths_without_check.append(current_path)
-                return
-
-            if isinstance(ins, (Eq, Neq)) and len(stack) >= 2:
-                one = stack[-1]
-                two = stack[-2]
-                if _is_rekey_check(one, two) or _is_rekey_check(two, one):
-                    return
-
-            stack.append(ins)
-
-        for next_bb in bb.next:
-            self._check_rekey_to(next_bb, current_path, paths_without_check)
+    def detect(self) -> "ListOutput":
+        """Detect execution paths with missing CloseRemainderTo check.
 
-    def detect(self) -> "SupportedOutput":
+        Returns:
+            ExecutionPaths instance containing the list of vulnerable execution
+            paths along with name, check, impact, confidence and other detector
+            information.
+        """
 
-        paths_without_check: List[List["BasicBlock"]] = []
-        self._check_rekey_to(self.teal.bbs[0], [], paths_without_check)
+        def checks_field(block_ctx: "BlockTransactionContext") -> bool:
+            # return False if RekeyTo field can have any address.
+            # return True if RekeyTo should have some address or zero address
+            return not block_ctx.rekeyto.any_addr
 
-        description = "Lack of txn RekeyTo check allows rekeying the account to"
-        description += " attacker controlled address and control the account"
-        filename = "missing_rekeyto_check_stateless"
+        output: List[
+            Tuple["Teal", List[List["BasicBlock"]]]
+        ] = detect_missing_tx_field_validations_group(self.tealer, checks_field)
+        detector_output: "ListOutput" = []
+        for contract, vulnerable_paths in output:
+            detector_output.append(ExecutionPaths(contract, self, vulnerable_paths))
 
-        return self.generate_result(paths_without_check, description, filename)
+        return detector_output
diff --git a/plugin_example/template/tealer_plugin/detectors/example.py b/plugin_example/template/tealer_plugin/detectors/example.py
index af26326..82962b2 100644
--- a/plugin_example/template/tealer_plugin/detectors/example.py
+++ b/plugin_example/template/tealer_plugin/detectors/example.py
@@ -8,7 +8,7 @@
 
 
 if TYPE_CHECKING:
-    from tealer.utils.output import SupportedOutput
+    from tealer.utils.output import ListOutput
 
 
 class Example(AbstractDetector):  # pylint: disable=too-few-public-methods
@@ -28,7 +28,7 @@ class Example(AbstractDetector):  # pylint: disable=too-few-public-methods
     WIKI_EXPLOIT_SCENARIO = ""
     WIKI_RECOMMENDATION = ""  # this will be shown in json output as help message
 
-    def detect(self) -> "SupportedOutput":
+    def detect(self) -> "ListOutput":
         """This method will be called if this detector is chosen.
 
         Implement this method to return output in one of the supported formats (ExecutionPaths).
diff --git a/scripts/ci_darglint.sh b/scripts/ci_darglint.sh
new file mode 100644
index 0000000..6d98fcc
--- /dev/null
+++ b/scripts/ci_darglint.sh
@@ -0,0 +1,11 @@
+#!/usr/bin/env bash
+
+pip install darglint
+
+darglint .
+
+if [ $? -ne 0 ]
+then
+    echo "Darglint failed. Please fix the above issues"
+    exit 255
+fi
diff --git a/setup.py b/setup.py
index b346231..1b08761 100644
--- a/setup.py
+++ b/setup.py
@@ -16,8 +16,9 @@
         "py-algorand-sdk",
         "pycryptodomex",
         "requests",
+        "pyyaml",
     ],
-    extras_require={"dev": ["pylint==2.13.4", "black==22.3.0", "mypy==0.942"]},
+    extras_require={"dev": ["pylint==2.13.4", "black==22.3.0", "mypy==0.942", "pytest-cov"]},
     license="AGPL-3.0",
     long_description=long_description,
     entry_points={"console_scripts": ["tealer = tealer.__main__:main"]},
diff --git a/tealer/__main__.py b/tealer/__main__.py
index e09934d..1c9b064 100644
--- a/tealer/__main__.py
+++ b/tealer/__main__.py
@@ -63,11 +63,11 @@
 """
 
 import argparse
-import re
-import os
-import sys
 import json
 import logging
+import os
+import re
+import sys
 from pathlib import Path
 from typing import List, Any, Type, Tuple, TYPE_CHECKING, Optional, Union, Sequence
 
@@ -76,29 +76,42 @@
 from tealer.detectors.abstract_detector import AbstractDetector, DetectorType
 from tealer.exceptions import TealerException
 from tealer.printers.abstract_printer import AbstractPrinter
-from tealer.teal.instructions.instructions import ContractType
-from tealer.teal.parse_teal import parse_teal
 from tealer.utils.algoexplorer import (
     get_application_using_app_id,
     logic_sig_from_contract_account,
     logic_sig_from_txn_id,
 )
-from tealer.utils.command_line import (
-    get_detectors_and_printers,
+from tealer.utils.command_line.command_output import (
     output_detectors,
     output_printers,
     output_to_markdown,
     output_wiki,
 )
+from tealer.utils.command_line.common import (
+    get_detectors_and_printers,
+    validate_command_line_options,
+    # handle_detect,
+    init_tealer_from_single_contract,
+    init_tealer_from_config,
+)
+from tealer.utils.command_line.group_config import (
+    read_config_from_file,
+)
+from tealer.utils.output import ROOT_OUTPUT_DIRECTORY, ExecutionPaths
+from tealer.utils.regex.regex import run_regex
+from tealer.utils.teal_enums import ExecutionMode
 
 if TYPE_CHECKING:
     from tealer.teal.teal import Teal
-    from tealer.utils.output import SupportedOutput
+    from tealer.tealer import Tealer
+    from tealer.utils.output import ListOutput
 
 
 # from slither: slither/__main__.py
 def choose_detectors(
-    args: argparse.Namespace, all_detector_classes: List[Type[AbstractDetector]], teal: "Teal"
+    args: argparse.Namespace,
+    all_detector_classes: List[Type[AbstractDetector]],
+    teal: Optional["Teal"] = None,
 ) -> List[Type[AbstractDetector]]:
     """Select detectors from available list based on command line arguments.
 
@@ -111,6 +124,7 @@ def choose_detectors(
         args: Namespace object representing the command line arguments selected
             by the user.
         all_detector_classes: list of all available detectors.
+        teal: The Teal contract.
 
     Returns:
         list of chosen detectors from the available list using the tealer
@@ -130,9 +144,9 @@ def choose_detectors(
         # IF there is no detectors provided:
         # - Stateful: run everything expect the stateless detectors
         # - Stateless: run only stateless and stateless & stateful
-        if teal.mode == ContractType.STATEFULL:
+        if teal is not None and teal.mode == ExecutionMode.STATEFUL:
             detectors_to_run = [d for d in detectors_to_run if d.TYPE != DetectorType.STATELESS]
-        if teal.mode == ContractType.STATELESS:
+        if teal is not None and teal.mode == ExecutionMode.STATELESS:
             detectors_to_run = [
                 d
                 for d in detectors_to_run
@@ -169,7 +183,7 @@ def choose_printers(
     Args:
         args: Namespace object representing the command line arguments selected
             by the user.
-        all_detector_classes: list of all available printers.
+        all_printer_classes: list of all available printers.
 
     Returns:
         list of chosen printers from the available list based on tealer
@@ -211,11 +225,9 @@ def parse_args(
 
     parser = argparse.ArgumentParser(
         description="TealAnalyzer",
-        usage="tealer program.teal [flag]",
+        usage="tealer [--init | --detect | --print ] --contracts ...",
     )
 
-    parser.add_argument("program", help="program.teal")
-
     parser.add_argument(
         "--version",
         help="displays the current version",
@@ -223,6 +235,19 @@ def parse_args(
         action="version",
     )
 
+    parser.add_argument(
+        "--contracts",
+        help="List of files/directories to search for .teal files",
+        action="store",
+        nargs="+",
+    )
+
+    parser.add_argument(
+        "--group-config",
+        help="The group configuration file",
+        action="store",
+    )
+
     parser.add_argument(
         "--network",
         help='Algorand network to fetch the contract from, ("mainnet" or "testnet"). defaults to "mainnet".',
@@ -230,10 +255,24 @@ def parse_args(
         default="mainnet",
     )
 
+    parser.add_argument(
+        "--regex",
+        help="Provide the regex file",
+        action="store",
+    )
+
+    group_init = parser.add_argument_group("Initialize")
     group_detector = parser.add_argument_group("Detectors")
     group_printer = parser.add_argument_group("Printers")
     group_misc = parser.add_argument_group("Additional options")
 
+    group_init.add_argument(
+        "--init",
+        help="Initializes group-config.yaml file given the contracts",
+        action="store_true",
+        default=False,
+    )
+
     group_detector.add_argument(
         "--list-detectors",
         help="List available detectors",
@@ -274,13 +313,6 @@ def parse_args(
         default=False,
     )
 
-    group_detector.add_argument(
-        "--all-paths-in-one",
-        help="highlights all the vunerable paths in a single file.",
-        action="store_true",
-        default=False,
-    )
-
     group_detector.add_argument(
         "--filter-paths",
         help="Excludes execution paths matching the regex from detector's output.",
@@ -314,13 +346,6 @@ def parse_args(
         default=None,
     )
 
-    group_misc.add_argument(
-        "--dest",
-        help="destination to save the output files, defaults to current directory",
-        action="store",
-        default=".",
-    )
-
     parser.add_argument("--debug", help=argparse.SUPPRESS, action="store_true", default=False)
     parser.add_argument("--markdown", help=argparse.SUPPRESS, action=OutputMarkdown, default=False)
 
@@ -400,17 +425,14 @@ def __call__(
 
 
 def handle_detectors_and_printers(
-    args: argparse.Namespace,
-    teal: "Teal",
+    tealer: "Tealer",
     detectors: List[Type[AbstractDetector]],
     printers: List[Type[AbstractPrinter]],
-) -> Tuple[List["SupportedOutput"], List]:
+) -> Tuple[List["ListOutput"], List]:
     """Util function to register and run detectors, printers.
 
     Args:
-        args: Namespace object representing the command line arguments selected
-            by the user.
-        teal: Teal object representing the contract being analyzed.
+        tealer: Teal object representing the contract being analyzed.
         detectors: Detector classes to register and run.
         printers: Printer classes to register and run.
 
@@ -419,18 +441,19 @@ def handle_detectors_and_printers(
     """
 
     for detector_cls in detectors:
-        teal.register_detector(detector_cls)
+        tealer.register_detector(detector_cls)
 
     for printer_cls in printers:
-        teal.register_printer(printer_cls)
+        tealer.register_printer(printer_cls)
 
-    return teal.run_detectors(), teal.run_printers(Path(args.dest))
+    return tealer.run_detectors(), tealer.run_printers()
 
 
 def handle_output(
     args: argparse.Namespace,
-    detector_results: List["SupportedOutput"],
+    detector_results: List["ListOutput"],
     _printer_results: List,
+    teal: "Teal",
     error: Optional[str],
 ) -> None:
     """Util function to output tealer results.
@@ -443,18 +466,30 @@ def handle_output(
             by the user.
         detector_results: results of running the selected detectors.
         _printer_results: results of running the selected printers.
+        teal: The contract.
+        error: Error string if any detector or printer resulted in an error.
     """
+    expanded_detector_results: "ListOutput" = []
+    for result in detector_results:
+        expanded_detector_results.extend(result)
 
+    output_directory = ROOT_OUTPUT_DIRECTORY / Path(teal.contract_name)
+    os.makedirs(output_directory, exist_ok=True)
     if args.json is None:
 
         if error is not None:
             print(f"Error: {error}")
             sys.exit(-1)
 
-        for output in detector_results:
-            output.write_to_files(args.dest, args.all_paths_in_one)
+        detectors_with_0_results: List["AbstractDetector"] = []
+        for output in expanded_detector_results:
+            if not output.generate_output(output_directory):
+                detectors_with_0_results.append(output.detector)
+        if detectors_with_0_results:
+            detectors_with_0_results_str = ", ".join(d.NAME for d in detectors_with_0_results)
+            print(f"\n 0 results found for {detectors_with_0_results_str}.")
     else:
-        json_results = [output.to_json() for output in detector_results]
+        json_results = [output.to_json() for output in expanded_detector_results]
 
         json_output = {
             "success": error is not None,
@@ -465,37 +500,40 @@ def handle_output(
         if args.json == "-":
             print(json.dumps(json_output, indent=2))
         else:
-            filename = Path(args.dest) / Path(args.json)
+            filename = output_directory / Path(args.json)
             print(f"json output is written to {filename}")
             with open(filename, "w", encoding="utf-8") as f:
                 f.write(json.dumps(json_output, indent=2))
 
 
-def fetch_contract(args: argparse.Namespace) -> str:
-    program: str = args.program
+def fetch_contract(args: argparse.Namespace) -> Tuple[str, str]:
+    program: str = args.contracts[0]
     network: str = args.network
     b32_regex = "[A-Z2-7]+"
     if program.isdigit():
         # is a number so a app id
         print(f'Fetching application using id "{program}"')
-        return get_application_using_app_id(network, int(program))
+        return get_application_using_app_id(network, int(program)), f"app-{program}"
     if len(program) == 52 and re.fullmatch(b32_regex, program) is not None:
         # is a txn id: base32 encoded. length after encoding == 52
         print(f'Fetching logic-sig contract that signed the transaction "{program}"')
-        return logic_sig_from_txn_id(network, program)
+        return logic_sig_from_txn_id(network, program), f"txn-{program[:8].lower()}"
     if len(program) == 58 and re.fullmatch(b32_regex, program) is not None:
         # is a address. base32 encoded. length after encoding == 58
         print(f'Fetching logic-sig of contract account "{program}"')
-        return logic_sig_from_contract_account(network, program)
+        return logic_sig_from_contract_account(network, program), f"lsig-acc-{program[:8].lower()}"
     # file path
     print(f'Reading contract from file: "{program}"')
     try:
+        contract_name = program[: -len(".teal")] if program.endswith(".teal") else program
+        contract_name = contract_name.lower()
         with open(program, encoding="utf-8") as f:
-            return f.read()
+            return f.read(), contract_name
     except FileNotFoundError as e:
         raise TealerException from e
 
 
+# pylint: disable=too-many-locals,too-many-branches
 def main() -> None:
     """Entry point of the tealer tool.
 
@@ -506,29 +544,51 @@ def main() -> None:
 
     detector_classes, printer_classes = get_detectors_and_printers()
     args = parse_args(detector_classes, printer_classes)
+    validate_command_line_options(args)
 
     default_log = logging.INFO if not args.debug else logging.DEBUG
     for (logger_name, logger_level) in [
         ("Detectors", default_log),
         ("TransactionCtxAnalysis", default_log),
         ("Parsing", default_log),
+        ("Tealer", default_log),
     ]:
         logger = logging.getLogger(logger_name)
         logger.setLevel(logger_level)
 
-    if args.dest != ".":
-        # if output destination directory is not current directory.
-        # create dest directory if is not present
-        os.makedirs(args.dest, exist_ok=True)
-
-    results_detectors: List["SupportedOutput"] = []
+    results_detectors: List["ListOutput"] = []
     _results_printers: List = []
     error = None
-    try:
-        contract_source = fetch_contract(args)
-        teal = parse_teal(contract_source)
+    if args.detectors_to_run is not None and args.group_config is not None:
+        # handle this case specially for now.
+        # handle_detect(args)
+        # sys.exit(1)
+        group_config = read_config_from_file(Path(args.group_config))
+        tealer = init_tealer_from_config(group_config)
+        detector_classes = choose_detectors(args, detector_classes)
+        for detector in detector_classes:
+            tealer.register_detector(detector)
+        group_results = tealer.run_detectors()[0]
+        for output in group_results:
+            # dest is ignored
+            output.generate_output(Path("."))
+        sys.exit(1)
 
-        detector_classes = choose_detectors(args, detector_classes, teal)
+    try:
+        contract_source, contract_name = fetch_contract(args)
+        tealer = init_tealer_from_single_contract(contract_source, contract_name)
+
+        # TODO: handle this as a subcommand instead of a flag
+        if args.regex:
+            default_path = Path("regex_result.dot")
+            if len(tealer.contracts) != 1:
+                print("Regex works only for single contract")
+                return
+            run_regex(tealer.contracts[contract_name], args.regex, default_path)
+            return
+
+        # TODO: decide on default classification for detectors in group transaction context.
+        detector_classes = choose_detectors(args, detector_classes, tealer.contracts[contract_name])
         printer_classes = choose_printers(args, printer_classes)
 
         # if a printer is ran using --print ... don't run all detectors.
@@ -543,7 +603,7 @@ def main() -> None:
             detector_classes = []
 
         results_detectors, _results_printers = handle_detectors_and_printers(
-            args, teal, detector_classes, printer_classes
+            tealer, detector_classes, printer_classes
         )
 
     except TealerException as e:
@@ -551,8 +611,14 @@ def main() -> None:
 
     if args.filter_paths is not None:
         for detector_result in results_detectors:
-            detector_result.filter_paths(args.filter_paths)
-    handle_output(args, results_detectors, _results_printers, error)
+            if isinstance(detector_result, ExecutionPaths):
+                detector_result.filter_paths(args.filter_paths)
+            else:
+                for result in detector_result:
+                    result.filter_paths(args.filter_paths)
+    handle_output(
+        args, results_detectors, _results_printers, tealer.contracts[contract_name], error
+    )
 
 
 if __name__ == "__main__":
diff --git a/tealer/analyses/dataflow/all_constraints.py b/tealer/analyses/dataflow/all_constraints.py
deleted file mode 100644
index 9d9954a..0000000
--- a/tealer/analyses/dataflow/all_constraints.py
+++ /dev/null
@@ -1,5 +0,0 @@
-# pylint: disable=unused-import
-from tealer.analyses.dataflow.int_fields import GroupIndices
-from tealer.analyses.dataflow.txn_types import TxnType
-from tealer.analyses.dataflow.addr_fields import AddrFields
-from tealer.analyses.dataflow.fee_field import FeeField
diff --git a/tealer/analyses/dataflow/transaction_context/__init__.py b/tealer/analyses/dataflow/transaction_context/__init__.py
new file mode 100644
index 0000000..57b1bbf
--- /dev/null
+++ b/tealer/analyses/dataflow/transaction_context/__init__.py
@@ -0,0 +1,225 @@
+"""
+The main purpose of transaction-context analysis is to simplify detectors based on transaction field validations.
+
+Transaction field based detectors:
+    Detectors that report execution paths that do not validate the value of a problematic transaction field such as
+    RekeyTo, CloseRemainderTo, OnCompletion, etc...
+
+The transaction field of the transaction executing the target contract can be accessed in multiple ways:
+
+- The target contract uses `txn {field}`.
+- The target contract use its index in the group and access the transaction field using gtxn.. type instructions.
+- A different contract uses the target contract's group index and access the target transaction field using gtxn.. type instructions.
+
+Code patterns for accessing the field:
+
+Index types:
+
+    - Absolute index: The index of the target transaction is given by the user input or is fixed at compile time.
+    - Relative index: The user input or the compiled code contains the offset. The index is calculated using `Txn.group_index() +/- offset`.
+
+The code patterns for target contract validating its own transaction fields:
+
+- The field is accessed using `txn {field}`
+- The target contract validates its index and uses absolute index:
+    - The absolute index is fixed at compile time and `gtxn {index} {field}` is used.
+    - The absolute index is fixed at compile time but is passed from one location to other:
+        - The absolute index is pushed to stack using an `int` instruction and `gtxns {field}` is used.
+            - The int instruction is pushed in the same basic block.
+            - The int instruction is pushed in different basic block but the basic block is of the
+                same subroutine.
+            - The int instruction value is passed as a subroutine's argument.
+    - The index is computed using `Txn.group_index()` and `gtxns {field}` is used.
+    - The index is an user input.
+- The target contract uses the group size (`global GroupSize`) and iterates over all transactions in the group
+    - accesses all indexes in the group sequentially, the index is fixed at compile time.
+        - Above listed code patterns for compile time fixed indexes.
+    - Iterates in a loop
+    - index is supplied as a subroutine argument.
+
+The code patterns for other contracts validating the target's transaction fields:
+
+- The other contract uses the group size (`global GroupSize`) and iterates over all transactions in the group
+    - Above listed code patterns
+- The other contracts validates the target contract's index and uses absolute index.
+    - The absolute index is fixed at compile time and `gtxn {index} {field}` is used.
+    - The absolute index is fixed at compile time but is passed from one location to other:
+        - Above listed patterns
+- The other contract uses relative index and accesses target's contract fields.
+    - The offset is fixed at compile time
+        - The index is calculated immediately after `int` instruction pushes the offset: relative index = Txn.group_index +/- offset.
+            - The calculated index is used by `gtxns {field}` instruction in the same block.
+            - The calculated index is passed around to different blocks.
+            - The calculated index is passed as argument to a subroutine.
+        - The offset is passed around to different blocks, to a subroutine.
+            - Above listed patterns.
+    - The offset is user input
+        - Above listed patterns.
+
+Code patterns for comparing the field:
+
+- The accessed field is directly compared against the target value.
+- The accessed field is compared against a value which reduces the list of possible values.
+- The value of the accessed field is passed around:
+    - basic blocks of the same subroutine.
+    - is passed to an subroutine which compares the field.
+- The value is compared using transitive properties: e.g `Gtxn[0].fee + Gtxn[1].fee < 3000`, fees of both transactions are compared.
+
+Code patterns for asserting the comparison:
+
+- The result of the comparison is directly asserted using `assert`.
+- The result of the comparison is used by conditional branching instructions `bz`, `bnz`.
+- The results of multiple comparisons are combined using `&&`, `||` and `!`.
+    - The combined result is asserted using `assert`
+    - The combined result is used by `bz` or `bnz` instructions.
+
+
+The contract analyzed by Tealer can use combination of any of the above patterns. The transaction field analysis tries to
+cover as many combinations as possible.
+
+The goal of the transaction field analysis:
+
+- Given an contract, it gives the possible values for the target transaction field of the target contract and possible values
+    for the target transaction field of other contracts in the group.
+
+The current results of transaction field analysis:
+
+- Given an contract, it gives the possible values for the target transaction field of the target contract.
+    Only some of the patterns are supported.
+
+
+How is it implemented:
+
+DataflowTransactionContext (in generic.py)
+
+The generic.py contains the utilities that are needed for any transaction field analysis.
+
+The execution flow for an analysis is as follows:
+
+- DataflowTransactionContext.run_analysis()
+    - Compute possible values for the transaction field at basic block level (DataflowTransactionContext._block_level_constraints()).
+        - i.e if the contract would contain just this basic block, then what would be the possible
+            transaction fields.
+    - Compute possible values for the transaction field at path level (DataflowTransactionContext._path_level_constraints())
+        - This is used when comparison result is used by `bz`, `bnz` instructions.
+    - Combine the transaction field information of all basic blocks and edges in the CFG.
+        - This is done using iterative fixed-point algorithm.
+    - The end result:
+        - For each block, list of possible values for the transaction field such that
+            if and only if the transaction field during execution is one of the possible values:
+            - The execution might reach this basic block
+            - The execution might reach end of the basic block
+            - The execution might reach from this basic block to a leaf block.
+
+DataflowTransactionContext._block_level_constraints() computes possible values at block level.
+    - finds `assert`, `return` instructions in the block.
+    - Uses DataflowTransactionContext._get_asserted() on the input argument of `assert` and `return` instructions
+        to find possible values for the field.
+
+DataflowTransactionContext._path_level_constraints() computes possible values at path level.
+    - checks the basic block has conditional branch instruction: `bz` or `bnz`.
+    - Uses DataflowTransactionContext._get_asserted() on the input argument of `bz` or `bnz` to find
+        possible values for the field for each out-edge(next) of the block.
+
+DataflowTransactionContext._get_asserted() computes possible values for the field given an comparison result.
+    - The input expression might contain `&&`, `||` or `!` and the result of the expression depends on multiple
+        small comparisons.
+    - This function finds all individual comparison expressions and uses DataflowTransactionContext._get_asserted_single()
+        on each individual comparisons.
+    - Given the possible values for each individual comparison, this function combines them to find possible values
+        for the input comparison.
+
+DataflowTransactionContext._get_asserted_single() is specific for individual fields and is implemented by the corresponding classes.
+
+What are possible valus returned by _get_asserted() and _get_asserted_single()?
+
+The returned values is a Tuple containing `true_values` and `false_values`.
+
+true_values: Given that the execution of input expression results in non-zero value on top of stack, What values
+    can the transaction field have?
+false_values: Given that the execution of input expression results in zero value on top of stack, What values
+    can the transaction field have?
+
+For example input expression is `Txn.group_index() == 2`, then
+    - if the result is non-zero => group_index must be in [2] => true_values for group_index = [2]
+    - if the result is zero => group_index must be in [0, 1, 3, ..., 15] => false_values for group_index = [0, 1, 3, ..., 15]
+
+_get_asserted_single() returns true_values and false_values for a single direct comparison.
+_get_asserted() combines true_values and false_values for all individual direct comparisons and returns true_values
+    and false_values for the entire input_expression.
+
+How are these true_values and false_values used by _block_level_constraints() and _path_level_constraints?
+
+_block_level_constraints() finds following instructions:
+    - `assert`: Finds the true_values for input expression of `assert` and sets them as possible values
+        For the execution to pass => the result must be non-zero => the transaction field can only have one of true_values to not fail the execution.
+    - `return`: Finds the true_values for input expression of `return` and sets them as possible values
+        For the execution to succeed => the return value must be non-zero => the expression result must be non-zero => the transaction field can have
+        only one of true_values.
+
+_path_level_constraints()
+    - finds conditional branch instructions (`bz` or `bnz`)
+    - finds the edge taken when the result of input expression is non-zero and the edge taken when the result of expression is zero.
+    - Assign true_values as possible values for edge taken when the result of input expression is non-zero
+    - Assign false_values as possible values for edge taken when the result of input expression is zero
+
+_block_level_constraints fills the possible values for each block.
+_path_level_constraints fills the possible values for each edge.
+
+DataflowTransactionContext.run_analysis uses iterative fixed point algorithm to combine information at each block and edge using the information
+for predecessors and successors. This gives the above mentioned end_result.
+
+
+DataflowTransactionContext.BASE_KEYS and DataflowTransactionContext.KEYS_WITH_GTXN
+
+BASE_KEYS: A key represents a transaction field. The possible values for a transaction field are tracked using its corresponding base key.
+    The main reason is: The calculation of true_values and false_values for multiple keys can be same. Using multiple keys
+    allows us to not create multiple classes that does exactly the same. The _get_asserted_single() is given the "key" and it
+    can compute the values for the transaction field corresponding to the key.
+
+    For example, all address fields have similar implementation for computing the true_values and false_values. A single implementation can
+    can be used for all fields.
+
+KEYS_WITH_GTXN: KEYS_WITH_GTXN is a subset of BASE_KEYS.
+    For a given field multiple types of possible values can be calculated.
+    The possible values of the field of the transaction
+        1. executing the contract/function irrespective of the transaction's group index.
+        2. executing the contract when the transaction group index is a particular value.
+        3. at a given absolute index irrespective of which contract is executed.
+        4. at a given relative index irrespective of which contract is executed.
+
+    KEYS_WITH_GTXN contains the BASE_KEYS for which the 2., 3. and 4. type of values should also be computed.
+    For example, computing the 2., 3. and 4. type of information does not make sense for GroupSize and GroupIndex
+    fields. These fields will not be included in KEYS_WITH_GTXN
+
+ANALYSIS_KEYS: List of all keys used for the analysis.
+ANALYSIS_KEYS contains BASE_KEYS and keys to represent type 2., 3. and 4. values for each key in KEYS_WITH_GTXN.
+
+How are values for each type of key calculated?
+
+For type (1), The comparisons made on the value accessed using `txn {field}` are used.
+
+For type (2), ANALYSIS_KEYS contains a key for each KEYS_WITH_GTXN and all possible indices.
+    - The analysis key with index `i` and `field` key of KEYS_WITH_GTXN will store information of type (2)
+    - First, the information at block level and path level is computed using the comparisons made on the field
+        accessed using an `gtxn` instruction with absolute index `i`.
+    - The information will be of type (3) at this step. In order to make it of type (2), the type (1) information
+        is combined with the information at block level and path level.
+    - see DataflowTransactionContext._update_gtxn_constraints()
+
+
+For every field type (1) is computed before type (2).
+Computation of type (2) information requires the possible group indices, so possible group indices are computed before
+any other field is analyzed.
+
+Supported fields
+
+addr_fields.AddrFields handles support of all address type transaction fields. Add the field name to BASE_KEYS to collect values
+for a field.
+
+fee_field.FeeField supports Fee field.
+
+int_fields.GroupIndices supports GroupIndex and GroupSize fields.
+
+txn_types.TxnType supports Transaction type.
+"""
diff --git a/tealer/analyses/dataflow/addr_fields.py b/tealer/analyses/dataflow/transaction_context/addr_fields.py
similarity index 68%
rename from tealer/analyses/dataflow/addr_fields.py
rename to tealer/analyses/dataflow/transaction_context/addr_fields.py
index 855404d..949525f 100644
--- a/tealer/analyses/dataflow/addr_fields.py
+++ b/tealer/analyses/dataflow/transaction_context/addr_fields.py
@@ -1,6 +1,12 @@
 from typing import TYPE_CHECKING, List, Set, Tuple, Callable
 
-from tealer.analyses.dataflow.generic import DataflowTransactionContext
+from tealer.analyses.dataflow.transaction_context.generic import DataflowTransactionContext
+from tealer.analyses.dataflow.transaction_context.utils.key_helpers import (
+    get_gtxn_at_index_key,
+    is_value_matches_key,
+    get_absolute_index_key,
+    get_relative_index_key,
+)
 from tealer.teal.instructions.instructions import (
     Eq,
     Neq,
@@ -10,9 +16,11 @@
 from tealer.teal.global_field import ZeroAddress, CreatorAddress
 from tealer.utils.algorand_constants import ZERO_ADDRESS
 from tealer.analyses.utils.stack_ast_builder import KnownStackValue, UnknownStackValue
+from tealer.utils.algorand_constants import MAX_GROUP_SIZE
 
 if TYPE_CHECKING:
     from tealer.teal.instructions.instructions import Instruction
+    from tealer.teal.basic_blocks import BasicBlock
     from tealer.teal.context.block_transaction_context import (
         AddrFieldValue,
         BlockTransactionContext,
@@ -60,7 +68,16 @@ def _null_set(self, key: str = "") -> Set:
         return set([NO_ADDRESS])
 
     def _union(self, key: str, a: Set, b: Set) -> Set:
-        """A union U = U, A union NullSet = A"""
+        """A union U = U, A union NullSet = A
+
+        Args:
+            key: The analysis key. The values in set :a: and :b: are values for this key.
+            a: Set 1.
+            b: Set 2.
+
+        Returns:
+            Returns union of set a and b.
+        """
         if ANY_ADDRESS in a or ANY_ADDRESS in b:
             return self._universal_set()
 
@@ -74,7 +91,16 @@ def _union(self, key: str, a: Set, b: Set) -> Set:
         return a | b
 
     def _intersection(self, key: str, a: Set, b: Set) -> Set:
-        """A intersection NullSet = NullSet, A intersection U = A"""
+        """A intersection NullSet = NullSet, A intersection U = A
+
+        Args:
+            key: The analysis key. The values in set :a: and :b: are values for this key.
+            a: Set 1.
+            b: Set 2.
+
+        Returns:
+            Returns union of set a and b.
+        """
         if NO_ADDRESS in a or NO_ADDRESS in b:
             return self._null_set()
 
@@ -91,6 +117,15 @@ def _get_asserted_address(self, ins: "Instruction") -> Set[str]:
         """return set of address which are represented by the ins
 
         ZeroAddress is considered to be null set.
+
+        Args:
+            ins: An instruction. The function considers that executing :ins: pushes an
+                "address" value onto the stack. Based on the instruction, the function
+                returns list of possible values.
+
+        Returns:
+            Returns a list of possible address values for the field when that field value is
+            asserted against the output of :ins:.
         """
         if isinstance(ins, Global) and isinstance(ins.field, ZeroAddress):
             # ZeroAddress
@@ -121,24 +156,20 @@ def _get_asserted_txn_gtxn(
             return self._universal_set(), self._universal_set()
 
         if isinstance(arg1, UnknownStackValue):
-            if not isinstance(arg2, UnknownStackValue) and not self._is_txn_or_gtxn(
-                key, arg2.instruction
-            ):
+            if not isinstance(arg2, UnknownStackValue) and not is_value_matches_key(key, arg2):
                 # arg1 is unknown and arg2 is not related to "key"
                 return self._universal_set(), self._universal_set()
             # arg2 is related to "key" but arg1 is unknown
             asserted_addresses = set([SOME_ADDRESS])
         elif isinstance(arg2, UnknownStackValue):
-            if not isinstance(arg1, UnknownStackValue) and not self._is_txn_or_gtxn(
-                key, arg1.instruction
-            ):
+            if not isinstance(arg1, UnknownStackValue) and not is_value_matches_key(key, arg1):
                 # arg2 is unknown and arg1 is not related to "key"
                 return self._universal_set(), self._universal_set()
             # arg1 is related to "key" but arg2 is unknown
             asserted_addresses = set([SOME_ADDRESS])
-        elif self._is_txn_or_gtxn(key, arg1.instruction):
+        elif is_value_matches_key(key, arg1):
             asserted_addresses = self._get_asserted_address(arg2.instruction)
-        elif self._is_txn_or_gtxn(key, arg2.instruction):
+        elif is_value_matches_key(key, arg2):
             asserted_addresses = self._get_asserted_address(arg1.instruction)
 
         if asserted_addresses is None:
@@ -169,13 +200,35 @@ def _store_results(self) -> None:
         for key, addr_field_obj in key_and_addr_obj:
             if key not in self.BASE_KEYS:
                 continue
-            for b in self._teal.bbs:
+            for block in self._function.blocks:
                 self._set_addr_values(
-                    addr_field_obj(b.transaction_context), self._block_contexts[key][b]
+                    addr_field_obj(self._function.transaction_context(block)),
+                    self._block_contexts[key][block],
                 )
                 for idx in range(16):
-                    addr_values = self._block_contexts[self.gtx_key(idx, key)][b]
+                    addr_values = self._block_contexts[get_gtxn_at_index_key(idx, key)][block]
                     self._set_addr_values(
-                        addr_field_obj(b.transaction_context.gtxn_context(idx)),
+                        addr_field_obj(self._function.transaction_context(block).gtxn_context(idx)),
                         addr_values,
                     )
+
+                    abs_addr_values = self._block_contexts[get_absolute_index_key(idx, key)][block]
+                    self._set_addr_values(
+                        addr_field_obj(
+                            self._function.transaction_context(block).absolute_context(idx)
+                        ),
+                        abs_addr_values,
+                    )
+
+                for offset in range(-(MAX_GROUP_SIZE - 1), MAX_GROUP_SIZE):
+                    if offset == 0:
+                        continue
+                    rel_addr_values = self._block_contexts[get_relative_index_key(offset, key)][
+                        block
+                    ]
+                    self._set_addr_values(
+                        addr_field_obj(
+                            self._function.transaction_context(block).relative_context(offset)
+                        ),
+                        rel_addr_values,
+                    )
diff --git a/tealer/analyses/dataflow/transaction_context/all_constraints.py b/tealer/analyses/dataflow/transaction_context/all_constraints.py
new file mode 100644
index 0000000..7725915
--- /dev/null
+++ b/tealer/analyses/dataflow/transaction_context/all_constraints.py
@@ -0,0 +1,5 @@
+# pylint: disable=unused-import
+from tealer.analyses.dataflow.transaction_context.int_fields import GroupIndices
+from tealer.analyses.dataflow.transaction_context.txn_types import TxnType
+from tealer.analyses.dataflow.transaction_context.addr_fields import AddrFields
+from tealer.analyses.dataflow.transaction_context.fee_field import FeeField
diff --git a/tealer/analyses/dataflow/fee_field.py b/tealer/analyses/dataflow/transaction_context/fee_field.py
similarity index 76%
rename from tealer/analyses/dataflow/fee_field.py
rename to tealer/analyses/dataflow/transaction_context/fee_field.py
index 1c3ee4e..ec11d0f 100644
--- a/tealer/analyses/dataflow/fee_field.py
+++ b/tealer/analyses/dataflow/transaction_context/fee_field.py
@@ -1,7 +1,13 @@
 from typing import TYPE_CHECKING, List, Tuple, Optional
 from dataclasses import dataclass
 
-from tealer.analyses.dataflow.generic import DataflowTransactionContext
+from tealer.analyses.dataflow.transaction_context.generic import DataflowTransactionContext
+from tealer.analyses.dataflow.transaction_context.utils.key_helpers import (
+    get_gtxn_at_index_key,
+    is_value_matches_key,
+    get_absolute_index_key,
+    get_relative_index_key,
+)
 from tealer.teal.instructions.instructions import (
     Eq,
     Neq,
@@ -16,7 +22,7 @@
 
 if TYPE_CHECKING:
     from tealer.teal.instructions.instructions import Instruction
-
+    from tealer.teal.basic_blocks import BasicBlock
 
 # It's not always possible to know the integer value used to compare Fee.
 # All the unknown values are represented using FeeValue(is_unknown=True).
@@ -100,8 +106,9 @@ def _get_asserted_max_value(
         Args:
             comparison_ins: Comparison operator.
             compared_value: fee value being compared with.
+
         Returns:
-            Tuple[int, int]: Max possible value that will make the comparison instruction return True and
+            Max possible value that will make the comparison instruction return True and
                 Max possible value that will make the comparison False.
         """
         # U = max_possible_value  # universal set
@@ -144,29 +151,25 @@ def _get_asserted_fee(  # pylint: disable=too-many-branches
                 return FeeValue(), FeeValue()
 
             if isinstance(arg1, UnknownStackValue):
-                if not isinstance(arg2, UnknownStackValue) and not self._is_txn_or_gtxn(
-                    key, arg2.instruction
-                ):
+                if not isinstance(arg2, UnknownStackValue) and not is_value_matches_key(key, arg2):
                     # arg1 is unknown and arg2 is not related to "key"
                     return FeeValue(), FeeValue()
                 # arg2 is related to key and arg1 is some unknown value
                 compared_value = FeeValue(is_unknown=True)
             elif isinstance(arg2, UnknownStackValue):
-                if not isinstance(arg1, UnknownStackValue) and not self._is_txn_or_gtxn(
-                    key, arg1.instruction
-                ):
+                if not isinstance(arg1, UnknownStackValue) and not is_value_matches_key(key, arg1):
                     # arg2 is unknown and arg1 is not related to "key"
                     return FeeValue(), FeeValue()
                 # arg1 is related to "key" and arg2 is some unknown int value
                 compared_value = FeeValue(is_unknown=True)
-            elif self._is_txn_or_gtxn(key, arg1.instruction):
+            elif is_value_matches_key(key, arg1):
                 is_int, value = is_int_push_ins(arg2.instruction)
                 if is_int and isinstance(value, int):
                     compared_value = FeeValue(value=value)
                 else:
                     compared_value = FeeValue(is_unknown=True)
 
-            elif self._is_txn_or_gtxn(key, arg2.instruction):
+            elif is_value_matches_key(key, arg2):
                 is_int, value = is_int_push_ins(arg1.instruction)
                 if is_int and isinstance(value, int):
                     compared_value = FeeValue(value=value)
@@ -188,17 +191,46 @@ def _get_asserted_single(
         return res
 
     def _store_results(self) -> None:
-        for b in self._teal.bbs:
-            max_fee = self._block_contexts[FEE_KEY][b]
+        for block in self._function.blocks:
+            max_fee = self._block_contexts[FEE_KEY][block]
             assert isinstance(max_fee, FeeValue)
             if max_fee.is_unknown:
-                b.transaction_context.max_fee_unknown = True
+                self._function.transaction_context(block).max_fee_unknown = True
             else:
-                b.transaction_context.max_fee = max_fee.value
+                self._function.transaction_context(block).max_fee = max_fee.value
             for idx in range(MAX_GROUP_SIZE):
-                max_fee = self._block_contexts[self.gtx_key(idx, FEE_KEY)][b]
+                max_fee = self._block_contexts[get_gtxn_at_index_key(idx, FEE_KEY)][block]
                 assert isinstance(max_fee, FeeValue)
                 if max_fee.is_unknown:
-                    b.transaction_context.gtxn_context(idx).max_fee_unknown = True
+                    self._function.transaction_context(block).gtxn_context(
+                        idx
+                    ).max_fee_unknown = True
+                else:
+                    self._function.transaction_context(block).gtxn_context(
+                        idx
+                    ).max_fee = max_fee.value
+
+                abs_max_fee = self._block_contexts[get_absolute_index_key(idx, FEE_KEY)][block]
+                assert isinstance(abs_max_fee, FeeValue)
+                if abs_max_fee.is_unknown:
+                    self._function.transaction_context(block).absolute_context(
+                        idx
+                    ).max_fee_unknown = True
+                else:
+                    self._function.transaction_context(block).absolute_context(
+                        idx
+                    ).max_fee = abs_max_fee.value
+
+            for offset in range(-(MAX_GROUP_SIZE - 1), MAX_GROUP_SIZE):
+                if offset == 0:
+                    continue
+                rel_max_fee = self._block_contexts[get_relative_index_key(offset, FEE_KEY)][block]
+                assert isinstance(rel_max_fee, FeeValue)
+                if rel_max_fee.is_unknown:
+                    self._function.transaction_context(block).relative_context(
+                        offset
+                    ).max_fee_unknown = True
                 else:
-                    b.transaction_context.gtxn_context(idx).max_fee = max_fee.value
+                    self._function.transaction_context(block).relative_context(
+                        offset
+                    ).max_fee = rel_max_fee.value
diff --git a/tealer/analyses/dataflow/generic.py b/tealer/analyses/dataflow/transaction_context/generic.py
similarity index 82%
rename from tealer/analyses/dataflow/generic.py
rename to tealer/analyses/dataflow/transaction_context/generic.py
index 2aa081f..26623cf 100644
--- a/tealer/analyses/dataflow/generic.py
+++ b/tealer/analyses/dataflow/transaction_context/generic.py
@@ -227,31 +227,41 @@
 from abc import ABC, abstractmethod
 import logging
 from typing import TYPE_CHECKING, Any, Dict, List, Tuple, Set
+from collections import defaultdict
 
+from tealer.analyses.dataflow.transaction_context.utils.key_helpers import (
+    get_gtxn_at_index_key,
+    get_absolute_index_key,
+    get_relative_index_key,
+)
 from tealer.teal.instructions.instructions import (
     Assert,
     Return,
     BZ,
     BNZ,
     Err,
-    Txn,
-    Gtxn,
     And,
     Or,
     Not,
+    TealerCustomErrInstruction,
+)
+from tealer.utils.analyses import (
+    is_int_push_ins,
+    prev_blocks_global,
+    next_blocks_global,
+    leaf_block_global,
 )
-from tealer.teal.instructions.parse_transaction_field import TX_FIELD_TXT_TO_OBJECT
-from tealer.utils.analyses import is_int_push_ins
 from tealer.utils.algorand_constants import MAX_GROUP_SIZE
 from tealer.analyses.utils.stack_ast_builder import (
     KnownStackValue,
     UnknownStackValue,
-    construct_stack_ast,
+    get_stack_value_for_ins,
     compute_equations,
 )
 
 if TYPE_CHECKING:
     from tealer.teal.teal import Teal
+    from tealer.teal.functions import Function
     from tealer.teal.basic_blocks import BasicBlock
     from tealer.teal.instructions.instructions import Instruction
 
@@ -274,73 +284,70 @@ class DataflowTransactionContext(ABC):  # pylint: disable=too-few-public-methods
     # BASE_KEYS for which transaction context information from `gtxn {i} {field}` is also stored.
     KEYS_WITH_GTXN: List[str] = []  # every key in this list should also present in BASE_KEYS.
 
-    def __init__(self, teal: "Teal"):
-        self._teal: "Teal" = teal
+    def __init__(self, function: "Function"):
+        # self._teal: "Teal" = teal
+        self._function: "Function" = function
         # entry block of CFG
-        self._entry_block: "BasicBlock" = teal.bbs[
-            0
-        ]  # blocks are ordered by entry instruction in parsing stage.
+        self._entry_block: "BasicBlock" = function.entry
         # self._block_contexts[KEY][B] -> block_context of KEY for block B
-        self._block_contexts: Dict[str, Dict["BasicBlock", Any]] = {}
+        self._block_contexts: Dict[str, Dict["BasicBlock", Any]] = defaultdict(dict)
         # self._path_contexts[KEY][Bi][Bj] -> path_context of KEY for path Bj -> Bi
-        self._path_contexts: Dict[str, Dict["BasicBlock", Dict["BasicBlock", Any]]] = {}
+        self._path_contexts: Dict[str, Dict["BasicBlock", Dict["BasicBlock", Any]]] = defaultdict(
+            dict
+        )
         if not self.BASE_KEYS:
             raise IncorrectDataflowTransactionContextInitialization(
                 f"BASE_KEYS are not initialized {self.__class__.__name__}"
             )
 
-    @staticmethod
-    def gtx_key(idx: int, key: str) -> str:
-        """return key used for tracking context of gtxn {idx} {field represented by key}"""
-        return f"GTXN_{idx:02d}_{key}"
-
-    @staticmethod
-    def is_gtx_key(key: str) -> bool:
-        """return if given key represents gtxn {i} {field}"""
-        return key.startswith("GTXN_")
-
-    @staticmethod
-    def get_gtx_ind_and_base_key(key: str) -> Tuple[int, str]:
-        """given a gtx key, return it's index and base key"""
-        # will fail if the key is not a GTXN key.
-        _, ind, base_key = key.split("_")
-        return int(ind), base_key
+    @abstractmethod
+    def _universal_set(self, key: str) -> Any:
+        """Return universal set for the field corresponding to given key
 
-    @staticmethod
-    def _is_txn_or_gtxn(key: str, ins: "Instruction") -> bool:
-        """return True if ins is of form txn {key} or gtxn {ind} {key} else False
+        Args:
+            key: The key used to represent values of a field in the analysis.
 
-        "key" should be string representation of the field. e.g if field is RekeyTo, then
-        key should also be "RekeyTo".
+        Returns:
+            Returns the universal set: list of all possible values for the field tracked by the
+            :key:
         """
-        if DataflowTransactionContext.is_gtx_key(key):
-            idx, field = DataflowTransactionContext.get_gtx_ind_and_base_key(key)
-            return (
-                isinstance(ins, Gtxn)
-                and ins.idx == idx
-                and isinstance(ins.field, TX_FIELD_TXT_TO_OBJECT[field])
-            )
-        return isinstance(ins, Txn) and isinstance(ins.field, TX_FIELD_TXT_TO_OBJECT[key])
-
-    @staticmethod
-    def _get_stack_value(ins: "Instruction") -> KnownStackValue:
-        return construct_stack_ast(ins.bb)[ins]
-
-    @abstractmethod
-    def _universal_set(self, key: str) -> Any:
-        """Return universal set for the field corresponding to given key"""
 
     @abstractmethod
     def _null_set(self, key: str) -> Any:
-        """Return null set for the field corresponding to given key"""
+        """Return null set for the field corresponding to given key
+
+        Args:
+            key: The key used to represent values of a field in the analysis.
+
+        Returns:
+            Returns the null set: empty value for the field tracked by the :key:
+        """
 
     @abstractmethod
     def _union(self, key: str, a: Any, b: Any) -> Any:
-        """return union of a and b, where a, b represent values for the given key"""
+        """return union of a and b, where a, b represent values for the given key
+
+        Args:
+            key: The analysis key. The values in set :a: and :b: are values of this key.
+            a: Set 1.
+            b: Set 2.
+
+        Returns:
+            Returns union of set a and b.
+        """
 
     @abstractmethod
     def _intersection(self, key: str, a: Any, b: Any) -> Any:
-        """return intersection of a and b, where a, b represent values for the given key"""
+        """return intersection of a and b, where a, b represent values for the given key
+
+        Args:
+            key: The analysis key. The values in set :a: and :b: are values of this key.
+            a: Set 1.
+            b: Set 2.
+
+        Returns:
+            Returns union of set a and b.
+        """
 
     @abstractmethod
     def _get_asserted_single(self, key: str, ins_stack_value: KnownStackValue) -> Tuple[Any, Any]:
@@ -350,6 +357,14 @@ def _get_asserted_single(self, key: str, ins_stack_value: KnownStackValue) -> Tu
             values for the key.
         false_values: Given that the result of `ins_stack_value` is ZERO, what are the possible values
             for the key.
+
+        Args:
+            key: The key used to represent values of a field in the analysis.
+            ins_stack_value: The stack value being considered for assertion. The :ins_stack_value: is
+                not the result of "And" or "Or" or "Not" instructions.
+
+        Returns:
+            Returns the "true_values" and "false_values" for the key.
         """
 
     @abstractmethod
@@ -363,6 +378,13 @@ def _get_asserted(self, key: str, ins_stack_value: KnownStackValue) -> Tuple[Any
             values for the key.
         false_values: Given that the result of `ins_stack_value` is ZERO, what are the possible values
             for the key.
+
+        Args:
+            key: The key used to represent values of a field in the analysis.
+            ins_stack_value: The stack value being considered for assertion.
+
+        Returns:
+            Returns the "true_values" and "false_values" for the key.
         """
         if not isinstance(ins_stack_value.instruction, (And, Or, Not)):
             # and not isinstance(ins_stack_value.instruction, Or):
@@ -430,15 +452,20 @@ def _block_level_constraints(  # pylint: disable=too-many-branches
         if block contains assert instruction, values are further constrained using the comparison being
         asserted. Values are stored in self._block_contexts
         self._block_contexts[KEY][B] -> block_context of KEY for block B
+
+        Args:
+            analysis_keys: List of keys, each representing a field.
+            block: The basic block.
+
+        Fills the _block_contexts[:key:][:block:] based on assertions performed by the instructions in the
+        :block:
         """
         for key in analysis_keys:
-            if key not in self._block_contexts:
-                self._block_contexts[key] = {}
             self._block_contexts[key][block] = self._universal_set(key)
 
         for ins in block.instructions:
             if isinstance(ins, Assert):
-                assert_ins_stack_value = self._get_stack_value(ins)
+                assert_ins_stack_value = get_stack_value_for_ins(ins)
                 assert_ins_arg = assert_ins_stack_value.args[0]
                 if isinstance(assert_ins_arg, UnknownStackValue):
                     continue
@@ -451,7 +478,7 @@ def _block_level_constraints(  # pylint: disable=too-many-branches
                     )
             elif isinstance(ins, Return):
                 # if return 0, set possible values to NullSet()
-                return_ins_value = self._get_stack_value(ins)
+                return_ins_value = get_stack_value_for_ins(ins)
                 return_ins_arg = return_ins_value.args[0]
                 if isinstance(return_ins_arg, UnknownStackValue):
                     continue
@@ -468,7 +495,7 @@ def _block_level_constraints(  # pylint: disable=too-many-branches
                     self._block_contexts[key][block] = self._intersection(
                         key, present_values, true_values
                     )
-            elif isinstance(ins, Err):
+            elif isinstance(ins, (Err, TealerCustomErrInstruction)):
                 # if err, set possible values to NullSet()
                 for key in analysis_keys:
                     self._block_contexts[key][block] = self._null_set(key)
@@ -483,21 +510,31 @@ def _path_level_constraints(  # pylint: disable=too-many-branches
         if block contains bz/bnz instruction, possible values are calculated for each branch and
         are stored in self._path_contexts
         self._path_contexts[KEY][Bi][Bj] -> path_context of KEY for path Bj -> Bi
+
+        Args:
+            analysis_keys: List of keys, each representing a field.
+            block: The basic block.
+
+        Fills the _path_contexts[key][Bj][Bi] where :key: is the key in analysis_keys and
+        Bi is the :block: and Bj are next blocks of :block: in the global CFG.
         """
 
         for key in analysis_keys:
-            if key not in self._path_contexts:
-                self._path_contexts[key] = {}
             path_context = self._path_contexts[key]
-            for b in block.next:
+            for b in next_blocks_global(self._function, block):
+                # print(
+                #     f"block: {repr(block)}, b: {repr(b)}, {key:}, block: {block.__hash__()}, b: {b.__hash__()}, block sub: {block.subroutine.name}, b sub: {b.subroutine.name}"
+                # )
                 # path_context[bi][bj]: path context of path bj -> bi, bi is the successor
                 if b not in path_context:
                     path_context[b] = {}
+                    # print(f"block: {repr(block)}, b: {repr(b)}, {key:}, ddd")
+
                 # if there are no constraints, set the possible values to universal set
                 path_context[b][block] = self._universal_set(key)
 
         if isinstance(block.exit_instr, (BZ, BNZ)):
-            exit_ins_stack_value = self._get_stack_value(block.exit_instr)
+            exit_ins_stack_value = get_stack_value_for_ins(block.exit_instr)
             exit_ins_arg = exit_ins_stack_value.args[0]
             if isinstance(exit_ins_arg, UnknownStackValue):
                 return
@@ -549,11 +586,18 @@ def _update_gtxn_constraints(self, keys_with_gtxn: List[str], block: "BasicBlock
         of `txn` when index of `txn` is `i`.
 
         This requires that group_indices analysis is done before any other analysis.
+
+        Args:
+            keys_with_gtxn: The list of base keys whose gtxn {idx} {RekeyTo} should be tracked.
+            block: The basic block
+
+        Fill the _block_contexts[gtx_key][block] using the possible group indices of the transaction and possible
+        values of the base key.
         """
         for key in keys_with_gtxn:
             for ind in range(MAX_GROUP_SIZE):
-                gtx_key = self.gtx_key(ind, key)
-                if ind in block.transaction_context.group_indices:
+                gtx_key = get_gtxn_at_index_key(ind, key)
+                if ind in self._function.transaction_context(block).group_indices:
                     # txn can have index {ind}
                     # gtxn {ind} {field} can have a value if and only if {txn} {field} can also have that value
                     self._block_contexts[gtx_key][block] = self._intersection(
@@ -575,14 +619,17 @@ def _calculate_reachin(
             reachin_information = self._null_set(key)
 
         path_context = self._path_contexts[key]
-        for prev_b in block.prev:
+        for prev_b in prev_blocks_global(self._function, block):
+            # print(f"block:", repr(block),"prev_b ", repr(prev_b), f"block: {block.__hash__()}, prev_b: {prev_b.__hash__()}")
+            # print(path_context)
+            # print(path_context[block])
             reachin_information = self._union(
                 key,
                 reachin_information,
                 self._intersection(key, reachout[prev_b], path_context[block][prev_b]),
             )
 
-        if block.callsub_block is not None:
+        if block.is_sub_return_point:
             # this block is the return point for callsub instruction present in `block.callsub_block`
             # execution will only reach this block, if it reaches `block.callsub_block`
             reachin_information = self._intersection(
@@ -611,13 +658,18 @@ def _merge_information_forward(
         return updated
 
     def forward_analyis(self, analysis_keys: List[str], worklist: List["BasicBlock"]) -> None:
-        """Perform forward analysis for analysis_keys and update self._block_contexts"""
+        """Perform forward analysis for analysis_keys and update self._block_contexts
+
+        Args:
+            analysis_keys: List of keys.
+            worklist: List of basic blocks considered for the analysis. Initial worklist.
+        """
         # reachout for all analysis keys. global_reachout[key] -> reachout of key.
         # global_reachout[key][block] -> reachout of block for key.
         global_reachout: Dict[str, Dict["BasicBlock", Any]] = {}
         for key in analysis_keys:
             global_reachout[key] = {}
-            for b in self._teal.bbs:
+            for b in self._function.blocks:
                 global_reachout[key][b] = self._null_set(key)
 
         while worklist:
@@ -626,8 +678,15 @@ def forward_analyis(self, analysis_keys: List[str], worklist: List["BasicBlock"]
             updated = self._merge_information_forward(analysis_keys, b, global_reachout)
 
             if updated:
-                return_point_block = [b.sub_return_point] if b.sub_return_point is not None else []
-                for bi in b.next + return_point_block:
+                # if b is callsub block the information of the called subroutine and return point need to be computed.
+                # called subroutine entry is already included by next_blocks_global. So, add return point here.
+                return_point_block = (
+                    [b.sub_return_point]
+                    if b.is_callsub_block and b.sub_return_point is not None
+                    else []
+                )
+
+                for bi in next_blocks_global(self._function, b) + return_point_block:
                     if bi not in worklist:
                         worklist.append(bi)
 
@@ -639,10 +698,14 @@ def _calculate_livein(
     ) -> Any:
         livein_information = self._null_set(key)
 
-        for next_b in block.next:
+        for next_b in next_blocks_global(self._function, block):
             livein_information = self._union(key, livein_information, liveout[next_b])
 
-        if block.sub_return_point is not None and len(block.sub_return_point.prev) != 0:
+        if (
+            block.is_callsub_block
+            and block.sub_return_point is not None
+            and len(block.called_subroutine.retsub_blocks) != 0
+        ):
             # this block is the `callsub block` and `block.sub_return_point` is the block that will be executed after subroutine.
             livein_information = self._intersection(
                 key, livein_information, liveout[block.sub_return_point]
@@ -655,7 +718,7 @@ def _merge_information_backward(
         block: "BasicBlock",
         global_liveout: Dict[str, Dict["BasicBlock", Any]],
     ) -> bool:
-        if len(block.next) == 0:  # leaf block
+        if leaf_block_global(block):  # leaf block
             return False
 
         updated = False
@@ -671,12 +734,17 @@ def _merge_information_backward(
         return updated
 
     def backward_analysis(self, analysis_keys: List[str], worklist: List["BasicBlock"]) -> None:
-        """Perform backward analysis for analysis_keys and update self._block_contexts"""
+        """Perform backward analysis for analysis_keys and update self._block_contexts
+
+        Args:
+            analysis_keys: List of keys.
+            worklist: List of basic blocks considered for the analysis. Initial worklist.
+        """
         global_liveout: Dict[str, Dict["BasicBlock", Any]] = {}
         for key in analysis_keys:
             global_liveout[key] = {}
-            for b in self._teal.bbs:
-                if len(b.next) == 0:  # leaf block
+            for b in self._function.blocks:
+                if leaf_block_global(b):  # leaf block
                     global_liveout[key][b] = self._block_contexts[key][b]
                 else:
                     global_liveout[key][b] = self._null_set(key)
@@ -687,8 +755,8 @@ def backward_analysis(self, analysis_keys: List[str], worklist: List["BasicBlock
             updated = self._merge_information_backward(analysis_keys, b, global_liveout)
 
             if updated:
-                callsub_block = [b.callsub_block] if b.callsub_block is not None else []
-                for bi in b.prev + callsub_block:
+                callsub_block = [b.callsub_block] if b.is_sub_return_point else []
+                for bi in prev_blocks_global(self._function, b) + callsub_block:
                     if bi not in worklist:
                         worklist.append(bi)
 
@@ -710,18 +778,25 @@ def dfs(block: "BasicBlock") -> None:
         dfs(entry)
         return order
 
-    def run_analysis(self) -> None:
+    def run_analysis(self) -> None:  # pylint: disable=too-many-branches
         """Run analysis"""
 
         gtx_keys = []
         for key in self.KEYS_WITH_GTXN:
             for ind in range(MAX_GROUP_SIZE):
-                gtx_keys.append(self.gtx_key(ind, key))
+                gtx_keys.append(get_gtxn_at_index_key(ind, key))
+                gtx_keys.append(get_absolute_index_key(ind, key))
+
+            for offset in range(-(MAX_GROUP_SIZE - 1), MAX_GROUP_SIZE):
+                if offset == 0:
+                    continue
+                gtx_keys.append(get_relative_index_key(offset, key))
 
         all_keys = self.BASE_KEYS + gtx_keys
 
         # step 1: initialise information
-        for block in self._teal.bbs:
+        # print(self._function.blocks)
+        for block in self._function.blocks:
             self._block_level_constraints(all_keys, block)  # initialise information for all keys
             self._path_level_constraints(all_keys, block)
             for debug_key in debug_keys:
@@ -731,44 +806,58 @@ def run_analysis(self) -> None:
                     logger_txn_ctx.debug(f"block_ctx: {self._block_contexts[debug_key][block]}")
                     logger_txn_ctx.debug("\n\n")
                     logger_txn_ctx.debug(f"path_context: {self._path_contexts[debug_key]}")
-
-        postorder = self._postorder(self._entry_block)
+        # print(self._block_contexts)
+        # The contract is represented using multiple CFG, one for each subroutine.
+        # Calculate postorder for each CFG and concatenate the lists for now.
+        postorder = [self._postorder(self._entry_block)]
+        for subroutine in self._function.subroutines.values():
+            postorder.append(self._postorder(subroutine.entry))
 
         # perform analysis of base keys first. Information of these base keys will be used for
         # gtxn keys. see `self._update_gtxn_constraints`
         analysis_keys = list(self.BASE_KEYS)
 
-        worklist = postorder[::-1]  # Reverse postorder
+        worklist = []
+        for l in postorder:
+            worklist += l[::-1]  # Reverse postorder
+
         self.forward_analyis(analysis_keys, worklist)
 
         logger_txn_ctx.debug("After Forward:")
         for debug_key in debug_keys:
-            for block in self._teal.bbs:
+            for block in self._function.blocks:
                 if debug_key in self.BASE_KEYS:
                     logger_txn_ctx.debug(f"block_id: B{block.idx}")
                     logger_txn_ctx.debug(f"block_ctx: {self._block_contexts[debug_key][block]}")
 
-        worklist = [b for b in postorder if len(b.next) != 0]  # postorder, exclude leaf blocks
+        worklist = []
+        for l in postorder:
+            worklist += [b for b in l if not leaf_block_global(b)]  # postorder, exclude leaf blocks
+
         self.backward_analysis(analysis_keys, worklist)
 
         logger_txn_ctx.debug("After Backward:")
         for debug_key in debug_keys:
-            for block in self._teal.bbs:
+            for block in self._function.blocks:
                 if debug_key in self.BASE_KEYS:
                     logger_txn_ctx.debug(f"block_id: B{block.idx}")
                     logger_txn_ctx.debug(f"block_ctx: {self._block_contexts[debug_key][block]}")
 
         # update gtxn constraints using possible group indices and txn constraints.
-        for block in self._teal.bbs:
+        for block in self._function.blocks:
             self._update_gtxn_constraints(self.KEYS_WITH_GTXN, block)
 
         # Now perform analysis for gtx_keys
         analysis_keys = gtx_keys
 
-        worklist = postorder[::-1]  # Reverse postorder
+        worklist = []
+        for l in postorder:
+            worklist += l[::-1]  # Reverse postorder
         self.forward_analyis(analysis_keys, worklist)
 
-        worklist = [b for b in postorder if len(b.next) != 0]  # postorder, exclude leaf blocks
+        worklist = []
+        for l in postorder:
+            worklist += [b for b in l if not leaf_block_global(b)]  # postorder, exclude leaf blocks
         self.backward_analysis(analysis_keys, worklist)
 
         self._store_results()
diff --git a/tealer/analyses/dataflow/int_fields.py b/tealer/analyses/dataflow/transaction_context/int_fields.py
similarity index 91%
rename from tealer/analyses/dataflow/int_fields.py
rename to tealer/analyses/dataflow/transaction_context/int_fields.py
index 5d7f05a..e5ec6d7 100644
--- a/tealer/analyses/dataflow/int_fields.py
+++ b/tealer/analyses/dataflow/transaction_context/int_fields.py
@@ -1,6 +1,6 @@
 from typing import TYPE_CHECKING, List, Set, Tuple, Dict
 
-from tealer.analyses.dataflow.generic import DataflowTransactionContext
+from tealer.analyses.dataflow.transaction_context.generic import DataflowTransactionContext
 from tealer.teal.instructions.instructions import (
     Global,
     Eq,
@@ -21,6 +21,7 @@
 
 if TYPE_CHECKING:
     from tealer.teal.instructions.instructions import Instruction
+    from tealer.teal.basic_blocks import BasicBlock
 
 # TODO: Change GroupSize, GroupIndex values representation to something similar of FeeValue.
 group_size_key = "GroupSize"
@@ -103,7 +104,7 @@ def _get_asserted_groupsizes(
         [ == | != | < | <= | > | >=]
 
         Args:
-            ins_stack: list of instructions that are executed up until the comparison instruction (including the comparison instruction).
+            ins_stack_value: The stack value. This value represents the result of the group-size check.
 
         Returns:
             set of groupsize values that will make the comparison true, set of groupsize values that will make the comparison false.
@@ -150,7 +151,7 @@ def _get_asserted_groupindices(
         [ == | != | < | <= | > | >=]
 
         Args:
-            ins_stack: list of instructions that are executed up until the comparison instruction (including the comparison instruction).
+            ins_stack_value: The stack value. This value represents the result of the group index check.
 
         Returns:
             List of groupindex values that will make the comparison true.
@@ -193,15 +194,19 @@ def _store_results(self) -> None:
         # use group_sizes to update group_indices
         group_sizes_context = self._block_contexts[self.GROUP_SIZE_KEY]
         group_indices_context = self._block_contexts[self.GROUP_INDEX_KEY]
-        for bi in self._teal.bbs:
+        for bi in self._function.blocks:
             group_indices_context[bi] = group_indices_context[bi] & set(
                 range(0, max(group_sizes_context[bi], default=0))
             )
 
         group_size_block_context = self._block_contexts[self.GROUP_SIZE_KEY]
-        for block in self._teal.bbs:
-            block.transaction_context.group_sizes = list(group_size_block_context[block])
+        for block in self._function.blocks:
+            self._function.transaction_context(block).group_sizes = list(
+                group_size_block_context[block]
+            )
 
         group_index_block_context = self._block_contexts[self.GROUP_INDEX_KEY]
-        for block in self._teal.bbs:
-            block.transaction_context.group_indices = list(group_index_block_context[block])
+        for block in self._function.blocks:
+            self._function.transaction_context(block).group_indices = list(
+                group_index_block_context[block]
+            )
diff --git a/tealer/analyses/dataflow/txn_types.py b/tealer/analyses/dataflow/transaction_context/txn_types.py
similarity index 64%
rename from tealer/analyses/dataflow/txn_types.py
rename to tealer/analyses/dataflow/transaction_context/txn_types.py
index 5862dea..2db5861 100644
--- a/tealer/analyses/dataflow/txn_types.py
+++ b/tealer/analyses/dataflow/transaction_context/txn_types.py
@@ -1,11 +1,16 @@
-from typing import TYPE_CHECKING, List, Set, Tuple, Dict, Type
-
-from tealer.analyses.dataflow.generic import DataflowTransactionContext
+from typing import TYPE_CHECKING, List, Set, Tuple, Dict
+
+from tealer.analyses.dataflow.transaction_context.generic import DataflowTransactionContext
+from tealer.analyses.dataflow.transaction_context.utils.key_helpers import (
+    get_gtxn_at_index_key,
+    get_ind_base_for_gtxn_type_keys,
+    is_value_matches_key,
+    get_absolute_index_key,
+    get_relative_index_key,
+)
 from tealer.teal.instructions.instructions import (
     Eq,
     Neq,
-    Txn,
-    Gtxn,
     Not,
 )
 from tealer.teal.instructions.transaction_field import (
@@ -22,11 +27,12 @@
 from tealer.utils.teal_enums import oncompletion_to_tealer_type, transaction_type_to_tealer_type
 from tealer.utils.analyses import is_int_push_ins
 from tealer.analyses.utils.stack_ast_builder import KnownStackValue, UnknownStackValue
+from tealer.utils.algorand_constants import MAX_GROUP_SIZE
 
 if TYPE_CHECKING:
     from tealer.teal.instructions.instructions import Instruction
     from tealer.teal.instructions.transaction_field import TransactionField
-
+    from tealer.teal.basic_blocks import BasicBlock
 
 # TODO: change the value representation for TxnTyp to something similar to FeeValue.
 # Same underlying type(TealerTransactionType) for three different things:
@@ -49,8 +55,8 @@ class TxnType(DataflowTransactionContext):  # pylint: disable=too-few-public-met
     UNIVERSAL_SETS: Dict[str, List] = universal_sets
 
     def _universal_set(self, key: str) -> Set:
-        if self.is_gtx_key(key):
-            _, base_key = self.get_gtx_ind_and_base_key(key)
+        if key not in self.BASE_KEYS:
+            _, base_key = get_ind_base_for_gtxn_type_keys(key)
             return set(self.UNIVERSAL_SETS[base_key])
         return set(self.UNIVERSAL_SETS[key])
 
@@ -63,16 +69,6 @@ def _union(self, key: str, a: Set, b: Set) -> Set:
     def _intersection(self, key: str, a: Set, b: Set) -> Set:
         return a & b
 
-    @classmethod
-    def _is_ins_tx_field(
-        cls, key: str, ins: "Instruction", field: Type["TransactionField"]
-    ) -> bool:
-        """return True if ins is "txn {field}" or ins is gtxn {idx} field"""
-        if cls.is_gtx_key(key):
-            idx, _ = cls.get_gtx_ind_and_base_key(key)
-            return isinstance(ins, Gtxn) and ins.idx == idx and isinstance(ins.field, field)
-        return isinstance(ins, Txn) and isinstance(ins.field, field)
-
     def _get_asserted_transaction_types(  # pylint: disable=too-many-branches, too-many-locals
         self, key: str, ins_stack_value: KnownStackValue
     ) -> Tuple[Set["TealerTransactionType"], Set["TealerTransactionType"]]:
@@ -100,11 +96,20 @@ def _get_asserted_transaction_types(  # pylint: disable=too-many-branches, too-m
         int [UpdateApplication | DeleteApplication | ...]
         ( == | != )
         bz/bnz
+
+        Args:
+            key: The analysis key.
+            ins_stack_value: A stack value. The stack value represents the result of ins_stack_value.instruction.
+                This function assumes that this value is being asserted/checked.
+
+        Returns:
+            Set of possible values for the :key: when the ins_stack_value is asserted to be True and when ins_stack_value is
+            asserted to be False.
         """
         U = set(self.UNIVERSAL_SETS[self.TRANSACTION_TYPE_KEY])
 
         ins1 = ins_stack_value.instruction
-        if self._is_ins_tx_field(key, ins1, ApplicationID):
+        if is_value_matches_key(key, ins_stack_value, ApplicationID):
             # txn ApplicationID pushes 0 if this Application creation transaction elses pushes nonzero
             return set(APPLICATION_TRANSACTION_TYPES) - set(
                 [TealerTransactionType.ApplCreation]
@@ -115,7 +120,7 @@ def _get_asserted_transaction_types(  # pylint: disable=too-many-branches, too-m
             if isinstance(not_arg, UnknownStackValue):
                 return set(U), set(U)
             ins2 = not_arg.instruction
-            if self._is_ins_tx_field(key, ins2, ApplicationID):
+            if is_value_matches_key(key, not_arg, ApplicationID):
                 # txn ApplicationID
                 # !
                 # return
@@ -141,34 +146,36 @@ def _get_asserted_transaction_types(  # pylint: disable=too-many-branches, too-m
                 return set(U), set(U)
 
             true_values, false_values = None, None
-            if self._is_ins_tx_field(key, ins2, ApplicationID):
+            if is_value_matches_key(key, arg1, ApplicationID) and value_3 is not None:
                 # TODO: ApplicationCreation transaction can be NoOp or OptIn
-                true_values, false_values = set([TealerTransactionType.ApplCreation]), set(
-                    APPLICATION_TRANSACTION_TYPES
-                ) - set([TealerTransactionType.ApplCreation])
-            elif self._is_ins_tx_field(key, ins3, ApplicationID):
+                if isinstance(value_3, int) and value_3 == 0:
+                    true_values, false_values = set([TealerTransactionType.ApplCreation]), set(
+                        APPLICATION_TRANSACTION_TYPES
+                    ) - set([TealerTransactionType.ApplCreation])
+            elif is_value_matches_key(key, arg2, ApplicationID) and value_2 is not None:
                 # TODO: ApplicationCreation transaction can be NoOp or OptIn
-                true_values, false_values = set([TealerTransactionType.ApplCreation]), set(
-                    APPLICATION_TRANSACTION_TYPES
-                ) - set([TealerTransactionType.ApplCreation])
+                if isinstance(value_2, int) and value_2 == 0:
+                    true_values, false_values = set([TealerTransactionType.ApplCreation]), set(
+                        APPLICATION_TRANSACTION_TYPES
+                    ) - set([TealerTransactionType.ApplCreation])
 
-            if self._is_ins_tx_field(key, ins2, TypeEnum) and value_3 is not None:
+            if is_value_matches_key(key, arg1, TypeEnum) and value_3 is not None:
                 compared_type = transaction_type_to_tealer_type(value_3)
                 true_values, false_values = set([compared_type]), set(
                     TYPEENUM_TRANSACTION_TYPES
                 ) - set([compared_type])
-            elif self._is_ins_tx_field(key, ins3, TypeEnum) and value_2 is not None:
+            elif is_value_matches_key(key, arg2, TypeEnum) and value_2 is not None:
                 compared_type = transaction_type_to_tealer_type(value_2)
                 true_values, false_values = set([compared_type]), set(
                     TYPEENUM_TRANSACTION_TYPES
                 ) - set([compared_type])
 
-            if self._is_ins_tx_field(key, ins2, OnCompletion) and value_3 is not None:
+            if is_value_matches_key(key, arg1, OnCompletion) and value_3 is not None:
                 compared_on_completion = oncompletion_to_tealer_type(value_3)
                 true_values, false_values = set([compared_on_completion]), set(
                     APPLICATION_TRANSACTION_TYPES
                 ) - set([compared_on_completion])
-            elif self._is_ins_tx_field(key, ins3, OnCompletion) and value_2 is not None:
+            elif is_value_matches_key(key, arg2, OnCompletion) and value_2 is not None:
                 compared_on_completion = oncompletion_to_tealer_type(value_2)
                 true_values, false_values = set([compared_on_completion]), set(
                     APPLICATION_TRANSACTION_TYPES
@@ -186,9 +193,33 @@ def _get_asserted_single(self, key: str, ins_stack_value: KnownStackValue) -> Tu
 
     def _store_results(self) -> None:
         transaction_type_context = self._block_contexts[self.TRANSACTION_TYPE_KEY]
-        for block in self._teal.bbs:
-            block.transaction_context.transaction_types = list(transaction_type_context[block])
 
-            for idx in range(16):
-                values = self._block_contexts[self.gtx_key(idx, self.TRANSACTION_TYPE_KEY)][block]
-                block.transaction_context.gtxn_context(idx).transaction_types = list(values)
+        for block in self._function.blocks:
+            self._function.transaction_context(block).transaction_types = list(
+                transaction_type_context[block]
+            )
+
+            for idx in range(MAX_GROUP_SIZE):
+                values = self._block_contexts[
+                    get_gtxn_at_index_key(idx, self.TRANSACTION_TYPE_KEY)
+                ][block]
+                self._function.transaction_context(block).gtxn_context(
+                    idx
+                ).transaction_types = list(values)
+
+                abs_values = self._block_contexts[
+                    get_absolute_index_key(idx, self.TRANSACTION_TYPE_KEY)
+                ][block]
+                self._function.transaction_context(block).absolute_context(
+                    idx
+                ).transaction_types = list(abs_values)
+
+            for offset in range(-(MAX_GROUP_SIZE - 1), MAX_GROUP_SIZE):
+                if offset == 0:
+                    continue
+                rel_values = self._block_contexts[
+                    get_relative_index_key(offset, self.TRANSACTION_TYPE_KEY)
+                ][block]
+                self._function.transaction_context(block).relative_context(
+                    offset
+                ).transaction_types = list(rel_values)
diff --git a/tealer/analyses/dataflow/transaction_context/utils/__init__.py b/tealer/analyses/dataflow/transaction_context/utils/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tealer/analyses/dataflow/transaction_context/utils/group_helpers.py b/tealer/analyses/dataflow/transaction_context/utils/group_helpers.py
new file mode 100644
index 0000000..5bf29e5
--- /dev/null
+++ b/tealer/analyses/dataflow/transaction_context/utils/group_helpers.py
@@ -0,0 +1,143 @@
+from typing import TYPE_CHECKING, Tuple, Optional
+from dataclasses import dataclass
+
+from tealer.teal.instructions.instructions import (
+    Txn,  # self, normal field
+    Gtxn,  # txn index is part of the instruction, normal field
+    # Txna,   # self, array field, array index in the instruction
+    # Gtxna,  # txn index is part of the instruction, array field, array index in the instruction
+    Gtxns,  # txn index is taken from stack, normal field
+    # Gtxnsa, # txn index is taken from stack, normal field, array index in the instruction
+    # Txnas,  # self, array field, array index from stack
+    # Gtxnas, # txn index is part of the instruction, array field, array index from stack
+    # Gtxnsas, # txn index from stack, array field, array index from stack.
+    Sub,
+    Add,
+)
+from tealer.utils.comparable_enum import ComparableEnum
+from tealer.analyses.utils.stack_ast_builder import KnownStackValue, UnknownStackValue
+from tealer.teal.instructions.transaction_field import GroupIndex
+from tealer.utils.analyses import is_int_push_ins
+
+if TYPE_CHECKING:
+    from tealer.teal.instructions.transaction_field import TransactionField
+
+
+class IndexType(ComparableEnum):
+    Self = 0
+    Absolute = 1
+    Relative = 2
+
+    Unknown = 777
+
+
+@dataclass
+class TransactionIndex:
+    index_type: IndexType
+    # value should be ignored for Self and Unknown index types.
+    value: int
+
+
+def get_index_and_field(
+    value: KnownStackValue,
+) -> Tuple[bool, Optional["TransactionIndex"], Optional["TransactionField"]]:
+    """Given a stack value, check if the value pushed is a transaction field. if so, return transaction index and the field.
+
+    Args:
+        value: The stack value.
+
+    Returns:
+        bool: True if the pushed value is a transaction field else False
+        TransactionIndex: Index of the transaction
+        TransactionField: The transaction field that will be pushed.
+    """
+    # TODO: probably could cache
+    # our analysis does not concern array fields, so ignoring those instructions for now.
+    if not isinstance(value.instruction, (Txn, Gtxn, Gtxns)):
+        return False, None, None
+
+    field = value.instruction.field
+    if isinstance(value.instruction, Txn):
+        return True, TransactionIndex(IndexType.Self, 0), field
+
+    if isinstance(value.instruction, Gtxn):
+        return True, TransactionIndex(IndexType.Absolute, value.instruction.idx), field
+
+    # Gtxns instruction
+    index_stack_value = value.args[0]
+    # happens when the Gtxns instruction is the first instruction of the block.
+    if isinstance(index_stack_value, UnknownStackValue):
+        return True, TransactionIndex(IndexType.Unknown, 0), field
+
+    index = _get_index(index_stack_value)
+    return True, index, field
+
+
+def _get_index(index_stack_value: KnownStackValue) -> "TransactionIndex":
+    # index patterns
+    # Txn.group_index() -> Self
+    # Int(n) -> Absolute index
+    # Txn.group_index() +/- Int(n) -> Relative / Unknown
+
+    # TODO: if there was some kind of analysis to calculate index implemented, call that function to fetch the index directly.
+    # There is no need for such analysis. below patterns cover most of the contracts.
+    # Txn.group_index() -> txn GroupIndex
+    if isinstance(index_stack_value.instruction, Txn) and isinstance(
+        index_stack_value.instruction.field, GroupIndex
+    ):
+        # Self
+        return TransactionIndex(IndexType.Self, 0)
+
+    pushes_int, int_value = is_int_push_ins(index_stack_value.instruction)
+    if pushes_int and isinstance(int_value, int):
+        # index_stack_value is an integer and the index is known to the tool
+        return TransactionIndex(IndexType.Absolute, int_value)
+
+    if pushes_int and not isinstance(int_value, int):
+        # pushes an integer but we don't its value. Unknown index.
+        return TransactionIndex(IndexType.Unknown, 0)
+
+    # does not push an literal int value.
+    # check for relative index patterns.
+    # Txn.group_index() - Int(n)
+    if isinstance(index_stack_value.instruction, Sub):
+        arg1, arg2 = index_stack_value.args[0], index_stack_value.args[1]
+        # index value is arg1 - arg2.
+        if isinstance(arg1, UnknownStackValue) or isinstance(arg2, UnknownStackValue):
+            return TransactionIndex(IndexType.Unknown, 0)
+        if not (
+            isinstance(arg1.instruction, Txn) and isinstance(arg1.instruction.field, GroupIndex)
+        ):
+            return TransactionIndex(IndexType.Unknown, 0)
+
+        # first argument is Txn.group_index()
+        is_int, int_value = is_int_push_ins(arg2.instruction)
+        if is_int and isinstance(int_value, int):
+            offset = -int_value  # pylint: disable=invalid-unary-operand-type
+            return TransactionIndex(IndexType.Relative, offset)
+        return TransactionIndex(IndexType.Unknown, 0)
+
+    # Txn.group_index() + Int(n)
+    if isinstance(index_stack_value.instruction, Add):
+        arg1, arg2 = index_stack_value.args[0], index_stack_value.args[1]
+        # index value is arg1 + arg2.
+        if isinstance(arg1, UnknownStackValue) or isinstance(arg2, UnknownStackValue):
+            return TransactionIndex(IndexType.Unknown, 0)
+        offset_arg = arg2
+        if not (
+            isinstance(arg1.instruction, Txn) and isinstance(arg1.instruction.field, GroupIndex)
+        ):
+            if not (
+                isinstance(arg2.instruction, Txn) and isinstance(arg2.instruction.field, GroupIndex)
+            ):
+                return TransactionIndex(IndexType.Unknown, 0)
+            # Int(n) + Txn.group_index()
+            offset_arg = arg1
+
+        is_int, int_value = is_int_push_ins(offset_arg.instruction)
+        if is_int and isinstance(int_value, int):
+            offset = int_value
+            return TransactionIndex(IndexType.Relative, offset)
+        return TransactionIndex(IndexType.Unknown, 0)
+
+    return TransactionIndex(IndexType.Unknown, 0)
diff --git a/tealer/analyses/dataflow/transaction_context/utils/key_helpers.py b/tealer/analyses/dataflow/transaction_context/utils/key_helpers.py
new file mode 100644
index 0000000..3627371
--- /dev/null
+++ b/tealer/analyses/dataflow/transaction_context/utils/key_helpers.py
@@ -0,0 +1,182 @@
+"""
+For a given field multiple types of possible values can be calculated.
+The possible values of the field of the transaction
+    1. executing the contract/function irrespective of the transaction's group index.
+    2. executing the contract when the transaction group index is a particular value.
+    3. at a given absolute index irrespective of which contract is executed.
+    4. at a given relative index irrespective of which contract is executed.
+
+see transaction_context/__init__.py for more information.
+"""
+
+from typing import TYPE_CHECKING, Tuple, Optional, Type
+
+from tealer.teal.instructions.parse_transaction_field import TX_FIELD_TXT_TO_OBJECT
+from tealer.analyses.dataflow.transaction_context.utils.group_helpers import (
+    IndexType,
+    get_index_and_field,
+)
+
+if TYPE_CHECKING:
+    from tealer.analyses.utils.stack_ast_builder import KnownStackValue
+    from tealer.teal.instructions.transaction_field import TransactionField
+
+
+def get_gtxn_at_index_key(idx: int, base_key: str) -> str:
+    """Analysis to represent type (2) information for base_key field
+
+    Args:
+        idx: index of the transaction in the group (0 to MAX_GROUP_SIZE - 1)
+        base_key: The key used to represent type (1) information of the field.
+
+    Returns:
+        Returns the key used to represent type (2) information in the analysis
+    """
+    return f"GTXN_AT_INDEX_{idx:02d}_{base_key}"
+
+
+def is_gtxn_at_index_key(analysis_key: str) -> bool:
+    """Return True if the analysis key represents type (2) information
+
+    Args:
+        analysis_key: The key.
+
+    Returns:
+        Returns True if analysis_key starts with "GTXN_AT_INDEX" or else False.
+    """
+    return analysis_key.startswith("GTXN_AT_INDEX")
+
+
+def get_ind_base_for_gtxn_type_keys(analysis_key: str) -> Tuple[int, str]:
+    """Calculate the index and base key for the txn_at_index, absolute, relative type keys.
+
+    Args:
+        analysis_key: A key
+
+    Returns:
+        Returns the transaction index and the base key for the analysis_key.
+    """
+    # will fail if the key is not a TXN_AT_INDEX key.
+    ind, base_key = analysis_key.split("_")[-2:]
+    return int(ind), base_key
+
+
+def get_absolute_index_key(idx: int, base_key: str) -> str:
+    """Analysis to represent type (3) information for base_key field
+
+    Args:
+        idx: index of the transaction in the group (0 to MAX_GROUP_SIZE - 1)
+        base_key: The key used to represent type (1) information of the field.
+
+    Returns:
+        Returns the key used to represent type (3) information in the analysis
+    """
+    return f"GTXN_ABS_{idx:02d}_{base_key}"
+
+
+def is_absolute_index_key(analysis_key: str) -> bool:
+    """Return True if the analysis key represents type (3) information
+
+    Args:
+        analysis_key: The key.
+
+    Returns:
+        Returns True if analysis_key is a absolute type of key.
+    """
+    return analysis_key.startswith("GTXN_ABS_")
+
+
+def get_relative_index_key(offset: int, base_key: str) -> str:
+    """Analysis to represent type (2) information for base_key field
+
+    Args:
+        offset: index of the transaction in the group  (-(MAX_GROUP_SIZE - 1) to (MAX_GROUP - 1))
+        base_key: The key used to represent type (1) information of the field.
+
+    Returns:
+        Returns the key used to represent type (4) information in the analysis
+    """
+    return f"GTXN_RELATIVE_{offset:02d}_{base_key}"
+
+
+def is_relative_index_key(analysis_key: str) -> bool:
+    """Return True if the analysis key represents type (4) information
+
+    Args:
+        analysis_key: The key.
+
+    Returns:
+        Returns True if analysis_key is a relative type of key.
+    """
+    return analysis_key.startswith("GTXN_RELATIVE_")
+
+
+# pylint: disable=too-many-branches
+def is_value_matches_key(
+    analysis_key: str,
+    stack_value: "KnownStackValue",
+    key_field: Optional[Type["TransactionField"]] = None,
+) -> bool:
+    """return True if the stack_value is value of the field tracked by the analysis_key else False.
+
+    Type of keys:
+        Self: The value of the current transaction irrespective of its index in the group
+        GTXN_AT_INDEX: The value of the transaction field executing this contract when this transaction is at the index.
+        Absolute: The field value of the transaction at an absolute index irrespective of the contract being executed at that index.
+        Relative: The field value of the transaction at an offset from the current transaction irrespective of the contract being executed at that offset.
+
+    Args:
+        analysis_key: The key tracking a field
+        stack_value: The value currently being checked
+        key_field: The transaction field if it cannot be calculated from the analysis_key
+
+    Returns:
+        Returns True if the stack_value is the value of the field tracked by the analysis_key else False.
+    """
+    value_is_field, value_index, value_field = get_index_and_field(stack_value)
+    if not value_is_field:
+        return False
+    assert value_index is not None and value_field is not None
+    if value_index.index_type == IndexType.Unknown:
+        return False
+
+    if key_field is None:
+        if (
+            is_gtxn_at_index_key(analysis_key)
+            or is_absolute_index_key(analysis_key)
+            or is_relative_index_key(analysis_key)
+        ):
+
+            _, field_str = get_ind_base_for_gtxn_type_keys(analysis_key)
+        else:
+            field_str = analysis_key
+        field = TX_FIELD_TXT_TO_OBJECT[field_str]
+    else:
+        field = key_field
+
+    if not isinstance(value_field, field):
+        return False
+
+    if is_gtxn_at_index_key(analysis_key) or is_absolute_index_key(analysis_key):
+        # difference between Gtxn_at_key and absolute_index comes from merging of Type(1) information before propagating.
+        # It is handled in DataflowTransactionContext._update_gtxn_constraints
+        if value_index.index_type != IndexType.Absolute:
+            return False
+
+        idx, _ = get_ind_base_for_gtxn_type_keys(analysis_key)
+        if value_index.value == idx:
+            return True
+        return False
+
+    if is_relative_index_key(analysis_key):
+        if value_index.index_type != IndexType.Relative:
+            return False
+        offset, _ = get_ind_base_for_gtxn_type_keys(analysis_key)
+        if value_index.value == offset:
+            return True
+        return False
+
+    # Txn/Self key
+    if value_index.index_type == IndexType.Self:
+        return True
+    return False
diff --git a/tealer/analyses/utils/stack_ast_builder.py b/tealer/analyses/utils/stack_ast_builder.py
index 439d12d..477d585 100644
--- a/tealer/analyses/utils/stack_ast_builder.py
+++ b/tealer/analyses/utils/stack_ast_builder.py
@@ -242,6 +242,9 @@ def args(self) -> List[Union["KnownStackValue", UnknownStackValue]]:
         self.args[-1] is the first popped value from the stack:
             self.args[-1] is on top of the stack right before the execution
             of self.instruction.
+
+        Returns:
+            Arguments of the current instruction that are taken from the stack.
         """
         return self._args
 
@@ -249,6 +252,13 @@ def args(self) -> List[Union["KnownStackValue", UnknownStackValue]]:
     def ins_out_values_index(self) -> int:
         """index into the list of out values pushed by self.instruction.
 
+        An instruction can push multiple stack values. In order to differentiate
+        between multiple values generated by the execution of the same instruction,
+        the ins_out_values_index is used.
+
+        The stack values pushed first by the instruction will have index 0. The stack value
+        pushed next will have index 1 and so on.
+
         stack:
                  ---- ----
                 |    |
@@ -276,6 +286,9 @@ def ins_out_values_index(self) -> int:
 
         X.instruction == Y.instruction == Mulw()
         X.args == Y.args = [Int(10), Int(11)]
+
+        Returns:
+            index of this value in the list of output values of self.instruction.
         """
         return self._ins_out_values_index
 
@@ -389,6 +402,14 @@ def construct_stack_ast(bb: BasicBlock) -> Dict[Instruction, KnownStackValue]:
                         ]),
                 ]),
         }
+
+    Args:
+        bb: A basic block of the CFG.
+
+    Returns:
+        A dict with each instruction of the bb as key. The values are the stack value representation
+        of that instruction. The value should be used to access the inputs of the instruction. These
+        inputs only includes stack arguments not the immediate arguments.
     """
     stack = Stack()
     ins_stack_value = {}
@@ -476,6 +497,14 @@ def _flatten_ast(root: StackValue, node_ins: Type[Instruction]) -> List[StackVal
         Or(Or(Or(<1>, <2>), Or(<3>, <4>)), Or(<5>, <6>))
         node_ins = Or
         => returns [<1>, <2>, <3>, <4>, <5>, <6>]
+
+    Args:
+        root: root stack value of the instruction tree
+        node_ins: node_ins is used to differentiate to identify leaf nodes.
+
+    Returns:
+        List of leaf nodes. Leaf nodes are stack values whose instruction is not :node_ins: and
+        All stack values in the path from the root to the leaf node have :node_ins: instruction.
     """
     if isinstance(root, UnknownStackValue):
         return [root]
@@ -492,6 +521,11 @@ def compute_equations(
 ) -> Tuple[List[KnownStackValue], bool]:
     """Compute equations from And or Or expression.
 
+    Args:
+        root: Root of the instruction AST
+        node_ins: The instruction whose result is being flattend. see documentation for _flatten_ast
+            function above.
+
     Returns:
         equations: List of values on which, the result of **root** depends.
 
@@ -514,3 +548,7 @@ def compute_equations(
         else:
             known_equations.append(eq)
     return known_equations, has_unkown_value
+
+
+def get_stack_value_for_ins(ins: "Instruction") -> KnownStackValue:
+    return construct_stack_ast(ins.bb)[ins]
diff --git a/tealer/detectors/abstract_detector.py b/tealer/detectors/abstract_detector.py
index 7fe01a5..e26e7ac 100644
--- a/tealer/detectors/abstract_detector.py
+++ b/tealer/detectors/abstract_detector.py
@@ -44,15 +44,13 @@
 """
 
 import abc
-from typing import List, TYPE_CHECKING
+from typing import TYPE_CHECKING
 
 from tealer.utils.comparable_enum import ComparableEnum
-from tealer.utils.output import ExecutionPaths
 
 if TYPE_CHECKING:
-    from tealer.teal.teal import Teal
-    from tealer.teal.basic_blocks import BasicBlock
-    from tealer.utils.output import SupportedOutput
+    from tealer.tealer import Tealer
+    from tealer.utils.output import ListOutput
 
 
 class IncorrectDetectorInitialization(Exception):
@@ -160,8 +158,8 @@ class AbstractDetector(metaclass=abc.ABCMeta):  # pylint: disable=too-few-public
     WIKI_EXPLOIT_SCENARIO = ""
     WIKI_RECOMMENDATION = ""
 
-    def __init__(self, teal: "Teal"):
-        self.teal = teal
+    def __init__(self, tealer: "Tealer"):
+        self.tealer = tealer
 
         if not self.NAME:
             raise IncorrectDetectorInitialization(
@@ -188,7 +186,10 @@ def __init__(self, teal: "Teal"):
                 f"WIKI_DESCRIPTION is not initialized {self.__class__.__name__}"
             )
 
-        if not self.WIKI_EXPLOIT_SCENARIO:
+        if not self.WIKI_EXPLOIT_SCENARIO and self.IMPACT not in [
+            DetectorClassification.INFORMATIONAL,
+            DetectorClassification.OPTIMIZATION,
+        ]:
             raise IncorrectDetectorInitialization(
                 f"WIKI_EXPLOIT_SCENARIO is not initialized {self.__class__.__name__}"
             )
@@ -223,41 +224,8 @@ def __init__(self, teal: "Teal"):
                 f"CONFIDENCE is not initialized {self.__class__.__name__}"
             )
 
-    def generate_result(
-        self, paths: List[List["BasicBlock"]], description: str, filename: str
-    ) -> ExecutionPaths:
-        """Helper method to construct ExecutionPaths result.
-
-        This function constructs and fills up the detector information of
-        ExecutionPaths object. ExecutionPaths is used to store output of
-        detector's that represent issues/vulnerabilities as execution paths.
-
-        Args:
-            paths: List of execution paths. Each execution path is represented
-                by a list of basic blocks.
-            description: Description of the issue/vulns.
-            filename: The execution paths are saved in dot files. :filename: is
-                used as the filename prefix for that dot files.
-
-        Returns:
-            ExecutionPaths object populated with the given args and detector
-            specific information.
-        """
-
-        output = ExecutionPaths(self.teal.bbs, description, filename)
-
-        for path in paths:
-            output.add_path(path)
-
-        output.check = self.NAME
-        output.impact = classification_txt[self.IMPACT]
-        output.confidence = classification_txt[self.CONFIDENCE]
-        output.help = self.WIKI_RECOMMENDATION.strip()
-
-        return output
-
     @abc.abstractmethod
-    def detect(self) -> "SupportedOutput":
+    def detect(self) -> "ListOutput":
         """Entry method of detector.
 
         All detectors must override this method with the functionality specific
@@ -265,10 +233,10 @@ def detect(self) -> "SupportedOutput":
         called to execute it.
 
         Returns:
-            detector results represented in one of the supported types. Currently,
+            Detector results represented in one of the supported types. Currently,
             all detectors work on Control Flow Graph(CFG) of the contract and represent
             the issues in the form of execution path in the CFG. ExecutionPaths class
-            is used to represent them. It is the only type supported currently and
-            AbstractDetector comes with a helper method to construct the result object
-            given the vulnerable execution paths, description and filenames.
+            is used to represent them.
+
+        # noqa: DAR202
         """
diff --git a/tealer/detectors/all_detectors.py b/tealer/detectors/all_detectors.py
index a81fe69..6d7d7b6 100644
--- a/tealer/detectors/all_detectors.py
+++ b/tealer/detectors/all_detectors.py
@@ -23,3 +23,6 @@
 from tealer.detectors.fee_check import MissingFeeCheck
 from tealer.detectors.anyone_can_update import AnyoneCanUpdate
 from tealer.detectors.anyone_can_delete import AnyoneCanDelete
+from tealer.detectors.optimizations.constant_gtxn import ConstantGtxn
+from tealer.detectors.optimizations.sender_access import SenderAccess
+from tealer.detectors.optimizations.self_access import SelfAccess
diff --git a/tealer/detectors/anyone_can_delete.py b/tealer/detectors/anyone_can_delete.py
index 63a528b..d356f6e 100644
--- a/tealer/detectors/anyone_can_delete.py
+++ b/tealer/detectors/anyone_can_delete.py
@@ -1,23 +1,25 @@
 """Detect paths missing validations on sender field AND allows to delete the application."""
 
-from typing import List, TYPE_CHECKING
+from typing import List, TYPE_CHECKING, Tuple
 
 from tealer.detectors.abstract_detector import (
     AbstractDetector,
     DetectorClassification,
     DetectorType,
 )
-from tealer.teal.basic_blocks import BasicBlock
 from tealer.detectors.utils import (
-    detect_missing_tx_field_validations,
-    detector_terminal_description,
+    detect_missing_tx_field_validations_group,
+    detect_missing_tx_field_validations_group_complete,
 )
 from tealer.utils.teal_enums import TealerTransactionType
+from tealer.utils.output import ExecutionPaths
 
 
 if TYPE_CHECKING:
-    from tealer.utils.output import SupportedOutput
+    from tealer.teal.basic_blocks import BasicBlock
+    from tealer.utils.output import ListOutput
     from tealer.teal.context.block_transaction_context import BlockTransactionContext
+    from tealer.teal.teal import Teal
 
 
 class AnyoneCanDelete(AbstractDetector):  # pylint: disable=too-few-public-methods
@@ -61,7 +63,7 @@ def delete_application() -> Expr:
 - Add access controls to the vulnerable method.
 """
 
-    def detect(self) -> "SupportedOutput":
+    def detect(self) -> "ListOutput":
         """Detect execution paths missing validations on sender field AND can delete the application .
 
         Returns:
@@ -78,11 +80,19 @@ def checks_field(block_ctx: "BlockTransactionContext") -> bool:
                 and block_ctx.sender.any_addr
             )
 
-        paths_without_check: List[List[BasicBlock]] = detect_missing_tx_field_validations(
-            self.teal.bbs[0], checks_field
-        )
+        # there should be a better to decide which function to call ??
+        if self.tealer.output_group:
+            # mypy complains if the value is returned directly. Uesd the second suggestion mentioned here:
+            # https://mypy.readthedocs.io/en/stable/common_issues.html#variance
+            return list(
+                detect_missing_tx_field_validations_group_complete(self.tealer, self, checks_field)
+            )
 
-        description = detector_terminal_description(self)
-        filename = "anyone_can_delete"
+        output: List[
+            Tuple["Teal", List[List["BasicBlock"]]]
+        ] = detect_missing_tx_field_validations_group(self.tealer, checks_field)
+        detector_output: "ListOutput" = []
+        for contract, vulnerable_paths in output:
+            detector_output.append(ExecutionPaths(contract, self, vulnerable_paths))
 
-        return self.generate_result(paths_without_check, description, filename)
+        return detector_output
diff --git a/tealer/detectors/anyone_can_update.py b/tealer/detectors/anyone_can_update.py
index 7c8c0ac..9fdeb14 100644
--- a/tealer/detectors/anyone_can_update.py
+++ b/tealer/detectors/anyone_can_update.py
@@ -1,22 +1,24 @@
 """Detect paths missing validations on sender field AND allows to update the application."""
 
-from typing import List, TYPE_CHECKING
+from typing import List, TYPE_CHECKING, Tuple
 
 from tealer.detectors.abstract_detector import (
     AbstractDetector,
     DetectorClassification,
     DetectorType,
 )
-from tealer.teal.basic_blocks import BasicBlock
 from tealer.detectors.utils import (
-    detect_missing_tx_field_validations,
-    detector_terminal_description,
+    detect_missing_tx_field_validations_group,
+    detect_missing_tx_field_validations_group_complete,
 )
 from tealer.utils.teal_enums import TealerTransactionType
+from tealer.utils.output import ExecutionPaths
 
 if TYPE_CHECKING:
-    from tealer.utils.output import SupportedOutput
+    from tealer.teal.basic_blocks import BasicBlock
+    from tealer.utils.output import ListOutput
     from tealer.teal.context.block_transaction_context import BlockTransactionContext
+    from tealer.teal.teal import Teal
 
 
 class AnyoneCanUpdate(AbstractDetector):  # pylint: disable=too-few-public-methods
@@ -60,7 +62,7 @@ def update_application() -> Expr:
 - Add access controls to the vulnerable method.
 """
 
-    def detect(self) -> "SupportedOutput":
+    def detect(self) -> "ListOutput":
         """Detect paths missing validations on sender field AND allows to update the application.
 
         Returns:
@@ -77,11 +79,19 @@ def checks_field(block_ctx: "BlockTransactionContext") -> bool:
                 and block_ctx.sender.any_addr
             )
 
-        paths_without_check: List[List[BasicBlock]] = detect_missing_tx_field_validations(
-            self.teal.bbs[0], checks_field
-        )
+        # there should be a better to decide which function to call ??
+        if self.tealer.output_group:
+            # mypy complains if the value is returned directly. Uesd the second suggestion mentioned here:
+            # https://mypy.readthedocs.io/en/stable/common_issues.html#variance
+            return list(
+                detect_missing_tx_field_validations_group_complete(self.tealer, self, checks_field)
+            )
 
-        description = detector_terminal_description(self)
+        output: List[
+            Tuple["Teal", List[List["BasicBlock"]]]
+        ] = detect_missing_tx_field_validations_group(self.tealer, checks_field)
+        detector_output: "ListOutput" = []
+        for contract, vulnerable_paths in output:
+            detector_output.append(ExecutionPaths(contract, self, vulnerable_paths))
 
-        filename = "anyone_can_update"
-        return self.generate_result(paths_without_check, description, filename)
+        return detector_output
diff --git a/tealer/detectors/can_close_account.py b/tealer/detectors/can_close_account.py
index b288ff0..34480d4 100644
--- a/tealer/detectors/can_close_account.py
+++ b/tealer/detectors/can_close_account.py
@@ -1,22 +1,25 @@
 """Detector for finding execution paths missing CloseRemainderTo check."""
 
-from typing import List, TYPE_CHECKING
+from typing import List, TYPE_CHECKING, Tuple
 
 from tealer.detectors.abstract_detector import (
     AbstractDetector,
     DetectorClassification,
     DetectorType,
 )
-from tealer.teal.basic_blocks import BasicBlock
 from tealer.detectors.utils import (
-    detect_missing_tx_field_validations,
-    detector_terminal_description,
+    detect_missing_tx_field_validations_group,
+    detect_missing_tx_field_validations_group_complete,
 )
 from tealer.utils.teal_enums import TealerTransactionType
+from tealer.utils.output import ExecutionPaths
+from tealer.utils.teal_enums import TransactionType
 
 if TYPE_CHECKING:
-    from tealer.utils.output import SupportedOutput
+    from tealer.teal.basic_blocks import BasicBlock
+    from tealer.utils.output import ListOutput
     from tealer.teal.context.block_transaction_context import BlockTransactionContext
+    from tealer.teal.teal import Teal
 
 
 class CanCloseAccount(AbstractDetector):  # pylint: disable=too-few-public-methods
@@ -76,7 +79,7 @@ def withdraw(...) -> Expr:
 Validate `CloseRemainderTo` field in the LogicSig.
 """
 
-    def detect(self) -> "SupportedOutput":
+    def detect(self) -> "ListOutput":
         """Detect execution paths with missing CloseRemainderTo check.
 
         Returns:
@@ -94,12 +97,24 @@ def checks_field(block_ctx: "BlockTransactionContext") -> bool:
                 and TealerTransactionType.Pay in block_ctx.transaction_types
             )
 
-        paths_without_check: List[List[BasicBlock]] = detect_missing_tx_field_validations(
-            self.teal.bbs[0], checks_field
-        )
-
-        description = detector_terminal_description(self)
+        # there should be a better to decide which function to call ??
+        if self.tealer.output_group:
+            # mypy complains if the value is returned directly. Uesd the second suggestion mentioned here:
+            # https://mypy.readthedocs.io/en/stable/common_issues.html#variance
+            return list(
+                detect_missing_tx_field_validations_group_complete(
+                    self.tealer,
+                    self,
+                    checks_field,
+                    [TransactionType.Any, TransactionType.Unknown, TransactionType.Pay],
+                )
+            )
 
-        filename = "can_close_account"
+        output: List[
+            Tuple["Teal", List[List["BasicBlock"]]]
+        ] = detect_missing_tx_field_validations_group(self.tealer, checks_field)
+        detector_output: "ListOutput" = []
+        for contract, vulnerable_paths in output:
+            detector_output.append(ExecutionPaths(contract, self, vulnerable_paths))
 
-        return self.generate_result(paths_without_check, description, filename)
+        return detector_output
diff --git a/tealer/detectors/can_close_asset.py b/tealer/detectors/can_close_asset.py
index 23a2340..26494c0 100644
--- a/tealer/detectors/can_close_asset.py
+++ b/tealer/detectors/can_close_asset.py
@@ -1,22 +1,25 @@
 """Detector for finding execution paths missing AssetCloseTo check."""
 
-from typing import List, TYPE_CHECKING
+from typing import List, TYPE_CHECKING, Tuple
 
 from tealer.detectors.abstract_detector import (
     AbstractDetector,
     DetectorClassification,
     DetectorType,
 )
-from tealer.teal.basic_blocks import BasicBlock
 from tealer.detectors.utils import (
-    detect_missing_tx_field_validations,
-    detector_terminal_description,
+    detect_missing_tx_field_validations_group,
+    detect_missing_tx_field_validations_group_complete,
 )
 from tealer.utils.teal_enums import TealerTransactionType
+from tealer.utils.output import ExecutionPaths
+from tealer.utils.teal_enums import TransactionType
 
 if TYPE_CHECKING:
-    from tealer.utils.output import SupportedOutput
+    from tealer.teal.basic_blocks import BasicBlock
+    from tealer.utils.output import ListOutput
     from tealer.teal.context.block_transaction_context import BlockTransactionContext
+    from tealer.teal.teal import Teal
 
 
 class CanCloseAsset(AbstractDetector):  # pylint: disable=too-few-public-methods
@@ -76,7 +79,7 @@ def withdraw(...) -> Expr:
 Validate `AssetCloseTo` field in the LogicSig.
 """
 
-    def detect(self) -> "SupportedOutput":
+    def detect(self) -> "ListOutput":
         """Detect execution paths with missing AssetCloseTo check.
 
         Returns:
@@ -94,12 +97,24 @@ def checks_field(block_ctx: "BlockTransactionContext") -> bool:
                 and TealerTransactionType.Axfer in block_ctx.transaction_types
             )
 
-        paths_without_check: List[List[BasicBlock]] = detect_missing_tx_field_validations(
-            self.teal.bbs[0], checks_field
-        )
-
-        description = detector_terminal_description(self)
+        # there should be a better to decide which function to call ??
+        if self.tealer.output_group:
+            # mypy complains if the value is returned directly. Uesd the second suggestion mentioned here:
+            # https://mypy.readthedocs.io/en/stable/common_issues.html#variance
+            return list(
+                detect_missing_tx_field_validations_group_complete(
+                    self.tealer,
+                    self,
+                    checks_field,
+                    [TransactionType.Any, TransactionType.Unknown, TransactionType.Axfer],
+                )
+            )
 
-        filename = "can_close_asset"
+        output: List[
+            Tuple["Teal", List[List["BasicBlock"]]]
+        ] = detect_missing_tx_field_validations_group(self.tealer, checks_field)
+        detector_output: "ListOutput" = []
+        for contract, vulnerable_paths in output:
+            detector_output.append(ExecutionPaths(contract, self, vulnerable_paths))
 
-        return self.generate_result(paths_without_check, description, filename)
+        return detector_output
diff --git a/tealer/detectors/fee_check.py b/tealer/detectors/fee_check.py
index 4aa5c5c..4b41982 100644
--- a/tealer/detectors/fee_check.py
+++ b/tealer/detectors/fee_check.py
@@ -1,47 +1,24 @@
 """Detector for finding execution paths missing Fee check."""
 
-from typing import List, TYPE_CHECKING
+from typing import List, TYPE_CHECKING, Tuple
 
 from tealer.detectors.abstract_detector import (
     AbstractDetector,
     DetectorClassification,
     DetectorType,
 )
-from tealer.teal.basic_blocks import BasicBlock
-from tealer.teal.instructions.instructions import (
-    Instruction,
-    Int,
-    Txn,
-)
-from tealer.teal.instructions.transaction_field import Fee
 from tealer.detectors.utils import (
-    detect_missing_tx_field_validations,
-    detector_terminal_description,
+    detect_missing_tx_field_validations_group,
+    detect_missing_tx_field_validations_group_complete,
 )
 from tealer.utils.algorand_constants import MAX_TRANSACTION_COST
+from tealer.utils.output import ExecutionPaths
 
 if TYPE_CHECKING:
-    from tealer.utils.output import SupportedOutput
+    from tealer.utils.output import ListOutput
+    from tealer.teal.basic_blocks import BasicBlock
     from tealer.teal.context.block_transaction_context import BlockTransactionContext
-
-
-def _is_fee_check(ins1: Instruction, ins2: Instruction) -> bool:
-    """Util function to check if given instructions form Fee check.
-
-    Args:
-        ins1: First instruction of the execution sequence that is supposed
-            to form a comparison check for Fee transaction field.
-        ins2: Second instruction in the execution sequence, will be executed
-            right after :ins1:.
-
-    Returns:
-        True if the given instructions :ins1:, :ins2: form a Fee check
-        i.e True if :ins1: is txn Fee and :ins2: is int .. .
-    """
-
-    if isinstance(ins1, Txn) and isinstance(ins1.field, Fee):
-        return isinstance(ins2, Int)
-    return False
+    from tealer.teal.teal import Teal
 
 
 class MissingFeeCheck(AbstractDetector):  # pylint: disable=too-few-public-methods
@@ -102,7 +79,7 @@ def withdraw(...) -> Expr:
 Validate `Fee` field in the LogicSig.
 """
 
-    def detect(self) -> "SupportedOutput":
+    def detect(self) -> "ListOutput":
         """Detect execution paths with missing Fee check.
 
         Returns:
@@ -116,12 +93,19 @@ def checks_field(block_ctx: "BlockTransactionContext") -> bool:
             # or is bounded by some known value less than maximum transaction cost.
             return block_ctx.max_fee_unknown or block_ctx.max_fee <= MAX_TRANSACTION_COST
 
-        paths_without_check: List[List[BasicBlock]] = detect_missing_tx_field_validations(
-            self.teal.bbs[0], checks_field
-        )
-
-        description = detector_terminal_description(self)
-
-        filename = "missing_fee_check"
-
-        return self.generate_result(paths_without_check, description, filename)
+        # there should be a better to decide which function to call ??
+        if self.tealer.output_group:
+            # mypy complains if the value is returned directly. Uesd the second suggestion mentioned here:
+            # https://mypy.readthedocs.io/en/stable/common_issues.html#variance
+            return list(
+                detect_missing_tx_field_validations_group_complete(self.tealer, self, checks_field)
+            )
+
+        output: List[
+            Tuple["Teal", List[List["BasicBlock"]]]
+        ] = detect_missing_tx_field_validations_group(self.tealer, checks_field)
+        detector_output: "ListOutput" = []
+        for contract, vulnerable_paths in output:
+            detector_output.append(ExecutionPaths(contract, self, vulnerable_paths))
+
+        return detector_output
diff --git a/tealer/detectors/groupsize.py b/tealer/detectors/groupsize.py
index 16a684d..e82bcab 100644
--- a/tealer/detectors/groupsize.py
+++ b/tealer/detectors/groupsize.py
@@ -1,6 +1,6 @@
 """Detector for finding execution paths missing GroupSize check."""
 
-from typing import List, TYPE_CHECKING
+from typing import List, TYPE_CHECKING, Tuple
 
 from tealer.detectors.abstract_detector import (
     AbstractDetector,
@@ -16,16 +16,19 @@
     Gtxnsa,
     Gtxnsas,
 )
-from tealer.teal.teal import Teal
 
 from tealer.utils.algorand_constants import MAX_GROUP_SIZE
 from tealer.utils.analyses import is_int_push_ins
 from tealer.analyses.utils.stack_ast_builder import construct_stack_ast, UnknownStackValue
-from tealer.detectors.utils import detector_terminal_description
+from tealer.detectors.utils import detect_missing_tx_field_validations_group
+from tealer.utils.output import ExecutionPaths
+
 
 if TYPE_CHECKING:
-    from tealer.utils.output import SupportedOutput
+    from tealer.teal.teal import Teal
+    from tealer.utils.output import ListOutput
     from tealer.teal.instructions.instructions import Instruction
+    from tealer.teal.context.block_transaction_context import BlockTransactionContext
 
 
 class MissingGroupSize(AbstractDetector):  # pylint: disable=too-few-public-methods
@@ -79,10 +82,6 @@ def mint_wrapped_algo() -> Expr:
 - Favor using ARC-4 ABI and relative indexes for group transactions.
 """
 
-    def __init__(self, teal: Teal):
-        super().__init__(teal)
-        self.results_number = 0
-
     @staticmethod
     def _accessed_using_absolute_index(bb: BasicBlock) -> bool:
         """Return True if a instruction in bb access a field using absolute index
@@ -97,6 +96,13 @@ def _accessed_using_absolute_index(bb: BasicBlock) -> bool:
         `gtxns f` and `gtxnsa f i` take only one argument and it is the transaction index.
         `gtxnsas f` takes two arguments and transaction index is the first argument.
         Return True if the transaction index is pushed by an int instruction.
+
+        Args:
+            bb: A basic block of the CFG
+
+        Returns:
+            Returns True if a instruction in bb access a field of group transaction using
+            an absolute index. returns False otherwise.
         """
         stack_gtxns_ins: List["Instruction"] = []
         for ins in bb.instructions:
@@ -116,54 +122,7 @@ def _accessed_using_absolute_index(bb: BasicBlock) -> bool:
                 return True
         return False
 
-    def _check_groupsize(
-        self,
-        bb: BasicBlock,
-        current_path: List[BasicBlock],
-        paths_without_check: List[List[BasicBlock]],
-        used_abs_index: bool,
-    ) -> None:
-        """Find execution paths with missing GroupSize check.
-
-        This function recursively explores the Control Flow Graph(CFG) of the
-        contract and reports execution paths with missing GroupSize
-        check.
-
-        This function is "in place", modifies arguments with the data it is
-        supposed to return.
-
-        Args:
-            bb: Current basic block being checked(whose execution is simulated.)
-            current_path: Current execution path being explored.
-            paths_without_check:
-                Execution paths with missing GroupSize check. This is a
-                "in place" argument. Vulnerable paths found by this function are
-                appended to this list.
-            used_abs_index: Should be True if absolute index in `current_path`.
-        """
-
-        # check for loops
-        if bb in current_path:
-            return
-
-        if MAX_GROUP_SIZE not in bb.transaction_context.group_sizes:
-            # GroupSize is checked
-            return
-
-        if not used_abs_index:
-            used_abs_index = self._accessed_using_absolute_index(bb)
-
-        current_path = current_path + [bb]
-        if not bb.next:
-            # leaf block
-            if used_abs_index:
-                # accessed a field using absolute index in this path.
-                paths_without_check.append(current_path)
-            return
-        for next_bb in bb.next:
-            self._check_groupsize(next_bb, current_path, paths_without_check, used_abs_index)
-
-    def detect(self) -> "SupportedOutput":
+    def detect(self) -> "ListOutput":
         """Detect execution paths with missing GroupSize check.
 
         Returns:
@@ -172,12 +131,30 @@ def detect(self) -> "SupportedOutput":
             information.
         """
 
-        paths_without_check: List[List[BasicBlock]] = []
-        self._check_groupsize(self.teal.bbs[0], [], paths_without_check, False)
+        def checks_group_size(block_ctx: "BlockTransactionContext") -> bool:
+            # return True if group-size is checked in the path
+            # otherwise return False
+            if block_ctx.is_gtxn_context:
+                # gtxn_contexts have group-sizes, group-indices set to 0.
+                return False
+            return MAX_GROUP_SIZE not in block_ctx.group_sizes
 
-        description = detector_terminal_description(self)
-        filename = "missing_group_size"
+        def satisfies_report_condition(path: List["BasicBlock"]) -> bool:
 
-        results = self.generate_result(paths_without_check, description, filename)
+            for block in path:
+                if self._accessed_using_absolute_index(block):
+                    return True
+            return False
+
+        output: List[
+            Tuple["Teal", List[List["BasicBlock"]]]
+        ] = detect_missing_tx_field_validations_group(
+            self.tealer, checks_group_size, satisfies_report_condition
+        )
         construct_stack_ast.cache_clear()
-        return results
+
+        detector_output: "ListOutput" = []
+        for contract, vulnerable_paths in output:
+            detector_output.append(ExecutionPaths(contract, self, vulnerable_paths))
+
+        return detector_output
diff --git a/tealer/detectors/is_deletable.py b/tealer/detectors/is_deletable.py
index f92cb0d..1057395 100644
--- a/tealer/detectors/is_deletable.py
+++ b/tealer/detectors/is_deletable.py
@@ -1,22 +1,25 @@
 """Detector for finding execution paths missing DeleteApplication check."""
 
-from typing import List, TYPE_CHECKING
+from typing import List, TYPE_CHECKING, Tuple
 
 from tealer.detectors.abstract_detector import (
     AbstractDetector,
     DetectorClassification,
     DetectorType,
 )
-from tealer.teal.basic_blocks import BasicBlock
+
 from tealer.detectors.utils import (
-    detect_missing_tx_field_validations,
-    detector_terminal_description,
+    detect_missing_tx_field_validations_group,
+    detect_missing_tx_field_validations_group_complete,
 )
 from tealer.utils.teal_enums import TealerTransactionType
+from tealer.utils.output import ExecutionPaths
 
 if TYPE_CHECKING:
-    from tealer.utils.output import SupportedOutput
+    from tealer.utils.output import ListOutput
+    from tealer.teal.basic_blocks import BasicBlock
     from tealer.teal.context.block_transaction_context import BlockTransactionContext
+    from tealer.teal.teal import Teal
 
 
 class IsDeletable(AbstractDetector):  # pylint: disable=too-few-public-methods
@@ -59,7 +62,7 @@ def delete_application() -> Expr:
 Do not approve `DeleteApplication` type application calls.
 """
 
-    def detect(self) -> "SupportedOutput":
+    def detect(self) -> "ListOutput":
         """Detect execution paths with missing DeleteApplication check.
 
         Returns:
@@ -73,12 +76,19 @@ def checks_field(block_ctx: "BlockTransactionContext") -> bool:
             # return True if Txn Type cannot be DeleteApplication.
             return not TealerTransactionType.ApplDeleteApplication in block_ctx.transaction_types
 
-        paths_without_check: List[List[BasicBlock]] = detect_missing_tx_field_validations(
-            self.teal.bbs[0], checks_field
-        )
-
-        description = detector_terminal_description(self)
-
-        filename = "is_deletable"
-
-        return self.generate_result(paths_without_check, description, filename)
+        # there should be a better to decide which function to call ??
+        if self.tealer.output_group:
+            # mypy complains if the value is returned directly. Uesd the second suggestion mentioned here:
+            # https://mypy.readthedocs.io/en/stable/common_issues.html#variance
+            return list(
+                detect_missing_tx_field_validations_group_complete(self.tealer, self, checks_field)
+            )
+
+        output: List[
+            Tuple["Teal", List[List["BasicBlock"]]]
+        ] = detect_missing_tx_field_validations_group(self.tealer, checks_field)
+        detector_output: "ListOutput" = []
+        for contract, vulnerable_paths in output:
+            detector_output.append(ExecutionPaths(contract, self, vulnerable_paths))
+
+        return detector_output
diff --git a/tealer/detectors/is_updatable.py b/tealer/detectors/is_updatable.py
index 476886e..8e8b57a 100644
--- a/tealer/detectors/is_updatable.py
+++ b/tealer/detectors/is_updatable.py
@@ -1,22 +1,24 @@
 """Detector for finding execution paths missing UpdateApplication."""
 
-from typing import List, TYPE_CHECKING
+from typing import List, TYPE_CHECKING, Tuple
 
 from tealer.detectors.abstract_detector import (
     AbstractDetector,
     DetectorClassification,
     DetectorType,
 )
-from tealer.teal.basic_blocks import BasicBlock
 from tealer.detectors.utils import (
-    detect_missing_tx_field_validations,
-    detector_terminal_description,
+    detect_missing_tx_field_validations_group,
+    detect_missing_tx_field_validations_group_complete,
 )
 from tealer.utils.teal_enums import TealerTransactionType
+from tealer.utils.output import ExecutionPaths
 
 if TYPE_CHECKING:
-    from tealer.utils.output import SupportedOutput
+    from tealer.teal.basic_blocks import BasicBlock
+    from tealer.utils.output import ListOutput
     from tealer.teal.context.block_transaction_context import BlockTransactionContext
+    from tealer.teal.teal import Teal
 
 
 class IsUpdatable(AbstractDetector):  # pylint: disable=too-few-public-methods
@@ -60,7 +62,7 @@ def update_application() -> Expr:
 Do not approve `UpdateApplication` type application calls.
 """
 
-    def detect(self) -> "SupportedOutput":
+    def detect(self) -> "ListOutput":
         """Detect execution paths with missing UpdateApplication check.
 
         Returns:
@@ -74,11 +76,24 @@ def checks_field(block_ctx: "BlockTransactionContext") -> bool:
             # return True if Txn Type cannot be UpdateApplication.
             return not TealerTransactionType.ApplUpdateApplication in block_ctx.transaction_types
 
-        paths_without_check: List[List[BasicBlock]] = detect_missing_tx_field_validations(
-            self.teal.bbs[0], checks_field
-        )
-
-        description = detector_terminal_description(self)
-
-        filename = "is_updatable"
-        return self.generate_result(paths_without_check, description, filename)
+        # there should be a better to decide which function to call ??
+        if self.tealer.output_group:
+            # mypy complains if the value is returned directly. Uesd the second suggestion mentioned here:
+            # https://mypy.readthedocs.io/en/stable/common_issues.html#variance
+            return list(
+                detect_missing_tx_field_validations_group_complete(self.tealer, self, checks_field)
+            )
+
+        # paths_without_check: List[List[BasicBlock]] = detect_missing_tx_field_validations(
+        #     self.teal, checks_field
+        # )
+
+        # return ExecutionPaths(self.teal, self, paths_without_check)
+        output: List[
+            Tuple["Teal", List[List["BasicBlock"]]]
+        ] = detect_missing_tx_field_validations_group(self.tealer, checks_field)
+        detector_output: "ListOutput" = []
+        for contract, vulnerable_paths in output:
+            detector_output.append(ExecutionPaths(contract, self, vulnerable_paths))
+
+        return detector_output
diff --git a/tealer/detectors/optimizations/__init__.py b/tealer/detectors/optimizations/__init__.py
new file mode 100644
index 0000000..d2d488a
--- /dev/null
+++ b/tealer/detectors/optimizations/__init__.py
@@ -0,0 +1,14 @@
+"""Module for tealer detectors.
+
+Detectors are analyzers which go through the parsed code and look for
+certain patterns in the program which are of interest to anyone invested
+in the security of the contract.
+
+This module contains Abstract base class for printers and implementation of
+few detectors that detect common issues in algorand smart contracts.
+
+Modules:
+    abstract_detector: Defines abstract base class for detectors.
+
+    all_detectors: Collects and exports detectors defined in tealer.
+"""
diff --git a/tealer/detectors/optimizations/constant_gtxn.py b/tealer/detectors/optimizations/constant_gtxn.py
new file mode 100644
index 0000000..10d2b8e
--- /dev/null
+++ b/tealer/detectors/optimizations/constant_gtxn.py
@@ -0,0 +1,72 @@
+"""Detector for finding unoptimized usage of constant GTXN."""
+
+from typing import TYPE_CHECKING, List
+
+from tealer.detectors.abstract_detector import (
+    AbstractDetector,
+    DetectorClassification,
+    DetectorType,
+)
+from tealer.teal.instructions.instructions import Gtxns, Instruction
+from tealer.utils.analyses import is_int_push_ins
+from tealer.utils.output import InstructionsOutput
+
+if TYPE_CHECKING:
+    from tealer.utils.output import ListOutput
+
+
+class ConstantGtxn(AbstractDetector):  # pylint: disable=too-few-public-methods
+    """Detector to find unoptimized usage of constant GTXN"""
+
+    NAME = "constant-gtxn"
+    DESCRIPTION = "Unoptimized Gtxn"
+    TYPE = DetectorType.STATELESS
+
+    IMPACT = DetectorClassification.OPTIMIZATION
+    CONFIDENCE = DetectorClassification.HIGH
+
+    WIKI_URL = "https://github.com/crytic/tealer/wiki/Detector-Documentation#Unoptimized-Gtxn"
+    WIKI_TITLE = "Unoptimized Gtxn"
+    WIKI_DESCRIPTION = (
+        "- Gtxn[Int(1)].field() produces instructions int 1; gtxns field"
+        "- Gtxn[1].field() produces only one instruction: gtxn 1 field"
+    )
+    WIKI_EXPLOIT_SCENARIO = """"""
+
+    WIKI_RECOMMENDATION = """Use Gtxn[idx].field()."""
+
+    def detect(self) -> "ListOutput":
+        """Detector to find unoptimized usage of constant GTXN
+
+        Returns:
+              The two instructions to optimized
+        """
+
+        detector_output: "ListOutput" = []
+
+        for contract in self.tealer.contracts.values():
+
+            all_findings: List[List[Instruction]] = []
+
+            for function in contract.functions.values():
+                for block in function.blocks:
+                    if len(block.instructions) < 2:
+                        continue
+
+                    # Sliding windows
+                    for i in range(0, len(block.instructions) - 1):
+                        first = block.instructions[i]
+                        second = block.instructions[i + 1]
+
+                        # Note: first / second are always assigned, as we check for len(block.instructions) first
+                        # pylint: disable=undefined-loop-variable
+                        first_ins_is_int, _ = is_int_push_ins(first)
+                        # pylint: disable=undefined-loop-variable
+                        if first_ins_is_int and isinstance(second, Gtxns):
+                            # pylint: disable=undefined-loop-variable
+                            all_findings.append([first, second])
+
+            if all_findings:
+                detector_output.append(InstructionsOutput(contract, self, all_findings))
+
+        return detector_output
diff --git a/tealer/detectors/optimizations/self_access.py b/tealer/detectors/optimizations/self_access.py
new file mode 100644
index 0000000..746f013
--- /dev/null
+++ b/tealer/detectors/optimizations/self_access.py
@@ -0,0 +1,73 @@
+"""Detector for finding unoptimized self access"""
+
+from typing import TYPE_CHECKING, List
+
+from tealer.detectors.abstract_detector import (
+    AbstractDetector,
+    DetectorClassification,
+    DetectorType,
+)
+from tealer.teal.instructions.instructions import Gtxns, Instruction, Txn, Gtxnsa, Gtxnsas
+from tealer.teal.instructions.transaction_field import GroupIndex
+from tealer.utils.output import InstructionsOutput
+
+if TYPE_CHECKING:
+    from tealer.utils.output import ListOutput
+
+
+class SelfAccess(AbstractDetector):  # pylint: disable=too-few-public-methods
+    """Detector to find unoptimized self access"""
+
+    NAME = "self-access"
+    DESCRIPTION = "Unoptimized self access"
+    TYPE = DetectorType.STATELESS
+
+    IMPACT = DetectorClassification.OPTIMIZATION
+    CONFIDENCE = DetectorClassification.HIGH
+
+    WIKI_URL = (
+        "https://github.com/crytic/tealer/wiki/Detector-Documentation#Unoptimized-self-access"
+    )
+    WIKI_TITLE = "Unoptimized self access"
+    WIKI_DESCRIPTION = "Gtxn[Txn.group_index()].field can be replaced by Txn.field"
+    WIKI_EXPLOIT_SCENARIO = """"""
+
+    WIKI_RECOMMENDATION = """Use Txn.field."""
+
+    def detect(self) -> "ListOutput":
+        """Detector to find unoptimized usage of self access
+
+        Returns:
+              The two instructions to optimized
+        """
+
+        detector_output: "ListOutput" = []
+
+        for contract in self.tealer.contracts.values():
+
+            all_findings: List[List[Instruction]] = []
+
+            for function in contract.functions.values():
+                for block in function.blocks:
+                    if len(block.instructions) < 2:
+                        continue
+
+                    # Sliding windows
+                    for i in range(0, len(block.instructions) - 1):
+                        first = block.instructions[i]
+                        second = block.instructions[i + 1]
+
+                        # Note: first / second are always assigned, as we check for len(block.instructions) first
+                        # pylint: disable=undefined-loop-variable
+                        if (
+                            isinstance(first, Txn)
+                            and isinstance(first.field, GroupIndex)
+                            and isinstance(second, (Gtxns, Gtxnsa, Gtxnsas))
+                        ):
+                            # pylint: disable=undefined-loop-variable
+                            all_findings.append([first, second])
+
+            if all_findings:
+                detector_output.append(InstructionsOutput(contract, self, all_findings))
+
+        return detector_output
diff --git a/tealer/detectors/optimizations/sender_access.py b/tealer/detectors/optimizations/sender_access.py
new file mode 100644
index 0000000..436d2dd
--- /dev/null
+++ b/tealer/detectors/optimizations/sender_access.py
@@ -0,0 +1,59 @@
+"""Detector for finding unoptimized access to the sender."""
+
+from typing import TYPE_CHECKING, List
+
+from tealer.detectors.abstract_detector import (
+    AbstractDetector,
+    DetectorClassification,
+    DetectorType,
+)
+from tealer.teal.instructions.instructions import Instruction, Txna
+from tealer.teal.instructions.transaction_field import Accounts
+from tealer.utils.output import InstructionsOutput
+
+if TYPE_CHECKING:
+    from tealer.utils.output import ListOutput
+
+
+class SenderAccess(AbstractDetector):  # pylint: disable=too-few-public-methods
+    """Detector to find unoptimized access to the sender"""
+
+    NAME = "sender-access"
+    DESCRIPTION = "Unoptimized Gtxn"
+    TYPE = DetectorType.STATELESS
+
+    IMPACT = DetectorClassification.OPTIMIZATION
+    CONFIDENCE = DetectorClassification.HIGH
+
+    WIKI_URL = (
+        "https://github.com/crytic/tealer/wiki/Detector-Documentation#Unoptimized-sender-access"
+    )
+    WIKI_TITLE = "Unoptimized sender access"
+    WIKI_DESCRIPTION = "Txn.accounts[0] can be optimized to be Txn.sender()]"
+    WIKI_EXPLOIT_SCENARIO = """"""
+
+    WIKI_RECOMMENDATION = """Use Txn.sender()."""
+
+    def detect(self) -> "ListOutput":
+        """Detector to find unoptimized access to the sender
+
+        Returns:
+              The instruction to optimized
+        """
+
+        detector_output: "ListOutput" = []
+
+        for contract in self.tealer.contracts.values():
+
+            all_findings: List[List[Instruction]] = []
+
+            for function in contract.functions.values():
+                for block in function.blocks:
+                    for ins in block.instructions:
+                        if isinstance(ins, Txna) and isinstance(ins.field, Accounts) and ins.field.idx == 0:  # type: ignore
+                            all_findings.append([ins])
+
+            if all_findings:
+                detector_output.append(InstructionsOutput(contract, self, all_findings))
+
+        return detector_output
diff --git a/tealer/detectors/rekeyto.py b/tealer/detectors/rekeyto.py
index 1005ca7..c1336c5 100644
--- a/tealer/detectors/rekeyto.py
+++ b/tealer/detectors/rekeyto.py
@@ -1,25 +1,27 @@
 """Detector for finding execution paths missing RekeyTo check."""
 
-from typing import List, TYPE_CHECKING
+from typing import List, TYPE_CHECKING, Tuple
 
 from tealer.detectors.abstract_detector import (
     AbstractDetector,
     DetectorClassification,
     DetectorType,
 )
-from tealer.teal.basic_blocks import BasicBlock
 from tealer.detectors.utils import (
-    detect_missing_tx_field_validations,
-    detector_terminal_description,
+    detect_missing_tx_field_validations_group,
+    detect_missing_tx_field_validations_group_complete,
 )
+from tealer.utils.output import ExecutionPaths
 
 
 if TYPE_CHECKING:
-    from tealer.utils.output import SupportedOutput
+    from tealer.teal.basic_blocks import BasicBlock
+    from tealer.utils.output import ListOutput, GroupTransactionOutput
     from tealer.teal.context.block_transaction_context import BlockTransactionContext
+    from tealer.teal.teal import Teal
 
 
-class MissingRekeyTo(AbstractDetector):
+class MissingRekeyTo(AbstractDetector):  # pylint: disable=too-few-public-methods
     """Detector to find execution paths missing RekeyTo check.
 
     TEAL, from version 2 onwards supports rekeying of accounts.
@@ -80,7 +82,7 @@ def withdraw(...) -> Expr:
 Validate `RekeyTo` field in the LogicSig.
 """
 
-    def detect(self) -> "SupportedOutput":
+    def detect(self) -> "ListOutput":
         """Detect execution paths with missing CloseRemainderTo check.
 
         Returns:
@@ -89,27 +91,24 @@ def detect(self) -> "SupportedOutput":
             information.
         """
 
-        paths_without_check: List[List[BasicBlock]] = []
-
         def checks_field(block_ctx: "BlockTransactionContext") -> bool:
             # return False if RekeyTo field can have any address.
             # return True if RekeyTo should have some address or zero address
             return not block_ctx.rekeyto.any_addr
 
-        paths_without_check += detect_missing_tx_field_validations(self.teal.bbs[0], checks_field)
-
-        # paths might repeat as cfg traversed twice, once for each check
-        paths_without_check_unique = []
-        added_paths = []
-        for path in paths_without_check:
-            short = " -> ".join(map(str, [bb.idx for bb in path]))
-            if short in added_paths:
-                continue
-            paths_without_check_unique.append(path)
-            added_paths.append(short)
-
-        description = detector_terminal_description(self)
-
-        filename = "missing_rekeyto_check"
-
-        return self.generate_result(paths_without_check_unique, description, filename)
+        # there should be a better to decide which function to call ??
+        if self.tealer.output_group:
+            # mypy complains if the value is returned directly. Uesd the second suggestion mentioned here:
+            # https://mypy.readthedocs.io/en/stable/common_issues.html#variance
+            return list(
+                detect_missing_tx_field_validations_group_complete(self.tealer, self, checks_field)
+            )
+
+        output: List[
+            Tuple["Teal", List[List["BasicBlock"]]]
+        ] = detect_missing_tx_field_validations_group(self.tealer, checks_field)
+        detector_output: "ListOutput" = []
+        for contract, vulnerable_paths in output:
+            detector_output.append(ExecutionPaths(contract, self, vulnerable_paths))
+
+        return detector_output
diff --git a/tealer/detectors/utils.py b/tealer/detectors/utils.py
index b512139..cd2c347 100644
--- a/tealer/detectors/utils.py
+++ b/tealer/detectors/utils.py
@@ -1,10 +1,16 @@
 import logging
 from typing import List, TYPE_CHECKING, Callable, Tuple, Optional
 
-from tealer.teal.instructions.instructions import Retsub, Callsub
+from tealer.utils.analyses import next_blocks_global, leaf_block_global
+from tealer.detectors.abstract_detector import DetectorType
+from tealer.utils.output import GroupTransactionOutput
+from tealer.utils.teal_enums import TransactionType
 
 if TYPE_CHECKING:
     from tealer.teal.basic_blocks import BasicBlock
+    from tealer.teal.teal import Teal
+    from tealer.tealer import Tealer
+    from tealer.teal.functions import Function
     from tealer.teal.subroutine import Subroutine
     from tealer.teal.context.block_transaction_context import BlockTransactionContext
     from tealer.detectors.abstract_detector import AbstractDetector
@@ -14,7 +20,10 @@
 
 
 def validated_in_block(
-    block: "BasicBlock", checks_field: Callable[["BlockTransactionContext"], bool]
+    block: "BasicBlock",
+    function: "Function",
+    checks_field: Callable[["BlockTransactionContext"], bool],
+    absolute_index: Optional[int] = None,
 ) -> bool:
     """
     A transaction field can be validated by accessing using instructions
@@ -28,28 +37,39 @@ def validated_in_block(
     `i`. It only represents possible values of current txn given that txn index is `i`.
 
     Args:
-        block:
-        checks_field: Given a block context, should return True if the target field cannot have the vulnerable value or else False
+        block: A basic block of the CFG
+        function: The function
+        checks_field: A function which given a block context, should return True if the target field
+            cannot have the vulnerable value or else False.
+            e.g: For is_updatable detector, vulnerable value is `UpdateApplication`.
+            if `block_ctx.transaction_types` can have `UpdateApplication`, this method will
+            return false or else returns True.
+        absolute_index: The index of the transaction (given in group config or computed)
 
-        e.g: For is_updatable detector, vulnerable value is `UpdateApplication`.
-        if `block_ctx.transaction_types` can have `UpdateApplication`, this method will
-        return false or else returns True.
     Returns:
         returns True if the field(s) is validated in this block or else False
     """
+    # print(str(block), "\n", block, function.transaction_context(block).transaction_types)
     # if field is checked using `txn {field}`, return true
-    if checks_field(block.transaction_context):
+    if checks_field(function.transaction_context(block)):
         return True
+    # if absolute index is given, check if field was checked by accessing it using gtxn(s) instructions`
+    if absolute_index is not None:
+        if checks_field(function.transaction_context(block).gtxn_context(absolute_index)):
+            return True
+        return False
     # for each possible {index} the transaction can have, check if field is checked using `gtxn {index} {field}`
-    for i in block.transaction_context.group_indices:
-        if not checks_field(block.transaction_context.gtxn_context(i)):
+    for i in function.transaction_context(block).group_indices:
+        if not checks_field(function.transaction_context(block).gtxn_context(i)):
             return False
     # the field is checked for all possible indices.
     return True
 
 
 def detect_missing_tx_field_validations(
-    entry_block: "BasicBlock", checks_field: Callable[["BlockTransactionContext"], bool]
+    function: "Function",
+    checks_field: Callable[["BlockTransactionContext"], bool],
+    satisfies_report_condition: Callable[[List["BasicBlock"]], bool] = lambda _x: True,
 ) -> List[List["BasicBlock"]]:
     """search for execution paths lacking validations on transaction fields.
 
@@ -73,13 +93,23 @@ def detect_missing_tx_field_validations(
     whether transaction field(s) can have the target vulnerable value.
 
     Args:
-        entry_block: entry basic block of the CFG
+        function: The function being checked
         checks_field: Given a block context, should return True if the target field cannot have the vulnerable value
             or else False.
+        satisfies_report_condition: Given a path, should return True if the "path" satifies the vulnerable condition.
 
+        Example for checks_field
             e.g: For is_updatable detector, vulnerable value is `UpdateApplication`.
             if `block_ctx.transaction_types` can have `UpdateApplication`, this method will
             return false or else returns True.
+
+        Example for satisfies_report_condition
+            The group-size detectors reports path lacking GroupSize check. However, it only reports the path
+            if an absolute index is used by one of the blocks in the path. That dectector can use this function
+            to decide on whether to report or not.
+            Other detectors can also use additional constraints that are based on entire vulnerable path before
+            reporting.
+
     Returns:
         Returns a list of vulnerable execution paths: none of the blocks in the path check the fields.
     """
@@ -92,7 +122,17 @@ def search_paths(
         current_subroutine_executed: List[List["BasicBlock"]],
     ) -> None:
         """
+        Traverse the CFG, generate possible execution paths and report vulnerable execution paths.
+
+        Execution paths are generated for the global CFG, where callsub blocks are connected to the subroutine's
+        entry block.
+
         Args:
+            bb: current basic block.
+            current_path: List of basic blocks in the execution path from the contract's entry block to
+                the current basic block bb.
+            paths_without_check: List of execution paths considered vulnerable out of the generated execution paths
+                upto now.
             current_call_stack: list of callsub blocks and called subroutine along the current path.
                 e.g current_callsub_blocks = [(Bi, S1), (Bj, S2), (Bk, S3), ...]
                 => 1. Bi, Bj, Bk, .. all end with callsub instruction.
@@ -167,7 +207,7 @@ def search_paths(
             )
             return
 
-        if validated_in_block(bb, checks_field):
+        if validated_in_block(bb, function, checks_field):
             logger_detectors.debug(
                 f"Validated Path: current_full_path = {current_path}\n, current_block: {repr(bb)}"
             )
@@ -176,19 +216,23 @@ def search_paths(
         current_path = current_path + [bb]
 
         # leaf block
-        if len(bb.next) == 0:
+        if leaf_block_global(bb):
             logger_detectors.debug(f"Vulnerable Path: current_full_path = {current_path}")
-            paths_without_check.append(current_path)
+            if satisfies_report_condition(current_path):
+                # only report if it satisfies the report condition
+                logger_detectors.debug(
+                    f"Vulnerable Path Satisfied Report Condition: current_full_path = {current_path} "
+                )
+                paths_without_check.append(current_path)
             return
 
         # do we need to make a copy of lists in [:-1]???
         current_subroutine_executed = current_subroutine_executed[:-1] + [
             current_subroutine_executed[-1] + [bb]
         ]
-        if isinstance(bb.exit_instr, Callsub):
-            teal = bb.teal
-            assert teal is not None
-            called_subroutine = teal.subroutines[bb.exit_instr.label]
+
+        if bb.is_callsub_block:
+            called_subroutine = bb.called_subroutine
             # check for recursion
             already_called_subroutines = [frame[1] for frame in current_call_stack]
             if called_subroutine in already_called_subroutines:
@@ -197,14 +241,14 @@ def search_paths(
             current_call_stack = current_call_stack + [(bb, called_subroutine)]
             current_subroutine_executed = current_subroutine_executed + [[]]
 
-        if isinstance(bb.exit_instr, Retsub):
+        if bb.is_retsub_block:
             # if this block is retsub then it returns execution to the return
             # point of callsub block. return point is the next instruction after the callsub
             # instruction.
             (callsub_block, _) = current_call_stack[-1]
             assert callsub_block is not None
             return_point = callsub_block.sub_return_point
-            if return_point is not None and return_point in bb.next:
+            if return_point is not None:
                 search_paths(
                     return_point,
                     current_path,
@@ -213,7 +257,7 @@ def search_paths(
                     current_subroutine_executed[:-1],
                 )
         else:
-            for next_bb in bb.next:
+            for next_bb in next_blocks_global(function, bb):
                 search_paths(
                     next_bb,
                     current_path,
@@ -222,17 +266,261 @@ def search_paths(
                     current_subroutine_executed,
                 )
 
-    teal = entry_block.teal
-    assert teal is not None
+    entry_block = function.entry
     paths_without_check: List[List["BasicBlock"]] = []
-    search_paths(entry_block, [], paths_without_check, [(None, teal.main)], [[]])
+    search_paths(entry_block, [], paths_without_check, [(None, function.main)], [[]])
+
     return paths_without_check
 
 
-def detector_terminal_description(detector: "AbstractDetector") -> str:
-    """Return description for the detector that is printed to terminal before listing vulnerable paths."""
-    return (
-        f'\nCheck: "{detector.NAME}", Impact: {detector.IMPACT}, Confidence: {detector.CONFIDENCE}\n'
-        f"Description: {detector.DESCRIPTION}\n\n"
-        f"Wiki: {detector.WIKI_URL}\n"
-    )
+def detect_missing_tx_field_validations_group(
+    tealer: "Tealer",
+    checks_field: Callable[["BlockTransactionContext"], bool],
+    satisfies_report_condition: Callable[[List["BasicBlock"]], bool] = lambda _x: True,
+) -> List[Tuple["Teal", List[List["BasicBlock"]]]]:
+    """Given the tealer object, return vulnerable execution paths.
+    The current implementation ignores the transaction structure. It iterates over the
+
+    Args:
+        tealer: the tealer object
+        checks_field:  A function which given a block context, should return True if the target field
+            cannot have the vulnerable value or else False.
+        satisfies_report_condition: Given a path, should return True if the "path" satifies the vulnerable condition.
+
+    Returns:
+        Returns a list of vulnerable execution paths: none of the blocks in the path check the fields.
+    """
+    output: List[Tuple["Teal", List[List["BasicBlock"]]]] = []
+    for group_txn in tealer.groups:
+        # Current implementation only works for single contract invocations
+        # assert len(group_txn.transactions) == 1
+        for txn in group_txn.transactions:
+            if txn.has_logic_sig and txn.logic_sig is not None:
+                vulnerable_paths = detect_missing_tx_field_validations(
+                    txn.logic_sig, checks_field, satisfies_report_condition
+                )
+                # Using old output class for now.
+                # TODO: create new output class to represent vulnerable paths in a group transaction context.
+                contract = txn.logic_sig.contract
+                output.append((contract, vulnerable_paths))
+            if txn.application is not None:
+                vulnerable_paths = detect_missing_tx_field_validations(
+                    txn.application, checks_field, satisfies_report_condition
+                )
+                contract = txn.application.contract
+                output.append((contract, vulnerable_paths))
+    return output
+
+
+def contract_checks_its_field(
+    function: "Function",
+    checks_field: Callable[["BlockTransactionContext"], bool],
+    absolute_index: Optional[int],
+) -> bool:
+    """
+    Return True if the contract checks its transaction field using `txn {field}` or `gtxn(s) {fields}` by accessing it using the index.
+
+    Args:
+        function: The function being checked
+        checks_field: A function which returns True if function validates the vulnerable transaction "field".
+        absolute_index: The absolute index of the transaction given by the user in the config.
+
+    Returns:
+        Returns True if the contracts checks its own transaction field.
+    """
+    leaf_blocks = [block for block in function.blocks if leaf_block_global(block)]
+    for block in leaf_blocks:
+        # checking the leaf blocks is enough to confirm the vulnerability
+        if not validated_in_block(block, function, checks_field, absolute_index):
+            # is vulnerable
+            return False
+    # contract checks its fields
+    return True
+
+
+def contract_checks_txn_at_absolute_index(
+    function: "Function",
+    checks_field: Callable[["BlockTransactionContext"], bool],
+    absolute_index: int,
+) -> bool:
+    """
+    Returns True if the Function checks the field of the transaction at `absolute_index` else False
+
+    Args:
+        function: The function
+        checks_field: A function which returns True if the context information has the vulnerable value.
+        absolute_index: The absolute index of the other transaction
+
+    Returns:
+        Returns True if the function checks the field of Gtxn[absolute_index] else False
+    """
+    leaf_blocks = [block for block in function.blocks if leaf_block_global(block)]
+    for block in leaf_blocks:
+        if not checks_field(function.transaction_context(block).absolute_context(absolute_index)):
+            # There is an execution path from entry to this leaf block in which the transaction field can have the
+            # vulnerable value.
+            return False
+    # The transaction field of Gtxn[absolute_index] is checked in all paths.
+    return True
+
+
+def contract_checks_using_relative_index(
+    function: "Function",
+    checks_field: Callable[["BlockTransactionContext"], bool],
+    offset: int,
+) -> bool:
+    """
+    Returns True if the Function checks the field of the transaction at `offset` from the function else False
+
+    Args:
+        function: The function
+        checks_field: A function which returns True if the context information has the vulnerable value.
+        offset: The offset of the other transaction from this function
+
+    Returns:
+        Returns True if the function checks the field of Gtxn[Txn.group_index() + offset] else False
+    """
+    leaf_blocks = [block for block in function.blocks if leaf_block_global(block)]
+    for block in leaf_blocks:
+        if not checks_field(function.transaction_context(block).relative_context(offset)):
+            # There is an execution path from entry to this leaf block in which the transaction field can have the
+            # vulnerable value.
+            return False
+    # The transaction field of Gtxn[absolute_index] is checked in all paths.
+    return True
+
+
+# def get_leaf_blocks(function: "Function") -> List["BasicBlock"]:
+#     for
+# change to a better name
+def detect_missing_tx_field_validations_group_complete(  # pylint: disable=too-many-branches
+    tealer: "Tealer",
+    detector: "AbstractDetector",
+    checks_field: Callable[["BlockTransactionContext"], bool],
+    vulnerable_transaction_types: Optional[List[TransactionType]] = None,
+) -> List[GroupTransactionOutput]:
+    """
+    `tealer.analyses.dataflow` calculates possible values a transaction field can have to successfully complete execution.
+    Information is computed at block level. Each block's context(`BlockTransactionContext`)
+    will have information on possible values for a transaction field such that
+        - Execution reaches to the first instruction of block `B`
+        - Execution reaches exit instruction of block `B` without failing.
+
+    The goal of current detectors is to find if a given transaction field(s) can have a certain vulnerable value(s).
+    e.g: is_updatable detector checks if the transaction can be UpdateApplication type transaction. Here, the vulnerable value for
+    transaction type is "UpdateApplication". if "UpdateApplication" is a possible value then the contract is considered vulnerable and is reported.
+
+    In order to find whether the given contract is vulnerable, it is sufficient to check the leaf blocks: whether transaction field can
+    have target value and execution reaches end of a leaf block. Note, this is not always true for AnyoneCanUpdate and AnyoneCanDelete dectectors. These
+    detectors depend on two fields that are tracked by independent keys. However, the current implementation is sufficient for the common patterns.
+    - Only LogicSigs are vulnerable to Stateless detectors.
+    - If a transaction does not have LogicSig then it is considered to be not vulnerable by Stateless detectors.
+
+    Args:
+        tealer: the tealer object
+        detector: The detector
+        checks_field:  A function which given a block context, should return True if the target field
+            cannot have the vulnerable value or else False.
+        vulnerable_transaction_types: The contracts are vulnerable when it is executed in only some of the transaction types. This list is that list of
+            transaction types. If this list is given, the transactions whose transaction type is not in the list will be skipped and are considered not
+            vulnerable to the detector.
+
+    Returns:
+        Found vulnerable operations/groups
+    """
+    output = []
+    for group_txn in tealer.groups:
+        is_vulnerable = False
+        vulnerable_transactions = {}
+        for txn in group_txn.transactions:
+            if detector.TYPE == DetectorType.STATELESS and not txn.has_logic_sig:
+                continue
+
+            if detector.TYPE == DetectorType.STATEFULL and txn.application is None:
+                # check for the application transaction type???
+                continue
+
+            if (
+                vulnerable_transaction_types is not None
+                and txn.type not in vulnerable_transaction_types
+            ):
+                continue
+            # print(txn.logic_sig.blocks)
+            # for block in txn.logic_sig.blocks:
+            #     print(block)
+            #     print(leaf_block_global(block))
+            #     print(txn.logic_sig.transaction_context(block).rekeyto)
+            # the contract has logic sig
+            # check if the contracts executed in the txn check the field.
+            # logic sig contract is given
+            # print("A")
+            if txn.logic_sig is not None and contract_checks_its_field(
+                txn.logic_sig, checks_field, txn.absoulte_index
+            ):
+                # the logic sig checks its field, not vulnerable
+                continue
+            # print("B")
+            if txn.application is not None and contract_checks_its_field(
+                txn.application, checks_field, txn.absoulte_index
+            ):
+                # the application checks the field. The transaction is not vulnerable
+                # continue to next transaction
+                continue
+            # check if any other transaction checks this transaction's field using absolute index
+            if txn.absoulte_index is not None:
+                checked = False
+                for other_txn in group_txn.transactions:
+                    if other_txn.logic_sig is not None and contract_checks_txn_at_absolute_index(
+                        other_txn.logic_sig, checks_field, txn.absoulte_index
+                    ):
+                        # The logic sig of some transaction in the group checks this transaction index using the absolute index
+                        checked = True
+                        break
+                    if other_txn.application is not None and contract_checks_txn_at_absolute_index(
+                        other_txn.application, checks_field, txn.absoulte_index
+                    ):
+                        checked = True
+                        break
+
+                if checked:
+                    continue
+
+            # check if any other transaction in the group checks this transaction's field using relative index
+            # group_txn.group_relative_indexes[t1][t2] = -1 => t1.group_index() == t2.group_index() - 1
+            # txn_relative_indexes =  group_txn.group_relative_indexes[txn]
+            checked = False
+            for (other_txn, offset) in group_txn.group_relative_indexes[txn].items():
+                # offset of this txn from other_txn. other_txn would have used this offset to access the field and validate it.
+                if other_txn.logic_sig is not None and contract_checks_using_relative_index(
+                    other_txn.logic_sig, checks_field, offset
+                ):
+                    # The logic sig of some other transaction in the group checks this transaction usiing the relative index.
+                    checked = True
+                    break
+
+                if other_txn.application is not None and contract_checks_using_relative_index(
+                    other_txn.application, checks_field, offset
+                ):
+                    checked = True
+                    break
+
+            if checked:
+                continue
+
+            # the contract is vulnerable. mark the operation/group as vulnerable
+            is_vulnerable = True
+
+            if detector.TYPE == DetectorType.STATELESS:
+                if txn.logic_sig is not None:
+                    vulnerable_transactions[txn] = [txn.logic_sig]
+                else:
+                    vulnerable_transactions[txn] = []
+            elif detector.TYPE == DetectorType.STATEFULL:
+                if txn.application is not None:
+                    vulnerable_transactions[txn] = [txn.application]
+                else:
+                    vulnerable_transactions[txn] = []
+
+        if is_vulnerable:
+            output.append(GroupTransactionOutput(detector, group_txn, vulnerable_transactions))
+    return output
diff --git a/tealer/execution_context/__init__.py b/tealer/execution_context/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tealer/execution_context/transactions.py b/tealer/execution_context/transactions.py
new file mode 100644
index 0000000..65891c5
--- /dev/null
+++ b/tealer/execution_context/transactions.py
@@ -0,0 +1,66 @@
+from typing import TYPE_CHECKING, Optional, Dict, List
+
+from tealer.utils.teal_enums import TransactionType
+
+# from tealer.exceptions import TealerException
+
+if TYPE_CHECKING:
+    from tealer.teal.functions import Function
+
+# pylint: disable=too-few-public-methods, too-many-instance-attributes
+class Transaction:
+    def __init__(self) -> None:
+        self.type: TransactionType = TransactionType.Any
+        self.has_logic_sig: bool = False
+        self.logic_sig: Optional["Function"] = None
+        self.application: Optional["Function"] = None
+        self.absoulte_index: Optional[int] = None
+        # t1.relative_indexes[-1] = t2 => t2.group_index() == t1.group_index() - 1
+        self.relative_indexes: Dict[int, Transaction] = {}
+        self.group_transaction: Optional[GroupTransaction] = None
+        self.transacton_id: str = ""
+
+
+# pylint: disable=too-few-public-methods
+class GroupTransaction:
+    def __init__(self) -> None:
+        self.transactions: List[Transaction] = []
+        self.absolute_indexes: Dict[int, Transaction] = {}
+        # {t1: (t2, -2)} => t1.group_index() == t2.group_index() - 2
+        self.group_relative_indexes: Dict[Transaction, Dict[Transaction, int]] = {}
+        self.operation_name: str = ""
+
+
+def fill_group_relative_indexes(group: "GroupTransaction") -> None:
+    # group_relative_indexes: Dict[Transaction, Dict[Transaction, int]] = {}
+    for txn in group.transactions:
+        group.group_relative_indexes[txn] = {}
+
+    for txn in group.transactions:
+        for offset in txn.relative_indexes:
+            other_txn = txn.relative_indexes[offset]
+            # other_txn.group_index() = txn.group_index() + offset
+            group.group_relative_indexes[other_txn][txn] = offset
+
+
+# def fill_indexes(group: GroupTransaction):
+#     """Calculate the absolute indexes and relative indexes using the indexes from the config.
+
+#     """
+
+#     for txn in group.transactions:
+#         if txn.absoulte_index is not None:
+#             for offset in txn.relative_indexes:
+#                 other_txn = txn.relative_indexes[offset]
+#                 other_txn_abs = txn.absoulte_index + offset
+#                 if other_txn.absoulte_index is None:
+#                     other_txn.absoulte_index = other_txn_abs
+#                     group.absolute_indexes[other_txn_abs] = other_txn
+#                 # else:
+#                 #     if other_txn.absoulte_index != other_txn_abs:
+#                         # raise TealerException("Invalid group configuration")
+#         for offset in txn.relative_indexes:
+#             other_txn = txn.relative_indexes[offset]
+#             this_txn_offset = -1 * offset
+#             if this_txn_offset not in other_txn.relative_indexes:
+#                 other_txn.relative_indexes[this_txn_offset] = txn
diff --git a/tealer/printers/abstract_printer.py b/tealer/printers/abstract_printer.py
index 6e3873f..63f5e1d 100644
--- a/tealer/printers/abstract_printer.py
+++ b/tealer/printers/abstract_printer.py
@@ -11,7 +11,7 @@
 
 import abc
 
-from typing import TYPE_CHECKING, Optional
+from typing import TYPE_CHECKING
 
 if TYPE_CHECKING:
     from tealer.teal.teal import Teal
@@ -73,16 +73,10 @@ def __init__(self, teal: "Teal"):
             )
 
     @abc.abstractmethod
-    def print(self, dest: Optional["Path"] = None) -> None:
+    def print(self) -> None:
         """entry method of the printer.
 
         All printers must override this method with the functionality
         specific to them. This method is the entry point of the printer
         and will be called to execute it.
-
-        Args:
-            dest: if printer generates and saves output in files then
-                :dest: will be used as destination directory if given. if
-                :dest: is None, files must be saved in the current
-                directory.
         """
diff --git a/tealer/printers/call_graph.py b/tealer/printers/call_graph.py
index 5ff1fc0..53f6401 100644
--- a/tealer/printers/call_graph.py
+++ b/tealer/printers/call_graph.py
@@ -1,11 +1,12 @@
 """Printer for exporting call-graph of the contract."""
 
 import html
+import os
 from pathlib import Path
-from typing import List, Dict, Optional
+from typing import Dict, Set
 
 from tealer.printers.abstract_printer import AbstractPrinter
-from tealer.teal.instructions.instructions import Callsub
+from tealer.utils.output import ROOT_OUTPUT_DIRECTORY
 
 
 class PrinterCallGraph(AbstractPrinter):  # pylint: disable=too-few-public-methods
@@ -22,43 +23,29 @@ class PrinterCallGraph(AbstractPrinter):  # pylint: disable=too-few-public-metho
     HELP = "Export the call graph of contract to a dot file"
     WIKI_URL = "https://github.com/crytic/tealer/wiki/Printer-documentation#call-graph"
 
-    def _construct_call_graph(self) -> Dict[str, List[str]]:
+    def _construct_call_graph(self) -> Dict[str, Set[str]]:
         """construct call graph for the contract.
 
-        entry point is treated as a separate subroutine/function `__entry__`.
+        entry point is treated as a separate subroutine/function `__main__`.
         For each subroutine, label(subroutine name) represents the node and each callsub
         instruction in the subroutine corresponds to directed edge to the target subroutine.
 
         Returns:
-            Dict[str, List[str]]: dictionary representing the graph. each `key` is a node and
-            `value` is a list representing the edges. Each str in value list corresponds to end
-            node for a directed edge from `key` node.
+            Dict[str, Set[str]]: dictionary representing the graph. The graph has edges from each
+            subroutine in d[key] to key. The source node of the edge are values and destination node is the key.
+            if d["S1"] = ["S2", "S3"] then graph has edges "S3 -> S1", "S2 -> S1".
         """
 
-        graph: Dict[str, List[str]] = {}
-        for sub_name, sub in self.teal.subroutines.items():
-            # graph edges
-            edges = []
-            for bb in sub.blocks:
-                for ins in bb.instructions:
-                    if isinstance(ins, Callsub):
-                        edges.append(ins.label)
-
-            graph[sub_name] = edges
-
-        # treat entry point of the contract as a separate function
-        entry_function_blocks = self.teal.main.blocks
-        # use __entry__ as function name for entry point
-        entry_function_name = "__entry__"
-        graph[entry_function_name] = []
-        for bb in entry_function_blocks:
-            for ins in bb.instructions:
-                if isinstance(ins, Callsub):
-                    graph[entry_function_name].append(ins.label)
+        graph: Dict[str, Set[str]] = {}
+        for _, subroutine in self.teal.subroutines.items():
+            graph[subroutine.name] = set(
+                map(lambda bi: bi.subroutine.name, subroutine.caller_blocks)
+            )
 
+        # no need to handle __main__ subroutine cause it is program entry point and there won't be caller blocks.
         return graph
 
-    def print(self, dest: Optional["Path"] = None) -> None:
+    def print(self) -> None:
         """Export call graph of the contract in dot format.
 
         Each node in the call graph corresponds to a subroutine and edge representing call from
@@ -66,12 +53,7 @@ def print(self, dest: Optional["Path"] = None) -> None:
         function and represented with label `__entry__`. Call graph will be saved as `call-graph.dot`
         in the destination directory if given or else in the current directory.
         Subroutines are supported from teal version 4. so, for contracts written in lesser version, this
-        function only prints the error message stating the same.
-
-        Args:
-            dest (Optional[Path]): destination directory to save output files in. files will be saved in
-            the current directory if it is None.
-        """
+        function only prints the error message stating the same."""
 
         if self.teal.version < 4:
             print("subroutines are not supported in teal version 3 or less")
@@ -82,18 +64,21 @@ def print(self, dest: Optional["Path"] = None) -> None:
 
         # construct dot representation of the graph
         graph_edges = ""
-        for sub_name, targets in graph.items():
-            sub_name = html.escape(sub_name, quote=True)
-            dot_output += f"{sub_name}[label={sub_name}];\n"
-            for target_sub in targets:
-                target_sub = html.escape(target_sub, quote=True)
-                graph_edges += f"{sub_name} -> {target_sub};\n"
+        for destination_sub, source_subs in graph.items():
+            destination_sub = html.escape(destination_sub, quote=True)
+            dot_output += f"{destination_sub}[label={destination_sub}];\n"
+            for source_sub in source_subs:
+                source_sub = html.escape(source_sub, quote=True)
+                graph_edges += f"{source_sub} -> {destination_sub};\n"
         dot_output += graph_edges
         dot_output += "}\n"
 
+        # outputs a single file: set the dir to {ROOT_DIRECTORY}/{CONTRACT_NAME}
+        dest = ROOT_OUTPUT_DIRECTORY / Path(self.teal.contract_name)
+        os.makedirs(dest, exist_ok=True)
+
         filename = Path("call-graph.dot")
-        if dest is not None:
-            filename = dest / filename
+        filename = dest / filename
 
         print(f"\nExported call graph to {filename}")
         with open(filename, "w", encoding="utf-8") as f:
diff --git a/tealer/printers/full_cfg.py b/tealer/printers/full_cfg.py
index d951d23..01a1039 100644
--- a/tealer/printers/full_cfg.py
+++ b/tealer/printers/full_cfg.py
@@ -4,12 +4,11 @@
     PrinterCFG: Printer to export contract CFG.
 """
 
+import os
 from pathlib import Path
 
-from typing import Optional
-
 from tealer.printers.abstract_printer import AbstractPrinter
-from tealer.utils.output import full_cfg_to_dot
+from tealer.utils.output import full_cfg_to_dot, ROOT_OUTPUT_DIRECTORY
 
 
 class PrinterCFG(AbstractPrinter):  # pylint: disable=too-few-public-methods
@@ -19,15 +18,13 @@ class PrinterCFG(AbstractPrinter):  # pylint: disable=too-few-public-methods
     HELP = "Export the CFG of entire contract"
     WIKI_URL = "https://github.com/crytic/tealer/wiki/Printer-documentation#cfg"
 
-    def print(self, dest: Optional[Path] = None) -> None:
-        """Export the CFG of entire contract.
+    def print(self) -> None:
+        """Export the CFG of entire contract."""
+        # outputs a single file: set the dir to {ROOT_DIRECTORY}/{CONTRACT_NAME}
+        dest = ROOT_OUTPUT_DIRECTORY / Path(self.teal.contract_name)
+        os.makedirs(dest, exist_ok=True)
 
-        Args:
-            dest (Optional[Path]): destination directory to save the dot file in.
-        """
-        if dest is None:
-            dest = Path(".")
+        filename = dest / Path("full_cfg.dot")
 
-        filename = Path("full_cfg.dot")
         print(f"\nCFG exported to file: {filename}")
-        full_cfg_to_dot(self.teal.bbs, filename=dest / filename)
+        full_cfg_to_dot(self.teal, filename=filename)
diff --git a/tealer/printers/function_cfg.py b/tealer/printers/function_cfg.py
index 561663e..4201d84 100644
--- a/tealer/printers/function_cfg.py
+++ b/tealer/printers/function_cfg.py
@@ -13,11 +13,11 @@
         in the contract. The CFGs will be saved in dot format.
 """
 
+import os
 from pathlib import Path
-from typing import Optional
 
 from tealer.printers.abstract_printer import AbstractPrinter
-from tealer.utils.output import all_subroutines_to_dot
+from tealer.utils.output import all_subroutines_to_dot, ROOT_OUTPUT_DIRECTORY
 
 
 class PrinterFunctionCFG(AbstractPrinter):  # pylint: disable=too-few-public-methods
@@ -33,14 +33,10 @@ class PrinterFunctionCFG(AbstractPrinter):  # pylint: disable=too-few-public-met
     HELP = "Export the CFG of each subroutine"
     WIKI_URL = "https://github.com/crytic/tealer/wiki/Printer-documentation#subroutine-cfg"
 
-    def print(self, dest: Optional[Path] = None) -> None:
-        """Export CFG of each subroutine defined in the contract.
-
-        Args:
-            dest (Optional[Path]): directory to save the generated dot files. Uses current directory
-            by default.
-        """
-        if dest is None:
-            dest = Path(".")
+    def print(self) -> None:
+        """Export CFG of each subroutine defined in the contract."""
+        # outputs multiple files: set the dir to {ROOT_DIRECTORY}/{CONTRACT_NAME}/{"print-"PRINTER_NAME}
+        dest = ROOT_OUTPUT_DIRECTORY / Path(self.teal.contract_name) / Path(f"print-{self.NAME}")
+        os.makedirs(dest, exist_ok=True)
 
         all_subroutines_to_dot(self.teal, dest)
diff --git a/tealer/printers/human_summary.py b/tealer/printers/human_summary.py
index cfb26d1..f929d18 100644
--- a/tealer/printers/human_summary.py
+++ b/tealer/printers/human_summary.py
@@ -12,17 +12,16 @@
 
 """
 
-from typing import List, Tuple, Optional, TYPE_CHECKING
+from typing import TYPE_CHECKING
 
 from tealer.printers.abstract_printer import AbstractPrinter
-from tealer.teal.instructions.instructions import contract_type_to_txt
 from tealer.utils.code_complexity import compute_cyclomatic_complexity
-from tealer.detectors.abstract_detector import DetectorClassification
 
 if TYPE_CHECKING:
     from pathlib import Path
 
 
+# pylint: disable=too-few-public-methods
 class PrinterHumanSummary(AbstractPrinter):
     """Printer to print summary of the contract"""
 
@@ -45,117 +44,110 @@ def _is_complex_code(self) -> str:
         result = "Yes" if is_complex else "No"
         return result
 
-    # pylint: disable=too-many-locals
-    def _get_detectors_result(self) -> Tuple[List, int, int, int, int, int]:
-        """Return dectector results and number of issues found in the contract for each `impact` class.
-
-        Returns:
-            (Tuple[List, int, int, int, int, int]): returns list of results found by all the detectors and
-            number of issues found by detectors of impact optimization, informational, low, medium, high in
-            the same order.
-
-        """
-        from tealer.utils.command_line import (  # pylint: disable=import-outside-toplevel
-            get_detectors_and_printers,
-        )
-
-        detector_classes, _ = get_detectors_and_printers()
-        detectors = [d(self.teal) for d in detector_classes]
-
-        checks_optimization = [
-            d for d in detectors if d.IMPACT == DetectorClassification.OPTIMIZATION
-        ]
-        checks_informational = [
-            d for d in detectors if d.IMPACT == DetectorClassification.INFORMATIONAL
-        ]
-        checks_low = [d for d in detectors if d.IMPACT == DetectorClassification.LOW]
-        checks_medium = [d for d in detectors if d.IMPACT == DetectorClassification.MEDIUM]
-        checks_high = [d for d in detectors if d.IMPACT == DetectorClassification.HIGH]
-
-        issues_optimization = [c.detect().paths for c in checks_optimization]
-        issues_optimization = [c for c in issues_optimization if c]
-
-        issues_informational = [c.detect().paths for c in checks_informational]
-        issues_informational = [c for c in issues_informational if c]
-
-        issues_low = [c.detect().paths for c in checks_low]
-        issues_low = [c for c in issues_low if c]
-
-        issues_medium = [c.detect().paths for c in checks_medium]
-        issues_medium = [c for c in issues_medium if c]
-
-        issues_high = [c.detect().paths for c in checks_high]
-        issues_high = [c for c in issues_high if c]
-
-        all_results = (
-            issues_optimization + issues_informational + issues_low + issues_medium + issues_high
-        )
-
-        return (
-            all_results,
-            len(issues_optimization),
-            len(issues_informational),
-            len(issues_low),
-            len(issues_medium),
-            len(issues_high),
-        )
-
-    def get_detectors_result(self) -> str:
-        """Return textual summary of number of issues found by detectors.
-
-        Textual summary contains number of issues found in the contract
-        for each class of impact(severity).
-
-        Returns:
-            (str): textual summary of number of issues found by detectors.
-        """
-
-        (
-            _,
-            optimization,
-            informational,
-            low,
-            medium,
-            high,
-        ) = self._get_detectors_result()
-
-        txt = f"Number of optimization issues: {optimization}\n"
-        txt += f"Number of informational issues: {informational}\n"
-        txt += f"Number of low issues: {low}\n"
-        txt += f"Number of medium issues: {medium}\n"
-        txt += f"Number of high issues: {high}\n"
-
-        return txt
-
-    def print(self, dest: Optional["Path"] = None) -> None:
+        # pylint: disable=too-many-locals
+        # TODO: Decide on how printers should behave in group context
+        # def _get_detectors_result(self) -> Tuple[List, int, int, int, int, int]:
+        #     """Return dectector results and number of issues found in the contract for each `impact` class.
+
+        #     Returns:
+        #         (Tuple[List, int, int, int, int, int]): returns list of results found by all the detectors and
+        #         number of issues found by detectors of impact optimization, informational, low, medium, high in
+        #         the same order.
+
+        #     """
+        #     from tealer.utils.command_line.common import (  # pylint: disable=import-outside-toplevel
+        #         get_detectors_and_printers,
+        #     )
+
+        #     detector_classes, _ = get_detectors_and_printers()
+        #     detectors = [d(self.teal) for d in detector_classes]
+
+        #     checks_optimization = [
+        #         d for d in detectors if d.IMPACT == DetectorClassification.OPTIMIZATION
+        #     ]
+        #     checks_informational = [
+        #         d for d in detectors if d.IMPACT == DetectorClassification.INFORMATIONAL
+        #     ]
+        #     checks_low = [d for d in detectors if d.IMPACT == DetectorClassification.LOW]
+        #     checks_medium = [d for d in detectors if d.IMPACT == DetectorClassification.MEDIUM]
+        #     checks_high = [d for d in detectors if d.IMPACT == DetectorClassification.HIGH]
+
+        #     issues_optimization = [c.detect().paths for c in checks_optimization]
+        #     issues_optimization = [c for c in issues_optimization if c]
+
+        #     issues_informational = [c.detect().paths for c in checks_informational]
+        #     issues_informational = [c for c in issues_informational if c]
+
+        #     issues_low = [c.detect().paths for c in checks_low]
+        #     issues_low = [c for c in issues_low if c]
+
+        #     issues_medium = [c.detect().paths for c in checks_medium]
+        #     issues_medium = [c for c in issues_medium if c]
+
+        #     issues_high = [c.detect().paths for c in checks_high]
+        #     issues_high = [c for c in issues_high if c]
+
+        #     all_results = (
+        #         issues_optimization + issues_informational + issues_low + issues_medium + issues_high
+        #     )
+
+        #     return (
+        #         all_results,
+        #         len(issues_optimization),
+        #         len(issues_informational),
+        #         len(issues_low),
+        #         len(issues_medium),
+        #         len(issues_high),
+        #     )
+
+        # def get_detectors_result(self) -> str:
+        # """Return textual summary of number of issues found by detectors.
+
+        # Textual summary contains number of issues found in the contract
+        # for each class of impact(severity).
+
+        # Returns:
+        #     (str): textual summary of number of issues found by detectors.
+        # """
+
+        # (
+        #     _,
+        #     optimization,
+        #     informational,
+        #     low,
+        #     medium,
+        #     high,
+        # ) = self._get_detectors_result()
+
+        # txt = f"Number of optimization issues: {optimization}\n"
+        # txt += f"Number of informational issues: {informational}\n"
+        # txt += f"Number of low issues: {low}\n"
+        # txt += f"Number of medium issues: {medium}\n"
+        # txt += f"Number of high issues: {high}\n"
+
+        # return txt
+
+    def print(self) -> None:
         """Print summary of the contract to stdout.
 
         Currently displays version, mode, number of basic blocks, instructions, subroutines,
         Names of subroutines, number of results found by detectors for each level of impact.
-
-        Args:
-            dest: if printer saves output in files then they will be saved in
-                :dest: directory. Currently, the printer doesn't save output in
-                files, this argument is ignored.
         """
 
         teal = self.teal
 
         txt = "\n"
         txt += f"Program version: {teal.version}\n"
-        txt += f"Mode: {contract_type_to_txt[teal.mode]}\n"
+        txt += f"Mode: {str(teal.mode)}\n"
         txt += f"Number of basic blocks: {len(teal.bbs)}\n"
         txt += f"Number of instructions: {len(teal.instructions)}\n"
 
-        txt += f"Number of subroutines: {len(teal.subroutines_list)}\n"
+        txt += f"Number of subroutines: {len(teal.subroutines.items())}\n"
         txt += "Subroutines:\n"
         for sub_name in teal.subroutines:
             txt += f"\t{sub_name}\n"
-        txt += "\n"
-
-        txt_detector_results = self.get_detectors_result()
-        txt += txt_detector_results
-
-        txt += f"is_complex: {self._is_complex_code()}\n"
-
+            block_ids = ", ".join(repr(bi) for bi in teal.subroutines[sub_name].blocks)
+            txt += f'\t\t"{block_ids}"\n'
+        # txt += f"is_complex: {self._is_complex_code()}\n" # update printer after deciding on its semantics
         print(txt)
+        # print(self.get_detectors_result())
diff --git a/tealer/printers/transaction_context.py b/tealer/printers/transaction_context.py
index 1c0735b..86a5de2 100644
--- a/tealer/printers/transaction_context.py
+++ b/tealer/printers/transaction_context.py
@@ -1,10 +1,16 @@
 """Printer to output information about transaction fields in the CFG."""
 
+import os
 from pathlib import Path
-from typing import List, Optional, TYPE_CHECKING
+from typing import List, TYPE_CHECKING
 
 from tealer.printers.abstract_printer import AbstractPrinter
-from tealer.utils.output import CFGDotConfig, full_cfg_to_dot, all_subroutines_to_dot
+from tealer.utils.output import (
+    CFGDotConfig,
+    full_cfg_to_dot,
+    all_subroutines_to_dot,
+    ROOT_OUTPUT_DIRECTORY,
+)
 
 if TYPE_CHECKING:
     from tealer.teal.basic_blocks import BasicBlock
@@ -35,6 +41,12 @@ def _repr_num_list(values: List[int]) -> str:
 
             1 2 3 5 6 7 8 9 11 13 14 15 16
            => 1 2 3 5..9 11 13..16
+
+        Args:
+            values: Sorted list of integers. smaller value is first.
+
+        Returns:
+            Returns short string representation of the :values:
         """
         values = sorted(values)
         sequences: List[List[int]] = [[]]
@@ -53,29 +65,26 @@ def _repr_num_list(values: List[int]) -> str:
                 str_seqs.append(" ".join(str(i) for i in seq))
         return " ".join(str_seqs)
 
-    def print(self, dest: Optional["Path"] = None) -> None:
-        """
-        Args:
-            dest (Optional[Path]): destination directory to save output files in. files will be saved in
-            the current directory if it is None.
-        """
-
+    def print(self) -> None:
         filename = Path("transaction-context.dot")
-        if dest is None:
-            # TODO: Change default directory to `tealer-export`
-            dest = Path(".")
+
+        # outputs multiple files: set the dir to {ROOT_DIRECTORY}/{CONTRACT_NAME}/{"print-"PRINTER_NAME}
+        dest = ROOT_OUTPUT_DIRECTORY / Path(self.teal.contract_name) / Path(f"print-{self.NAME}")
+        os.makedirs(dest, exist_ok=True)
 
         filename = dest / filename
+        function = list(self.teal.functions.values())[0]
 
         def get_info(bb: "BasicBlock") -> List[str]:
-            group_indices_str = self._repr_num_list(bb.transaction_context.group_indices)
-            group_sizes_str = self._repr_num_list(bb.transaction_context.group_sizes)
+            # NOTE: use the first function for now as `init_tealer_from_single_contract` uses entire contract as single function.
+            group_indices_str = self._repr_num_list(function.transaction_context(bb).group_indices)
+            group_sizes_str = self._repr_num_list(function.transaction_context(bb).group_sizes)
             return [f"GroupIndex: {group_indices_str}", f"GroupSize: {group_sizes_str}"]
 
         config = CFGDotConfig()
         config.bb_additional_comments = get_info
         # generate a Full CFG with all group-size, group-index comments
-        full_cfg_to_dot(self.teal.bbs, config, filename)
+        full_cfg_to_dot(self.teal, config, filename)
         # Also generate shortened CFGs with group-size and group-index comments.
         all_subroutines_to_dot(
             self.teal, dest, config, "txn_ctx"
diff --git a/tealer/teal/basic_blocks.py b/tealer/teal/basic_blocks.py
index 5515e83..60b1584 100644
--- a/tealer/teal/basic_blocks.py
+++ b/tealer/teal/basic_blocks.py
@@ -15,15 +15,15 @@
 
 from typing import List, Optional, TYPE_CHECKING
 
-from tealer.teal.instructions.instructions import Instruction
-from tealer.teal.context.block_transaction_context import BlockTransactionContext
-
+from tealer.teal.instructions.instructions import Instruction, Callsub, Retsub
+from tealer.exceptions import TealerException
 
 if TYPE_CHECKING:
     from tealer.teal.teal import Teal
+    from tealer.teal.subroutine import Subroutine
 
 
-class BasicBlock:  # pylint: disable=too-many-instance-attributes
+class BasicBlock:  # pylint: disable=too-many-instance-attributes,too-many-public-methods
     """Class to represent basic blocks of the teal contract.
 
     A basic block is a sequence of instructions with a single entry
@@ -39,10 +39,8 @@ def __init__(self) -> None:
         self._next: List[BasicBlock] = []
         self._idx: int = 0
         self._teal: Optional["Teal"] = None
-        self._transaction_context = BlockTransactionContext()
-        self._callsub_block: Optional[BasicBlock] = None
-        self._sub_return_point: Optional[BasicBlock] = None
         self._tealer_comments: List[str] = []
+        self._subroutine: Optional["Subroutine"] = None
 
     def add_instruction(self, instruction: Instruction) -> None:
         """Append instruction to this basic block.
@@ -55,17 +53,29 @@ def add_instruction(self, instruction: Instruction) -> None:
 
     @property
     def instructions(self) -> List[Instruction]:
-        """List of instructions part of this basic block."""
+        """List of instructions part of this basic block.
+
+        Returns:
+            list of basic block instructions.
+        """
         return self._instructions
 
     @property
     def entry_instr(self) -> Instruction:
-        """Entry(first) instruction of this basic block."""
+        """Entry(first) instruction of this basic block.
+
+        Returns:
+            The entry instruction of the basic block.
+        """
         return self._instructions[0]
 
     @property
     def exit_instr(self) -> Instruction:
-        """Exit(Last) instruction of this basic block."""
+        """Exit(Last) instruction of this basic block.
+
+        Returns:
+            The exit instruction of the basic block.
+        """
         return self._instructions[-1]
 
     def add_prev(self, prev_bb: "BasicBlock") -> None:
@@ -102,17 +112,29 @@ def add_next(self, next_bb: "BasicBlock") -> None:
 
     @property
     def prev(self) -> List["BasicBlock"]:
-        """List of previous basic blocks to this basic block."""
+        """List of previous basic blocks to this basic block.
+
+        Returns:
+            list of previous instructions that might have been executed before this block.
+        """
         return self._prev
 
     @property
     def next(self) -> List["BasicBlock"]:
-        """List of next basic blocks to this basic block."""
+        """List of next basic blocks to this basic block.
+
+        Returns:
+            list of next basic blocks that might be executed after this block.
+        """
         return self._next
 
     @property
     def idx(self) -> int:
-        """Index of this basic block when ordered by line number of entry instruction."""
+        """Index of this basic block when ordered by line number of entry instruction.
+
+        Returns:
+            index of this basic block in list of all basic blocks.
+        """
         return self._idx
 
     @idx.setter
@@ -120,35 +142,21 @@ def idx(self, i: int) -> None:
         self._idx = i
 
     @property
-    def callsub_block(self) -> Optional["BasicBlock"]:
-        """If this block is the return point of a subroutine, `callsub_block` is the block
-        that called the subroutine.
-        """
-        return self._callsub_block
-
-    @callsub_block.setter
-    def callsub_block(self, b: "BasicBlock") -> None:
-        self._callsub_block = b
+    def cost(self) -> int:
+        """cost of executing all instructions in this basic block
 
-    @property
-    def sub_return_point(self) -> Optional["BasicBlock"]:
-        """If a subroutine is executed after this block i.e exit instruction is callsub.
-        then, sub_return_point will be basic block that will be executed after the subroutine.
+        Returns:
+            Returns the OpcodeCost of executing all instructions in the block.
         """
-        return self._sub_return_point
-
-    @sub_return_point.setter
-    def sub_return_point(self, b: "BasicBlock") -> None:
-        self._sub_return_point = b
-
-    @property
-    def cost(self) -> int:
-        """cost of executing all instructions in this basic block"""
         return sum(ins.cost for ins in self.instructions)
 
     @property
     def teal(self) -> Optional["Teal"]:
-        """Teal instance of the contract this basic block belongs to."""
+        """Teal instance of the contract this basic block belongs to.
+
+        Returns:
+            Returns the contract of this basic block.
+        """
         return self._teal
 
     @teal.setter
@@ -156,12 +164,103 @@ def teal(self, teal_instance: "Teal") -> None:
         self._teal = teal_instance
 
     @property
-    def transaction_context(self) -> "BlockTransactionContext":
-        return self._transaction_context
+    def subroutine(self) -> "Subroutine":
+        """Subroutine instrance of the subroutine this basic block belongs to.
+
+        Returns:
+            Returns the subroutine this block belongs to.
+
+        Raises:
+            TealerException: raises error if subroutine is not set during parsing.
+        """
+        if self._subroutine is None:
+            raise TealerException(f"subroutine of B{self._idx} is not initialized")
+        return self._subroutine
+
+    @subroutine.setter
+    def subroutine(self, subroutine_instance: "Subroutine") -> None:
+        self._subroutine = subroutine_instance
+
+    @property
+    def is_callsub_block(self) -> bool:
+        """Return True if the block calls a subroutine.
+
+        Returns:
+            Returns True if this block has a callsub instruction.
+        """
+        return isinstance(self.exit_instr, Callsub)
+
+    @property
+    def called_subroutine(self) -> "Subroutine":
+        """Return the subroutine called by this subroutine.
+
+        Returns:
+            Returns the subroutine called by the callsub instruction in this block.
+
+        Raises:
+            TealerException: if this block is not a callsub_block.
+        """
+        if not isinstance(self.exit_instr, Callsub):
+            raise TealerException("called subroutine of a non callsub block is accessed")
+        return self.exit_instr.called_subroutine
+
+    @property
+    def sub_return_point(self) -> Optional["BasicBlock"]:
+        """Return the return point block of this block.
+
+        Returns:
+            The subroutine return_point block of this(callsub) block.
+            Returns None if this callsub block does not have a return point block. This happens when
+            callsub instruction is the last instruction in the contract and The called subroutine always exits
+            the program.
+
+        Raises:
+            TealerException: if this block is not a callsub_block.
+        """
+        if not self.is_callsub_block:
+            raise TealerException("sub_return_point block of a non callsub block is accessed")
+        return self.next[0] if self.next else None
+
+    @property
+    def is_sub_return_point(self) -> bool:
+        """Return True if this block is executed after the subroutine i.e next block of callsub_block
+
+        Returns:
+            Returns True if this block is executed a subroutine: The next block of a block with callsub
+            instruction. Otherwise, returns False.
+        """
+        for bi in self.prev:
+            if bi.is_callsub_block:
+                return True
+        return False
+
+    @property
+    def callsub_block(self) -> "BasicBlock":
+        """Return the callsub_block which calls the subroutine. This block is the return point block.
+
+        Returns:
+            Return the callsub_block which calls the subroutine that returns the execution to this
+            basic block.
+
+        Raises:
+            TealerException: if this block is not a sub_return_point block.
+        """
+        for bi in self.prev:
+            if bi.is_callsub_block:
+                return bi
+        raise TealerException("callsub_block of a non sub_return_point block is accessed")
+
+    @property
+    def is_retsub_block(self) -> bool:
+        return isinstance(self.exit_instr, Retsub)
 
     @property
     def tealer_comments(self) -> List[str]:
-        """Additional comments added by tealer for each basic block in the output CFG."""
+        """Additional comments added by tealer for each basic block in the output CFG.
+
+        Returns:
+            Returns the additional comments added by tealer for this basic block.
+        """
         return self._tealer_comments
 
     @tealer_comments.setter
diff --git a/tealer/teal/context/block_transaction_context.py b/tealer/teal/context/block_transaction_context.py
index 57a273e..310392b 100644
--- a/tealer/teal/context/block_transaction_context.py
+++ b/tealer/teal/context/block_transaction_context.py
@@ -1,4 +1,4 @@
-from typing import List, Optional
+from typing import List, Optional, Dict
 from dataclasses import dataclass, field
 
 from tealer.exceptions import TealerException
@@ -15,20 +15,32 @@ class AddrFieldValue:
 
 class BlockTransactionContext:  # pylint: disable=too-few-public-methods, too-many-instance-attributes
 
-    _group_transactions_context: Optional[List["BlockTransactionContext"]] = None
+    _gtxn_at_index_context: Optional[List["BlockTransactionContext"]] = None
+    _abs_context: Optional[List["BlockTransactionContext"]] = None
+    _relative_context: Optional[Dict[int, "BlockTransactionContext"]] = None
 
     def __init__(self, tail: bool = False) -> None:
         if not tail:
-            self._group_transactions_context = [BlockTransactionContext(True) for _ in range(16)]
+            self._gtxn_at_index_context = [
+                BlockTransactionContext(True) for _ in range(MAX_GROUP_SIZE)
+            ]
+            self._abs_context = [BlockTransactionContext(True) for _ in range(MAX_GROUP_SIZE)]
+            self._relative_context = {
+                offset: BlockTransactionContext(True)
+                for offset in range(-(MAX_GROUP_SIZE - 1), MAX_GROUP_SIZE)
+                if offset != 0
+            }
 
         # set default values
         if tail:
             # information from gtxn {i} instructions.
             self.group_indices = []
             self.group_sizes = []
+            self.is_gtxn_context = True
         else:
             self.group_sizes = list(range(1, MAX_GROUP_SIZE + 1))
             self.group_indices = list(range(0, MAX_GROUP_SIZE))
+            self.is_gtxn_context = False
         self.transaction_types = list(ALL_TRANSACTION_TYPES)
         self.rekeyto: AddrFieldValue = AddrFieldValue()
         self.closeto: AddrFieldValue = AddrFieldValue()
@@ -38,9 +50,59 @@ def __init__(self, tail: bool = False) -> None:
         self.max_fee_unknown: bool = False  # True if max possible fee is bounded and unknown
 
     def gtxn_context(self, txn_index: int) -> "BlockTransactionContext":
-        """context information collected from gtxn {txn_index} field instructions"""
-        if self._group_transactions_context is None:
+        """context information collected from gtxn {txn_index} field instructions
+
+        Args:
+            txn_index: Transaction index.
+
+        Returns:
+            context information collected from "gtxn {txn_index} field" instructions.
+            The information is of the txn executing this contract when it's index in the group
+            is :txn_index:
+
+        Raises:
+            TealerException: Raises error if gtxn_context of a gtxn_context is accessed or if the
+                transaction index is greater than MAX_GROUP_SIZE.
+        """
+        if self._gtxn_at_index_context is None:
+            raise TealerException()
+        if txn_index >= MAX_GROUP_SIZE:
+            raise TealerException()
+        return self._gtxn_at_index_context[txn_index]
+
+    def absolute_context(self, txn_index: int) -> "BlockTransactionContext":
+        """context information of the transaction at `txn_index`
+
+        Args:
+            txn_index: Transaction index
+
+        Returns:
+            The context information of the transaction at the give absolute index.
+
+        Raises:
+            TealerException: Raises error if abs_context of a tail context is accessed or if the transaction
+                index is greater than MAX_GROUP_SIZE.
+        """
+        if self._abs_context is None:
             raise TealerException()
         if txn_index >= MAX_GROUP_SIZE:
             raise TealerException()
-        return self._group_transactions_context[txn_index]
+        return self._abs_context[txn_index]
+
+    def relative_context(self, offset: int) -> "BlockTransactionContext":
+        """context information of the transaction at `offset` from the current transaction
+
+        Args:
+            offset: Transaction offset
+
+        Returns:
+            The context information of the transaction at the offset from the executing this contract
+
+        Raises:
+            TealerException: Raises error if r of a tail context is accessed or if the offset is not valid
+        """
+        if self._relative_context is None:
+            raise TealerException()
+        if offset not in self._relative_context:
+            raise TealerException()
+        return self._relative_context[offset]
diff --git a/tealer/teal/functions.py b/tealer/teal/functions.py
new file mode 100644
index 0000000..f5776fc
--- /dev/null
+++ b/tealer/teal/functions.py
@@ -0,0 +1,87 @@
+"""Defines a class to represent a Function in Teal contract.
+
+Functions are different from subroutine. Functions are abstract blocks of code, each callable
+by a end-user. Function represents an operation of the contract.
+
+Classes:
+    Function: Represents a Function.
+"""
+
+from typing import TYPE_CHECKING, List, Dict
+
+from tealer.teal.context.block_transaction_context import BlockTransactionContext
+
+if TYPE_CHECKING:
+    from tealer.teal.basic_blocks import BasicBlock
+    from tealer.teal.teal import Teal
+    from tealer.teal.subroutine import Subroutine
+
+
+# pylint: disable=too-few-public-methods,too-many-instance-attributes
+class Function:
+    """Class to represent a function in a contract."""
+
+    def __init__(  # pylint: disable=too-many-arguments
+        self,
+        function_name: str,
+        entry: "BasicBlock",
+        blocks: List["BasicBlock"],
+        contract: "Teal",
+        main: "Subroutine",
+        subroutines: Dict[str, "Subroutine"],
+    ) -> None:
+        self.function_name: str = function_name
+        self.entry: "BasicBlock" = entry
+        self._blocks: List["BasicBlock"] = blocks
+        self.contract: "Teal" = contract
+        self.main: "Subroutine" = main
+        self.subroutines: Dict[str, "Subroutine"] = subroutines
+        # A subroutine is represented using a single CFG. A subroutine can be part of multiple
+        # functions. Basic Blocks part of the subroutine CFG will be part of all functions the subroutine
+        # is used in. But, block transaction context is specific for each function. As a result,
+        # We cannot store the block transaction context in basic block object.
+        self._transaction_contexts: Dict["BasicBlock", "BlockTransactionContext"] = {
+            block: BlockTransactionContext() for block in self._blocks
+        }
+        # caller blocks of a subroutine depend on the function __main__ CFG. As a result, they
+        # cannot be computed without considering the function.
+        self._subroutine_caller_blocks: Dict["Subroutine", List["BasicBlock"]] = {
+            sub: [] for sub in subroutines.values()
+        }
+
+        for block in self._blocks:
+            if block.is_callsub_block:
+                self._subroutine_caller_blocks[block.called_subroutine].append(block)
+
+        self._subroutine_return_point_blocks: Dict["Subroutine", List["BasicBlock"]] = {}
+        for sub, caller_blocks in self._subroutine_caller_blocks.items():
+            self._subroutine_return_point_blocks[sub] = [
+                bi.next[0] for bi in caller_blocks if len(bi.next) == 1
+            ]
+
+        # print(self._subroutine_caller_blocks)
+
+        # TODO: add exit_blocks
+
+    @property
+    def blocks(self) -> List["BasicBlock"]:
+        return self._blocks
+
+    def transaction_context(self, block: "BasicBlock") -> "BlockTransactionContext":
+        return self._transaction_contexts[block]
+
+    def caller_blocks(self, subroutine: "Subroutine") -> List["BasicBlock"]:
+        """BasicBlock with callsub instructions which call the subroutine.
+
+        Args:
+            subroutine: the subroutine
+
+        Returns:
+            List of caller callsub basic blocks. This blocks have "callsub {subroutine.name}"
+            as exit instruction.
+        """
+        print("name", subroutine.name)
+        return self._subroutine_caller_blocks[subroutine]
+
+    def return_point_blocks(self, subroutine: "Subroutine") -> List["BasicBlock"]:
+        return self._subroutine_return_point_blocks[subroutine]
diff --git a/tealer/teal/global_field.py b/tealer/teal/global_field.py
index 63a5326..481f861 100644
--- a/tealer/teal/global_field.py
+++ b/tealer/teal/global_field.py
@@ -18,7 +18,11 @@ def __init__(self) -> None:
 
     @property
     def version(self) -> int:
-        """Teal version this field is introduced in and supported from."""
+        """Teal version this field is introduced in and supported from.
+
+        Returns:
+            Teal version the field is introduced in and supported from.
+        """
         return self._version
 
     def __str__(self) -> str:
diff --git a/tealer/teal/instructions/acct_params_field.py b/tealer/teal/instructions/acct_params_field.py
index fab7d49..239df88 100644
--- a/tealer/teal/instructions/acct_params_field.py
+++ b/tealer/teal/instructions/acct_params_field.py
@@ -18,7 +18,11 @@ def __init__(self) -> None:
 
     @property
     def version(self) -> int:
-        """Teal version this field is introduced in and supported from."""
+        """Teal version this field is introduced in and supported from.
+
+        Returns:
+            Teal version the field is introduced in and supported from.
+        """
         return self._version
 
     def __str__(self) -> str:
diff --git a/tealer/teal/instructions/app_params_field.py b/tealer/teal/instructions/app_params_field.py
index 1b0e971..a0b7e7c 100644
--- a/tealer/teal/instructions/app_params_field.py
+++ b/tealer/teal/instructions/app_params_field.py
@@ -18,7 +18,11 @@ def __init__(self) -> None:
 
     @property
     def version(self) -> int:
-        """Teal version this field is introduced in and supported from."""
+        """Teal version this field is introduced in and supported from.
+
+        Returns:
+            Teal version the field is introduced in and supported from.
+        """
         return self._version
 
     def __str__(self) -> str:
diff --git a/tealer/teal/instructions/asset_holding_field.py b/tealer/teal/instructions/asset_holding_field.py
index ec11878..d888248 100644
--- a/tealer/teal/instructions/asset_holding_field.py
+++ b/tealer/teal/instructions/asset_holding_field.py
@@ -18,7 +18,11 @@ def __init__(self) -> None:
 
     @property
     def version(self) -> int:
-        """Teal version this field is introduced in and supported from."""
+        """Teal version this field is introduced in and supported from.
+
+        Returns:
+            Teal version the field is introduced in and supported from.
+        """
         return self._version
 
     def __str__(self) -> str:
diff --git a/tealer/teal/instructions/asset_params_field.py b/tealer/teal/instructions/asset_params_field.py
index ba447db..1a61cb9 100644
--- a/tealer/teal/instructions/asset_params_field.py
+++ b/tealer/teal/instructions/asset_params_field.py
@@ -18,7 +18,11 @@ def __init__(self) -> None:
 
     @property
     def version(self) -> int:
-        """Teal version this field is introduced in and supported from."""
+        """Teal version this field is introduced in and supported from.
+
+        Returns:
+            Teal version the field is introduced in and supported from.
+        """
         return self._version
 
     def __str__(self) -> str:
diff --git a/tealer/teal/instructions/instructions.py b/tealer/teal/instructions/instructions.py
index 1c8a260..155188a 100644
--- a/tealer/teal/instructions/instructions.py
+++ b/tealer/teal/instructions/instructions.py
@@ -6,20 +6,6 @@ class which defines common methods and properties of teal instructions.
 
 Few teal instructions have immediate values which are defined and stored
 as appropriate properties in the class representing that instruction.
-
-Attributes:
-    ContractType: ContractType is a comparable enumerator. it defines
-        three symbols ``STATELESS``, ``STATEFULL``, ``ANY``.
-        ``STATELESS`` represents stateless(signature) contracts,
-        ``STATEFULL`` represents stateful(Application) contracts and
-        ``ANY`` represents that contract is either ``STATELESS`` Or
-        ``STATEFULL``. Exact meaning of the symbols depend on the place
-        of usage.
-
-    contract_type_to_txt: This is a mapping from ContractType symbol to
-        their corresponding string representation. Useful while printing
-        or outputting ContractType variable.
-
 """
 
 # pylint: disable=too-many-lines
@@ -31,24 +17,12 @@ class which defines common methods and properties of teal instructions.
 from tealer.teal.instructions.asset_params_field import AssetParamsField
 from tealer.teal.instructions.app_params_field import AppParamsField
 from tealer.teal.instructions.acct_params_field import AcctParamsField
-from tealer.utils.comparable_enum import ComparableEnum
+from tealer.utils.teal_enums import ExecutionMode
 from tealer.exceptions import TealerException
 
 if TYPE_CHECKING:
     from tealer.teal.basic_blocks import BasicBlock
-
-
-class ContractType(ComparableEnum):
-    STATELESS = 0
-    STATEFULL = 1
-    ANY = 2
-
-
-contract_type_to_txt = {
-    ContractType.STATEFULL: "stateful",
-    ContractType.STATELESS: "stateless",
-    ContractType.ANY: "any",
-}
+    from tealer.teal.subroutine import Subroutine
 
 
 class Instruction:  # pylint: disable=too-many-instance-attributes
@@ -70,8 +44,7 @@ def __init__(self) -> None:
         self._tealer_comments: List[str] = []
         self._bb: Optional["BasicBlock"] = None
         self._version: int = 1
-        self._mode: ContractType = ContractType.ANY
-        self._callsub_ins: Optional["Instruction"] = None
+        self._mode: ExecutionMode = ExecutionMode.ANY
 
     def add_prev(self, prev_ins: "Instruction") -> None:
         """Add instruction that may execute just before this instruction.
@@ -108,17 +81,29 @@ def add_next(self, next_ins: "Instruction") -> None:
 
     @property
     def prev(self) -> List["Instruction"]:
-        """List of previous instructions to this instruction."""
+        """List of previous instructions to this instruction.
+
+        Returns:
+            list of previous instructions that might have been executed before this instruction.
+        """
         return self._prev
 
     @property
     def next(self) -> List["Instruction"]:
-        """List of next instructions to this instruction."""
+        """List of next instructions to this instruction.
+
+        Returns:
+            list of next instructions that might be executed after this instruction.
+        """
         return self._next
 
     @property
     def line(self) -> int:
-        """Source code line number of this instruction."""
+        """Source code line number of this instruction.
+
+        Returns:
+            Returns the line number of this instruction in the contract source file.
+        """
         return self._line_num
 
     @line.setter
@@ -135,6 +120,10 @@ def source_code(self) -> str:
             int      4   // Number 4
         ```
         Int(4).source_line == "    int      4   // Number 4"
+
+        Returns:
+            Returns the source line of this instruction along with the indentation
+            and the comments.
         """
         return self._source_code_line
 
@@ -144,7 +133,12 @@ def source_code(self, line: str) -> None:
 
     @property
     def comment(self) -> str:
-        """Teal comment present in the instruction line."""
+        """Teal comment present in the instruction line.
+
+        Returns:
+            Returns the user comment present on the same line (at the end) as this
+            instruction.
+        """
         return self._comment
 
     @comment.setter
@@ -170,6 +164,9 @@ def comments_before_ins(self) -> List[str]:
             ]
 
             Txn(..).comment == "// OnCompletion"
+
+        Returns:
+            Returns the user comments that are above this instruction in the source code.
         """
         return self._comments_before_ins
 
@@ -179,7 +176,11 @@ def comments_before_ins(self, comments: List[str]) -> None:
 
     @property
     def tealer_comments(self) -> List[str]:
-        """Additional comments added by tealer for each basic block in the output CFG."""
+        """Additional comments added by tealer for the instruction in the output CFG.
+
+        Returns:
+            Returns comments added by tealer for the instruction.
+        """
         return self._tealer_comments
 
     @tealer_comments.setter
@@ -187,8 +188,17 @@ def tealer_comments(self, comments: List[str]) -> None:
         self._tealer_comments = comments
 
     @property
-    def bb(self) -> Optional["BasicBlock"]:
-        """Instance of BasicBlock this instruction is part of."""
+    def bb(self) -> "BasicBlock":
+        """Instance of BasicBlock this instruction is part of.
+
+        Returns:
+            The basic block of this instruction
+
+        Raises:
+            TealerException: Raises error if instruction.bb is not initialized during parsing.
+        """
+        if self._bb is None:
+            raise TealerException(f"Instruction.bb is not initialized: {str(self)}")
         return self._bb
 
     @bb.setter
@@ -196,27 +206,12 @@ def bb(self, b: "BasicBlock") -> None:
         self._bb = b
 
     @property
-    def callsub_ins(self) -> Optional["Instruction"]:
-        """if this instruction is a return point to a callsub instruction i.e callsub instruction is
-        present right before this instruction, then callsub_ins returns a reference to that callsub
-        instruction object.
-
-        e.g
-        callsub main
-        int 1
-        return
+    def version(self) -> int:
+        """Teal version this instruction is introduced in and supported from.
 
-        callsub_ins of `int 1` will be instruction obj of `callsub main`.
+        Returns:
+            Returns the Teal version the instruction is supporte from.
         """
-        return self._callsub_ins
-
-    @callsub_ins.setter
-    def callsub_ins(self, ins: "Instruction") -> None:
-        self._callsub_ins = ins
-
-    @property
-    def version(self) -> int:
-        """Teal version this instruction is introduced in and supported from."""
         return self._version
 
     @property
@@ -228,7 +223,7 @@ def stack_push_size(self) -> int:
         return 0
 
     @property
-    def mode(self) -> ContractType:
+    def mode(self) -> ExecutionMode:
         """Type of smart contract this instruction execution is supported in.
 
         Execution of every teal instruction is not supported in all types
@@ -243,6 +238,9 @@ def mode(self) -> ContractType:
         in stateless(signature) contracts, ``STATEFULL`` implies that the instructions
         only work in application contracts and ``ANY`` represents that this instruction
         is supported in both kind of contracts.
+
+        Returns:
+            Returns the execution mode the instruction is supported in.
         """
 
         return self._mode
@@ -255,6 +253,10 @@ def cost(self) -> int:
         will return 1 independent of contract version. Instructions whose cost
         is not 1 or depends on contract version must override the cost property
         and return the correct cost.
+
+        Returns:
+            OpcodeCost of the instruction if the instruction is supported in the
+            contract version. Returns zero if it is not supported.
         """
 
         return 1
@@ -266,6 +268,18 @@ def __repr__(self) -> str:
         return f"<Instruction({self.line}, '{str(self)}')>"
 
 
+class TealerCustomErrInstruction(Instruction):
+    """Instruction used to replace basic blocks that are not part of function.
+
+    The semantics of the instruction are equal to Err instruction.
+    """
+
+    # TODO: Add custom values for line num, source_code, tealer_comments.
+
+    def __str__(self) -> str:
+        return "TealerCustomErrInstruction"
+
+
 class UnsupportedInstruction(Instruction):
     """
     Instruction that is not supported by Tealer.
@@ -283,7 +297,11 @@ def __str__(self) -> str:
 
     @property
     def verbatim_line(self) -> str:
-        """unsupported instruction as read from input"""
+        """unsupported instruction as read from input
+
+        Returns:
+            Returns the source line for this instruction as it is.
+        """
         return self._verbatim_line
 
 
@@ -308,7 +326,11 @@ def __str__(self) -> str:
 
     @property
     def program_version(self) -> int:
-        """version number of teal program"""
+        """version number of teal program
+
+        Returns:
+            version argument of the instruction. Specifies the contract version.
+        """
         return self._program_version
 
 
@@ -328,7 +350,6 @@ class Assert(Instruction):
 
     Errors:
         creates error and fails immediately if X is not a non-zero number.
-
     """
 
     def __init__(self) -> None:
@@ -353,7 +374,6 @@ class Int(Instruction):
 
     Pushes:
         (uint64): x if x is uint64 else value corresponding to the given named constant
-
     """
 
     def __init__(self, value: Union[str, int]):
@@ -362,7 +382,11 @@ def __init__(self, value: Union[str, int]):
 
     @property
     def value(self) -> Union[str, int]:
-        """Immediate value of int instruction."""
+        """Immediate value of int instruction.
+
+        Returns:
+            immediate argument of the instruction.
+        """
         return self._value
 
     def __str__(self) -> str:
@@ -401,7 +425,11 @@ def __init__(self, value: Union[str, int]):
 
     @property
     def value(self) -> Union[str, int]:
-        """Immediate value of pushint instruction."""
+        """Immediate value of pushint instruction.
+
+        Returns:
+            immediate argument of the instruction.
+        """
         return self._value
 
     def __str__(self) -> str:
@@ -430,7 +458,11 @@ def __init__(self, field: TransactionField):
 
     @property
     def field(self) -> TransactionField:
-        """Transaction field being accessed using the txn instruction."""
+        """Transaction field being accessed using the txn instruction.
+
+        Returns:
+            Returns the transaction field accessed by this instruction.
+        """
         return self._field
 
     @property
@@ -464,7 +496,11 @@ def __init__(self, field: TransactionField):
 
     @property
     def field(self) -> TransactionField:
-        """Transaction field being accessed."""
+        """Transaction field being accessed.
+
+        Returns:
+            Returns the transaction field accessed by this instruction.
+        """
         return self._field
 
     def __str__(self) -> str:
@@ -494,12 +530,20 @@ def __init__(self, idx: int, field: TransactionField):
 
     @property
     def idx(self) -> int:
-        """Index of the transaction in the atomic group"""
+        """Index of the transaction in the atomic group
+
+        Returns:
+            index of the transaction whose field is being accessed.
+        """
         return self._idx
 
     @property
     def field(self) -> TransactionField:
-        """Transaction field of the instruction being accessed."""
+        """Transaction field of the instruction being accessed.
+
+        Returns:
+            Returns the transaction field accessed by this instruction.
+        """
         return self._field
 
     def __str__(self) -> str:
@@ -535,12 +579,20 @@ def __init__(self, idx: int, field: TransactionField):
 
     @property
     def idx(self) -> int:
-        """Index of the transaction in the atomic group."""
+        """Index of the transaction in the atomic group.
+
+        Returns:
+            index of the transaction whose field is being accessed.
+        """
         return self._idx
 
     @property
     def field(self) -> TransactionField:
-        """Array transaction field being accessed."""
+        """Array transaction field being accessed.
+
+        Returns:
+            Returns the transaction field accessed by this instruction.
+        """
         return self._field
 
     def __str__(self) -> str:
@@ -572,7 +624,11 @@ def __init__(self, field: TransactionField):
 
     @property
     def field(self) -> TransactionField:
-        """Transaction field being accessed."""
+        """Transaction field being accessed.
+
+        Returns:
+            Returns the transaction field accessed by this instruction.
+        """
         return self._field
 
     def __str__(self) -> str:
@@ -609,7 +665,11 @@ def __init__(self, field: TransactionField):
 
     @property
     def field(self) -> TransactionField:
-        """Array transaction field being accessed."""
+        """Array transaction field being accessed.
+
+        Returns:
+            Returns the transaction field accessed by this instruction.
+        """
         return self._field
 
     def __str__(self) -> str:
@@ -691,7 +751,7 @@ def __init__(self, idx: int, slot: int):
         self._idx = idx
         self._slot = slot
         self._version: int = 4
-        self._mode: ContractType = ContractType.STATEFULL
+        self._mode = ExecutionMode.STATEFUL
 
     def __str__(self) -> str:
         return f"gload {self._idx} {self._slot}"
@@ -723,7 +783,7 @@ def __init__(self, slot: int):
         super().__init__()
         self._slot = slot
         self._version: int = 4
-        self._mode: ContractType = ContractType.STATEFULL
+        self._mode = ExecutionMode.STATEFUL
 
     def __str__(self) -> str:
         return f"gloads {self._slot}"
@@ -755,7 +815,7 @@ def __init__(self, idx: int):
         super().__init__()
         self._idx = idx
         self._version: int = 4
-        self._mode: ContractType = ContractType.STATEFULL
+        self._mode = ExecutionMode.STATEFUL
 
     def __str__(self) -> str:
         return f"gaid {self._idx}"
@@ -782,7 +842,7 @@ class Gaids(Instruction):
     def __init__(self) -> None:
         super().__init__()
         self._version: int = 4
-        self._mode: ContractType = ContractType.STATEFULL
+        self._mode = ExecutionMode.STATEFUL
 
     @property
     def stack_pop_size(self) -> int:
@@ -1018,12 +1078,20 @@ def __init__(self, idx: int, idy: int) -> None:
 
     @property
     def start_position(self) -> int:
-        """Starting position of the substring in the bytearray."""
+        """Starting position of the substring in the bytearray.
+
+        Returns:
+            Returns "start_position" immediate argument of the instruction
+        """
         return self._idx
 
     @property
     def length(self) -> int:
-        """Length of the bytearray to extract."""
+        """Length of the bytearray to extract.
+
+        Returns:
+            Returns "length" immediate argument of the instruction
+        """
         return self._idy
 
     def __str__(self) -> str:
@@ -1168,7 +1236,16 @@ class Sha256(Instruction):
 
     @property
     def cost(self) -> int:
-        """cost of executing sha256 instruction."""
+        """cost of executing sha256 instruction.
+
+        Returns:
+            OpcodeCost of the instruction if the instruction is supported in the
+            contract version. Returns zero if it is not supported.
+
+        Raises:
+            ValueError: Raises value error if self.bb or self.bb.teal are not initialized
+                properly.
+        """
 
         if self.bb and self.bb.teal:
             contract_version = self.bb.teal.version
@@ -1207,7 +1284,16 @@ class Sha512_256(Instruction):
 
     @property
     def cost(self) -> int:
-        """cost of executing sha512_256 instruction."""
+        """cost of executing sha512_256 instruction.
+
+        Returns:
+            OpcodeCost of the instruction if the instruction is supported in the
+            contract version. Returns zero if it is not supported.
+
+        Raises:
+            ValueError: Raises value error if self.bb or self.bb.teal are not initialized
+                properly.
+        """
 
         if self.bb and self.bb.teal:
             contract_version = self.bb.teal.version
@@ -1246,7 +1332,16 @@ class Keccak256(Instruction):
 
     @property
     def cost(self) -> int:
-        """cost of executing keccak256 instruction."""
+        """cost of executing keccak256 instruction.
+
+        Returns:
+            OpcodeCost of the instruction if the instruction is supported in the
+            contract version. Returns zero if it is not supported.
+
+        Raises:
+            ValueError: Raises value error if self.bb or self.bb.teal are not initialized
+                properly.
+        """
 
         if self.bb and self.bb.teal:
             contract_version = self.bb.teal.version
@@ -1287,7 +1382,12 @@ class Ed25519verify(Instruction):
 
     @property
     def cost(self) -> int:
-        """cost of executing ed25519verify instruction."""
+        """cost of executing ed25519verify instruction.
+
+        Returns:
+            OpcodeCost of the instruction if the instruction is supported in the
+            contract version. Returns zero if it is not supported.
+        """
         return 1900
 
     @property
@@ -1335,6 +1435,14 @@ def cost(self) -> int:
         overrides cost property. ecdsa_verify instruction is introduced in
         Teal version 5. if the cost property is accessed for contracts with
         lesser version, value 0 is returned instead of raising an error.
+
+        Returns:
+            OpcodeCost of the instruction if the instruction is supported in the
+            contract version. Returns zero if it is not supported.
+
+        Raises:
+            ValueError: Raises value error if self.bb or self.bb.teal are not initialized
+                properly.
         """
 
         if self.bb and self.bb.teal:
@@ -1391,6 +1499,14 @@ def cost(self) -> int:
         overrides cost property. ecdsa_pk_decompress instruction is introduced in
         Teal version 5. if the cost property is accessed for contracts with
         lesser version, value 0 is returned instead of raising an error.
+
+        Returns:
+            OpcodeCost of the instruction if the instruction is supported in the
+            contract version. Returns zero if it is not supported.
+
+        Raises:
+            ValueError: Raises value error if self.bb or self.bb.teal are not initialized
+                properly.
         """
 
         if self.bb and self.bb.teal:
@@ -1447,6 +1563,14 @@ def cost(self) -> int:
         overrides cost property. ecdsa_pk_recover instruction is introduced in
         Teal version 5. if the cost property is accessed for contracts with
         lesser version, value 0 is returned instead of raising an error.
+
+        Returns:
+            OpcodeCost of the instruction if the instruction is supported in the
+            contract version. Returns zero if it is not supported.
+
+        Raises:
+            ValueError: Raises value error if self.bb or self.bb.teal are not initialized
+                properly.
         """
 
         if self.bb and self.bb.teal:
@@ -1490,7 +1614,11 @@ def __init__(self, field: GlobalField):
 
     @property
     def field(self) -> GlobalField:
-        """Global field being accessed."""
+        """Global field being accessed.
+
+        Returns:
+            Returns the global field accessed by this instruction.
+        """
         return self._field
 
     @property
@@ -1655,7 +1783,11 @@ def __init__(self, label: str):
 
     @property
     def label(self) -> str:
-        """String representing the label."""
+        """String representing the label.
+
+        Returns:
+            Returns the label.
+        """
         return self._label
 
 
@@ -1736,29 +1868,28 @@ class Callsub(InstructionWithLabel):
 
     def __init__(self, label: str):
         super().__init__(label)
-        self._return_point: Optional[Instruction] = None
         self._version: int = 4
+        self._called_subroutine: Optional["Subroutine"] = None
 
     @property
-    def return_point(self) -> Optional[Instruction]:
-        """Return point of this call instruction.
+    def called_subroutine(self) -> "Subroutine":
+        """Return the subroutine object called by this instruction
+
+        Returns:
+            The subroutine object called by this instruction.
 
-        Execution returns to the next instruction of the call instruction
-        after executing the called subroutine. This property returns instance
-        of that instruction. This is helpful in construction of contract CFG and
-        subroutine CFGs.
+        Raises:
+            TealerException: raises error if _called_subroutine is also set during parsing.
         """
-        # TODO: raise Error if return_point is None
-        # if self._return_point is None:
-        # return point is accessed before assignment
-        # raise TealerException(f"Callsub return point is accessed before assignment: {str(self)}")
-        return self._return_point
+        if self._called_subroutine is None:
+            raise TealerException(
+                f"callsub.called_subroutine is accessed before assignment: {str(self)}"
+            )
+        return self._called_subroutine
 
-    @return_point.setter
-    def return_point(self, ins: Instruction) -> None:
-        if self._return_point is not None:
-            raise ValueError("Return point already set")
-        self._return_point = ins
+    @called_subroutine.setter
+    def called_subroutine(self, subroutine: "Subroutine") -> None:
+        self._called_subroutine = subroutine
 
     def __str__(self) -> str:
         return f"callsub {self._label}"
@@ -1804,7 +1935,7 @@ class AppGlobalGet(Instruction):
     def __init__(self) -> None:
         super().__init__()
         self._version: int = 2
-        self._mode: ContractType = ContractType.STATEFULL
+        self._mode = ExecutionMode.STATEFUL
 
     def __str__(self) -> str:
         return "app_global_get"
@@ -1839,7 +1970,7 @@ class AppGlobalGetEx(Instruction):
     def __init__(self) -> None:
         super().__init__()
         self._version: int = 2
-        self._mode: ContractType = ContractType.STATEFULL
+        self._mode = ExecutionMode.STATEFUL
 
     def __str__(self) -> str:
         return "app_global_get_ex"
@@ -1867,7 +1998,7 @@ class AppGlobalPut(Instruction):
     def __init__(self) -> None:
         super().__init__()
         self._version: int = 2
-        self._mode: ContractType = ContractType.STATEFULL
+        self._mode = ExecutionMode.STATEFUL
 
     def __str__(self) -> str:
         return "app_global_put"
@@ -1892,7 +2023,7 @@ class AppGlobalDel(Instruction):
     def __init__(self) -> None:
         super().__init__()
         self._version: int = 2
-        self._mode: ContractType = ContractType.STATEFULL
+        self._mode = ExecutionMode.STATEFUL
 
     def __str__(self) -> str:
         return "app_global_del"
@@ -1926,7 +2057,7 @@ class AppLocalGetEx(Instruction):
     def __init__(self) -> None:
         super().__init__()
         self._version: int = 2
-        self._mode: ContractType = ContractType.STATEFULL
+        self._mode = ExecutionMode.STATEFUL
 
     def __str__(self) -> str:
         return "app_local_get_ex"
@@ -1960,7 +2091,7 @@ class AppLocalGet(Instruction):
     def __init__(self) -> None:
         super().__init__()
         self._version: int = 2
-        self._mode: ContractType = ContractType.STATEFULL
+        self._mode = ExecutionMode.STATEFUL
 
     def __str__(self) -> str:
         return "app_local_get"
@@ -1993,7 +2124,7 @@ class AppLocalPut(Instruction):
     def __init__(self) -> None:
         super().__init__()
         self._version: int = 2
-        self._mode: ContractType = ContractType.STATEFULL
+        self._mode = ExecutionMode.STATEFUL
 
     def __str__(self) -> str:
         return "app_local_put"
@@ -2021,7 +2152,7 @@ class AppLocalDel(Instruction):
     def __init__(self) -> None:
         super().__init__()
         self._version: int = 2
-        self._mode: ContractType = ContractType.STATEFULL
+        self._mode = ExecutionMode.STATEFUL
 
     def __str__(self) -> str:
         return "app_local_del"
@@ -2057,11 +2188,15 @@ def __init__(self, field: AssetHoldingField):
         super().__init__()
         self._field: AssetHoldingField = field
         self._version: int = 2
-        self._mode: ContractType = ContractType.STATEFULL
+        self._mode = ExecutionMode.STATEFUL
 
     @property
     def field(self) -> AssetHoldingField:
-        """Asset holding field being accessed."""
+        """Asset holding field being accessed.
+
+        Returns:
+            Returns the asset holding field accessed by this instruction.
+        """
         return self._field
 
     def __str__(self) -> str:
@@ -2099,11 +2234,15 @@ def __init__(self, field: AssetParamsField):
         super().__init__()
         self._field: AssetParamsField = field
         self._version: int = 2
-        self._mode: ContractType = ContractType.STATEFULL
+        self._mode = ExecutionMode.STATEFUL
 
     @property
     def field(self) -> AssetParamsField:
-        """Asset Parameter field being accessed."""
+        """Asset Parameter field being accessed.
+
+        Returns:
+            Returns the asset parameter field accessed by this instruction.
+        """
         return self._field
 
     def __str__(self) -> str:
@@ -2141,11 +2280,15 @@ def __init__(self, field: AppParamsField):
         super().__init__()
         self._field: AppParamsField = field
         self._version: int = 5
-        self._mode: ContractType = ContractType.STATEFULL
+        self._mode = ExecutionMode.STATEFUL
 
     @property
     def field(self) -> AppParamsField:
-        """Application parameter field being accessed."""
+        """Application parameter field being accessed.
+
+        Returns:
+            Returns the application parameter field accessed by this instruction.
+        """
         return self._field
 
     def __str__(self) -> str:
@@ -2181,7 +2324,7 @@ class AppOptedIn(Instruction):
     def __init__(self) -> None:
         super().__init__()
         self._version: int = 2
-        self._mode: ContractType = ContractType.STATEFULL
+        self._mode = ExecutionMode.STATEFUL
 
     def __str__(self) -> str:
         return "app_opted_in"
@@ -2215,7 +2358,7 @@ class Balance(Instruction):
     def __init__(self) -> None:
         super().__init__()
         self._version: int = 2
-        self._mode: ContractType = ContractType.STATEFULL
+        self._mode = ExecutionMode.STATEFUL
 
     @property
     def stack_pop_size(self) -> int:
@@ -2244,7 +2387,7 @@ class MinBalance(Instruction):
     def __init__(self) -> None:
         super().__init__()
         self._version: int = 3
-        self._mode: ContractType = ContractType.STATEFULL
+        self._mode = ExecutionMode.STATEFUL
 
     def __str__(self) -> str:
         return "min_balance"
@@ -2831,6 +2974,14 @@ def cost(self) -> int:
         overrides cost property. b% instruction is introduced in Teal version 4.
         if the cost property is accessed for contracts with lesser version,
         value 0 is returned instead of raising an error.
+
+        Returns:
+            OpcodeCost of the instruction if the instruction is supported in the
+            contract version. Returns zero if it is not supported.
+
+        Raises:
+            ValueError: Raises value error if self.bb or self.bb.teal are not initialized
+                properly.
         """
 
         if self.bb and self.bb.teal:
@@ -2941,6 +3092,14 @@ def cost(self) -> int:
         overrides cost property. b& instruction is introduced in Teal version 4.
         if the cost property is accessed for contracts with lesser version,
         value 0 is returned instead of raising an error.
+
+        Returns:
+            OpcodeCost of the instruction if the instruction is supported in the
+            contract version. Returns zero if it is not supported.
+
+        Raises:
+            ValueError: Raises value error if self.bb or self.bb.teal are not initialized
+                properly.
         """
 
         if self.bb and self.bb.teal:
@@ -2991,6 +3150,14 @@ def cost(self) -> int:
         overrides cost property. b| instruction is introduced in Teal version 4.
         if the cost property is accessed for contracts with lesser version,
         value 0 is returned instead of raising an error.
+
+        Returns:
+            OpcodeCost of the instruction if the instruction is supported in the
+            contract version. Returns zero if it is not supported.
+
+        Raises:
+            ValueError: Raises value error if self.bb or self.bb.teal are not initialized
+                properly.
         """
 
         if self.bb and self.bb.teal:
@@ -3041,6 +3208,14 @@ def cost(self) -> int:
         overrides cost property. b+ instruction is introduced in Teal version 4.
         if the cost property is accessed for contracts with lesser version,
         value 0 is returned instead of raising an error.
+
+        Returns:
+            OpcodeCost of the instruction if the instruction is supported in the
+            contract version. Returns zero if it is not supported.
+
+        Raises:
+            ValueError: Raises value error if self.bb or self.bb.teal are not initialized
+                properly.
         """
 
         if self.bb and self.bb.teal:
@@ -3094,6 +3269,14 @@ def cost(self) -> int:
         overrides cost property. b- instruction is introduced in Teal version 4.
         if the cost property is accessed for contracts with lesser version,
         value 0 is returned instead of raising an error.
+
+        Returns:
+            OpcodeCost of the instruction if the instruction is supported in the
+            contract version. Returns zero if it is not supported.
+
+        Raises:
+            ValueError: Raises value error if self.bb or self.bb.teal are not initialized
+                properly.
         """
 
         if self.bb and self.bb.teal:
@@ -3147,6 +3330,14 @@ def cost(self) -> int:
         overrides cost property. b/ instruction is introduced in Teal version 4.
         if the cost property is accessed for contracts with lesser version,
         value 0 is returned instead of raising an error.
+
+        Returns:
+            OpcodeCost of the instruction if the instruction is supported in the
+            contract version. Returns zero if it is not supported.
+
+        Raises:
+            ValueError: Raises value error if self.bb or self.bb.teal are not initialized
+                properly.
         """
 
         if self.bb and self.bb.teal:
@@ -3197,6 +3388,14 @@ def cost(self) -> int:
         overrides cost property. b* instruction is introduced in Teal version 4.
         if the cost property is accessed for contracts with lesser version,
         value 0 is returned instead of raising an error.
+
+        Returns:
+            OpcodeCost of the instruction if the instruction is supported in the
+            contract version. Returns zero if it is not supported.
+
+        Raises:
+            ValueError: Raises value error if self.bb or self.bb.teal are not initialized
+                properly.
         """
 
         if self.bb and self.bb.teal:
@@ -3367,6 +3566,14 @@ def cost(self) -> int:
         overrides cost property. b^ instruction is introduced in Teal version 4.
         if the cost property is accessed for contracts with lesser version,
         value 0 is returned instead of raising an error.
+
+        Returns:
+            OpcodeCost of the instruction if the instruction is supported in the
+            contract version. Returns zero if it is not supported.
+
+        Raises:
+            ValueError: Raises value error if self.bb or self.bb.teal are not initialized
+                properly.
         """
 
         if self.bb and self.bb.teal:
@@ -3418,6 +3625,14 @@ def cost(self) -> int:
         overrides cost property. b~ instruction is introduced in Teal version 4.
         if the cost property is accessed for contracts with lesser version,
         value 0 is returned instead of raising an error.
+
+        Returns:
+            OpcodeCost of the instruction if the instruction is supported in the
+            contract version. Returns zero if it is not supported.
+
+        Raises:
+            ValueError: Raises value error if self.bb or self.bb.teal are not initialized
+                properly.
         """
 
         if self.bb and self.bb.teal:
@@ -3479,7 +3694,7 @@ class Log(Instruction):
     def __init__(self) -> None:
         super().__init__()
         self._version: int = 5
-        self._mode: ContractType = ContractType.STATEFULL
+        self._mode = ExecutionMode.STATEFUL
 
     @property
     def stack_pop_size(self) -> int:
@@ -3499,7 +3714,7 @@ class Itxn_begin(Instruction):
     def __init__(self) -> None:
         super().__init__()
         self._version: int = 5
-        self._mode: ContractType = ContractType.STATEFULL
+        self._mode = ExecutionMode.STATEFUL
 
 
 class Itxn_field(Instruction):
@@ -3523,11 +3738,15 @@ def __init__(self, field: TransactionField):
         super().__init__()
         self._field: TransactionField = field
         self._version: int = 5
-        self._mode: ContractType = ContractType.STATEFULL
+        self._mode = ExecutionMode.STATEFUL
 
     @property
     def field(self) -> TransactionField:
-        """Transaction field of inner transaction being set."""
+        """Transaction field of inner transaction being set.
+
+        Returns:
+            Returns the inner transaction field being set by this instruction.
+        """
         return self._field
 
     def __str__(self) -> str:
@@ -3554,7 +3773,7 @@ class Itxn_submit(Instruction):
     def __init__(self) -> None:
         super().__init__()
         self._version: int = 5
-        self._mode: ContractType = ContractType.STATEFULL
+        self._mode = ExecutionMode.STATEFUL
 
 
 class Itxn(Instruction):
@@ -3573,11 +3792,15 @@ def __init__(self, field: TransactionField):
         super().__init__()
         self._field: TransactionField = field
         self._version: int = 5
-        self._mode: ContractType = ContractType.STATEFULL
+        self._mode = ExecutionMode.STATEFUL
 
     @property
     def field(self) -> TransactionField:
-        """Transaction field being accessed."""
+        """Transaction field being accessed.
+
+        Returns:
+            Returns the transaction field accessed by this instruction.
+        """
         return self._field
 
     def __str__(self) -> str:
@@ -3612,11 +3835,15 @@ def __init__(self, field: TransactionField):
         super().__init__()
         self._field: TransactionField = field
         self._version: int = 5
-        self._mode: ContractType = ContractType.STATEFULL
+        self._mode = ExecutionMode.STATEFUL
 
     @property
     def field(self) -> TransactionField:
-        """Array transaction field being accessed."""
+        """Array transaction field being accessed.
+
+        Returns:
+            Returns the transaction field accessed by this instruction.
+        """
         return self._field
 
     def __str__(self) -> str:
@@ -3648,7 +3875,11 @@ def __init__(self, field: TransactionField):
 
     @property
     def field(self) -> TransactionField:
-        """Array transaction field being accessed."""
+        """Array transaction field being accessed.
+
+        Returns:
+            Returns the transaction field accessed by this instruction.
+        """
         return self._field
 
     def __str__(self) -> str:
@@ -3690,12 +3921,21 @@ def __init__(self, idx: int, field: TransactionField):
 
     @property
     def idx(self) -> int:
-        """Index into array of array transaction field."""
+        """Index into array of array transaction field.
+
+        Returns:
+            This instruction can access only one value of the arrary type field at a time.
+            idx returns the index into the field values array.
+        """
         return self._idx
 
     @property
     def field(self) -> TransactionField:
-        """Transaction field being accessed."""
+        """Transaction field being accessed.
+
+        Returns:
+            Returns the transaction field accessed by this instruction.
+        """
         return self._field
 
     def __str__(self) -> str:
@@ -3736,7 +3976,11 @@ def __init__(self, field: TransactionField):
 
     @property
     def field(self) -> TransactionField:
-        """Transaction field being accessed."""
+        """Transaction field being accessed.
+
+        Returns:
+            Returns the transaction field accessed by this instruction.
+        """
         return self._field
 
     def __str__(self) -> str:
@@ -3765,7 +4009,7 @@ class Args(Instruction):
     def __init__(self) -> None:
         super().__init__()
         self._version: int = 5
-        self._mode: ContractType = ContractType.STATELESS
+        self._mode = ExecutionMode.STATELESS
 
     @property
     def stack_pop_size(self) -> int:
@@ -3855,6 +4099,14 @@ def cost(self) -> int:
         overrides cost property. divmodw instruction is introduced in Teal version 4.
         if the cost property is accessed for contracts with lesser version, value 0
         is returned instead of raising an error.
+
+        Returns:
+            OpcodeCost of the instruction if the instruction is supported in the
+            contract version. Returns zero if it is not supported.
+
+        Raises:
+            ValueError: Raises value error if self.bb or self.bb.teal are not initialized
+                properly.
         """
 
         if self.bb and self.bb.teal:
@@ -3932,6 +4184,14 @@ def cost(self) -> int:
         overrides cost property. expw instruction is introduced in Teal version 4.
         if the cost property is accessed for contracts with lesser version, value 0
         is returned instead of raising an error.
+
+        Returns:
+            OpcodeCost of the instruction if the instruction is supported in the
+            contract version. Returns zero if it is not supported.
+
+        Raises:
+            ValueError: Raises value error if self.bb or self.bb.teal are not initialized
+                properly.
         """
 
         if self.bb and self.bb.teal:
@@ -4026,6 +4286,14 @@ def cost(self) -> int:
         overrides cost property. sqrt instruction is introduced in Teal version 4.
         if the cost property is accessed for contracts with lesser version, value 0
         is returned instead of raising an error.
+
+        Returns:
+            OpcodeCost of the instruction if the instruction is supported in the
+            contract version. Returns zero if it is not supported.
+
+        Raises:
+            ValueError: Raises value error if self.bb or self.bb.teal are not initialized
+                properly.
         """
 
         if self.bb and self.bb.teal:
@@ -4285,7 +4553,7 @@ class Arg(Instruction):
     def __init__(self, idx: int):
         super().__init__()
         self._idx = idx
-        self._mode: ContractType = ContractType.STATELESS
+        self._mode = ExecutionMode.STATELESS
 
     def __str__(self) -> str:
         return f"arg {self._idx}"
@@ -4305,7 +4573,7 @@ class Arg0(Instruction):
 
     def __init__(self) -> None:
         super().__init__()
-        self._mode: ContractType = ContractType.STATELESS
+        self._mode = ExecutionMode.STATELESS
 
     def __str__(self) -> str:
         return "arg_0"
@@ -4325,7 +4593,7 @@ class Arg1(Instruction):
 
     def __init__(self) -> None:
         super().__init__()
-        self._mode: ContractType = ContractType.STATELESS
+        self._mode = ExecutionMode.STATELESS
 
     def __str__(self) -> str:
         return "arg_1"
@@ -4345,7 +4613,7 @@ class Arg2(Instruction):
 
     def __init__(self) -> None:
         super().__init__()
-        self._mode: ContractType = ContractType.STATELESS
+        self._mode = ExecutionMode.STATELESS
 
     def __str__(self) -> str:
         return "arg_2"
@@ -4365,7 +4633,7 @@ class Arg3(Instruction):
 
     def __init__(self) -> None:
         super().__init__()
-        self._mode: ContractType = ContractType.STATELESS
+        self._mode = ExecutionMode.STATELESS
 
     def __str__(self) -> str:
         return "arg_3"
@@ -4568,11 +4836,15 @@ def __init__(self, field: AcctParamsField):
         super().__init__()
         self._field: AcctParamsField = field
         self._version: int = 6
-        self._mode: ContractType = ContractType.STATEFULL
+        self._mode = ExecutionMode.STATEFUL
 
     @property
     def field(self) -> AcctParamsField:
-        """Account parameter field being accessed."""
+        """Account parameter field being accessed.
+
+        Returns:
+            Returns the transaction field accessed by this instruction.
+        """
         return self._field
 
     def __str__(self) -> str:
@@ -4620,6 +4892,14 @@ def cost(self) -> int:
         overrides cost property. bsqrt instruction is introduced in Teal version 6.
         if the cost property is accessed for contracts with lesser version,
         value 0 is returned instead of raising an error.
+
+        Returns:
+            OpcodeCost of the instruction if the instruction is supported in the
+            contract version. Returns zero if it is not supported.
+
+        Raises:
+            ValueError: Raises value error if self.bb or self.bb.teal are not initialized
+                properly.
         """
 
         if self.bb and self.bb.teal:
@@ -4647,7 +4927,7 @@ class Itxn_next(Instruction):
     def __init__(self) -> None:
         super().__init__()
         self._version: int = 6
-        self._mode: ContractType = ContractType.STATEFULL
+        self._mode = ExecutionMode.STATEFUL
 
 
 class Divw(Instruction):
@@ -4696,16 +4976,25 @@ def __init__(self, idx: int, field: TransactionField):
         self._idx = idx
         self._field: TransactionField = field
         self._version = 6
-        self._mode: ContractType = ContractType.STATEFULL
+        self._mode = ExecutionMode.STATEFUL
 
     @property
     def idx(self) -> int:
-        """Index of the transaction in the last inner group"""
+        """Index of the transaction in the last inner group
+
+        Returns:
+            index of the transaction whose field is being accessed. index is the index into
+            the last inner group transaction.
+        """
         return self._idx
 
     @property
     def field(self) -> TransactionField:
-        """Transaction field of the instruction being accessed."""
+        """Transaction field of the instruction being accessed.
+
+        Returns:
+            Returns the transaction field accessed by this instruction.
+        """
         return self._field
 
     @property
@@ -4738,16 +5027,25 @@ def __init__(self, idx: int, field: TransactionField):
         self._idx = idx
         self._field: TransactionField = field
         self._version: int = 6
-        self._mode: ContractType = ContractType.STATEFULL
+        self._mode = ExecutionMode.STATEFUL
 
     @property
     def idx(self) -> int:
-        """Index of the transaction in the last inner transaction group."""
+        """Index of the transaction in the last inner transaction group.
+
+        Returns:
+            index of the transaction whose field is being accessed. index is the index into
+            the last inner group transaction.
+        """
         return self._idx
 
     @property
     def field(self) -> TransactionField:
-        """Array transaction field being accessed."""
+        """Array transaction field being accessed.
+
+        Returns:
+            Returns the transaction field accessed by this instruction.
+        """
         return self._field
 
     @property
@@ -4785,7 +5083,7 @@ def stack_push_size(self) -> int:
     def __init__(self) -> None:
         super().__init__()
         self._version: int = 6
-        self._mode: ContractType = ContractType.STATEFULL
+        self._mode = ExecutionMode.STATEFUL
 
 
 class Itxnas(Instruction):
@@ -4810,11 +5108,15 @@ def __init__(self, field: TransactionField):
         super().__init__()
         self._field: TransactionField = field
         self._version: int = 6
-        self._mode: ContractType = ContractType.STATEFULL
+        self._mode = ExecutionMode.STATEFUL
 
     @property
     def field(self) -> TransactionField:
-        """Array transaction field being accessed."""
+        """Array transaction field being accessed.
+
+        Returns:
+            Returns the transaction field accessed by this instruction.
+        """
         return self._field
 
     @property
@@ -4845,7 +5147,6 @@ class Gitxnas(Instruction):
 
     Pushes:
         pushes value at index X of array transaction field f of transaction t of the last inner transaction group.
-
     """
 
     def __init__(self, idx: int, field: TransactionField):
@@ -4853,16 +5154,25 @@ def __init__(self, idx: int, field: TransactionField):
         self._idx: int = idx
         self._field: TransactionField = field
         self._version: int = 6
-        self._mode: ContractType = ContractType.STATEFULL
+        self._mode = ExecutionMode.STATEFUL
 
     @property
     def idx(self) -> int:
-        """Index into array of array transaction field."""
+        """Index into array of array transaction field.
+
+        Returns:
+            This instruction can access only one value of the arrary type field at a time.
+            idx returns the index into the field values array.
+        """
         return self._idx
 
     @property
     def field(self) -> TransactionField:
-        """Transaction field being accessed."""
+        """Transaction field being accessed.
+
+        Returns:
+            Returns the transaction field accessed by this instruction.
+        """
         return self._field
 
     @property
@@ -4925,7 +5235,11 @@ def __init__(self, idx: int) -> None:
 
     @property
     def start_position(self) -> int:
-        """Replacement index."""
+        """Replacement index.
+
+        Returns:
+            Starting position for the replacement.
+        """
         return self._idx
 
     @property
@@ -5007,7 +5321,15 @@ def is_replace3(self) -> bool:
 
     @property
     def start_position(self) -> int:
-        """Replacement index."""
+        """Replacement index.
+
+        Returns:
+            Start position for replacing.
+
+        Raises:
+            TealerException: Raises error if start position is not given as an immediate
+                argument and is fetched from the stack at runtime.
+        """
         if self._idx is None:
             raise TealerException("replace instruction does not have any immediates")
         return self._idx
@@ -5054,6 +5376,14 @@ def cost(self) -> int:
         """cost of executing base64_decode instruction.
 
         base cost is 1 and cost increases by 1 per 16 bytes of A. returns base cost of 1.
+
+        Returns:
+            OpcodeCost of the instruction if the instruction is supported in the
+            contract version. Returns zero if it is not supported.
+
+        Raises:
+            ValueError: Raises value error if self.bb or self.bb.teal are not initialized
+                properly.
         """
 
         if self.bb and self.bb.teal:
@@ -5106,6 +5436,14 @@ def cost(self) -> int:
         """cost of executing json_ref instruction.
 
         base cost is 25 and cost increases by 2 per 7 bytes of A. returns base cost of 25.
+
+        Returns:
+            OpcodeCost of the instruction if the instruction is supported in the
+            contract version. Returns zero if it is not supported.
+
+        Raises:
+            ValueError: Raises value error if self.bb or self.bb.teal are not initialized
+                properly.
         """
 
         if self.bb and self.bb.teal:
@@ -5152,6 +5490,14 @@ def cost(self) -> int:
         """cost of executing json_ref instruction.
 
         base cost is 25 and cost increases by 2 per 7 bytes of A. returns base cost of 1.
+
+        Returns:
+            OpcodeCost of the instruction if the instruction is supported in the
+            contract version. Returns zero if it is not supported.
+
+        Raises:
+            ValueError: Raises value error if self.bb or self.bb.teal are not initialized
+                properly.
         """
 
         if self.bb and self.bb.teal:
@@ -5225,6 +5571,14 @@ def cost(self) -> int:
         overrides cost property. vrf_verify instruction is introduced in
         Teal version 7. if the cost property is accessed for contracts with
         lesser version, value 0 is returned instead of raising an error.
+
+        Returns:
+            OpcodeCost of the instruction if the instruction is supported in the
+            contract version. Returns zero if it is not supported.
+
+        Raises:
+            ValueError: Raises value error if self.bb or self.bb.teal are not initialized
+                properly.
         """
 
         if self.bb and self.bb.teal:
@@ -5273,7 +5627,11 @@ def __init__(self, field: str):
 
     @property
     def field(self) -> str:
-        """block field"""
+        """block field
+
+        Returns:
+            block field being accessed by this instruction.
+        """
         return self._field
 
     @property
@@ -5588,7 +5946,7 @@ class BoxCreate(Instruction):
     def __init__(self) -> None:
         super().__init__()
         self._version: int = 8
-        self._mode: ContractType = ContractType.STATEFULL
+        self._mode = ExecutionMode.STATEFUL
 
     @property
     def stack_pop_size(self) -> int:
@@ -5614,7 +5972,7 @@ class BoxExtract(Instruction):
     def __init__(self) -> None:
         super().__init__()
         self._version: int = 8
-        self._mode: ContractType = ContractType.STATEFULL
+        self._mode = ExecutionMode.STATEFUL
 
     @property
     def stack_pop_size(self) -> int:
@@ -5640,7 +5998,7 @@ class BoxReplace(Instruction):
     def __init__(self) -> None:
         super().__init__()
         self._version: int = 8
-        self._mode: ContractType = ContractType.STATEFULL
+        self._mode = ExecutionMode.STATEFUL
 
     @property
     def stack_pop_size(self) -> int:
@@ -5661,7 +6019,7 @@ class BoxDel(Instruction):
     def __init__(self) -> None:
         super().__init__()
         self._version: int = 8
-        self._mode: ContractType = ContractType.STATEFULL
+        self._mode = ExecutionMode.STATEFUL
 
     @property
     def stack_pop_size(self) -> int:
@@ -5686,7 +6044,7 @@ class BoxLen(Instruction):
     def __init__(self) -> None:
         super().__init__()
         self._version: int = 8
-        self._mode: ContractType = ContractType.STATEFULL
+        self._mode = ExecutionMode.STATEFUL
 
     @property
     def stack_pop_size(self) -> int:
@@ -5712,7 +6070,7 @@ class BoxGet(Instruction):
     def __init__(self) -> None:
         super().__init__()
         self._version: int = 8
-        self._mode: ContractType = ContractType.STATEFULL
+        self._mode = ExecutionMode.STATEFUL
 
     @property
     def stack_pop_size(self) -> int:
@@ -5738,7 +6096,7 @@ class BoxPut(Instruction):
     def __init__(self) -> None:
         super().__init__()
         self._version: int = 8
-        self._mode: ContractType = ContractType.STATEFULL
+        self._mode = ExecutionMode.STATEFUL
 
     @property
     def stack_pop_size(self) -> int:
diff --git a/tealer/teal/instructions/parse_instruction.py b/tealer/teal/instructions/parse_instruction.py
index a83d53b..7b5793a 100644
--- a/tealer/teal/instructions/parse_instruction.py
+++ b/tealer/teal/instructions/parse_instruction.py
@@ -236,7 +236,7 @@ def _parse_byte_arguments(fields: List[str]) -> List[str]:
 
     Raises:
         ParseError: raises ParseError if the byte arguments are not correctly
-        encoded.
+            encoded.
     """
 
     arguments: List[str] = []
diff --git a/tealer/teal/instructions/parse_transaction_field.py b/tealer/teal/instructions/parse_transaction_field.py
index f845cc7..695d34a 100644
--- a/tealer/teal/instructions/parse_transaction_field.py
+++ b/tealer/teal/instructions/parse_transaction_field.py
@@ -17,10 +17,10 @@
     ARRAY_TX_FIELD_TXT_TO_OBJECT: Map(dict) from string representation
         of array transaction field to the corresponding class.
 """
-
+from typing import Dict, Type
 from tealer.teal.instructions import transaction_field
 
-TX_FIELD_TXT_TO_OBJECT = {
+TX_FIELD_TXT_TO_OBJECT: Dict[str, Type[transaction_field.TransactionField]] = {
     "Sender": transaction_field.Sender,
     "Fee": transaction_field.Fee,
     "FirstValid": transaction_field.FirstValid,
diff --git a/tealer/teal/instructions/transaction_field.py b/tealer/teal/instructions/transaction_field.py
index 650b446..41acac7 100644
--- a/tealer/teal/instructions/transaction_field.py
+++ b/tealer/teal/instructions/transaction_field.py
@@ -21,7 +21,11 @@ def __init__(self) -> None:
 
     @property
     def version(self) -> int:
-        """Teal version this field is introduced in and supported from."""
+        """Teal version this field is introduced in and supported from.
+
+        Returns:
+            Teal version the field is introduced in and supported from.
+        """
         return self._version
 
     def __str__(self) -> str:
diff --git a/tealer/teal/parse_functions.py b/tealer/teal/parse_functions.py
new file mode 100644
index 0000000..01c2314
--- /dev/null
+++ b/tealer/teal/parse_functions.py
@@ -0,0 +1,272 @@
+"""Create functions for a Teal contract.
+
+Teal contract with N subroutines is represented using `N + 1` disconnected CFGs. Each subroutine
+has its own CFG and the code which is not part of any subroutine is represented using a separate CFG, referred to
+as __main__ CFG. The entry block of the __main__ CFG is the entry block of the contract. Execution of the contract
+always starts here. Each of the CFG, including the __main__ CFG, is represented using a Subroutine class.
+
+Every contract has abstract divison of function. A function represents an user operation: whenever a contract is executed,
+the execution always results in execution of one of the function. The main benefit of functions is the ability to perform
+analysis of each function independently which gives more accurate results.
+
+In general, the first thing executed in a contract is the method dispatcher, it passes the execution to the method/function's
+entry block based on the method selector. The method dispatcher is often generated by the compiler(PyTeal) or the framework used(Beaker).
+Some contracts also contain custom method dispatchers implemented by the developers. And some contracts would not contain any functions,
+the entire contract is a single function.
+
+The method dispatcher can be:
+    - Compiler or the compilation framework generated
+    - Custom method dispatcher.
+    - No method dispatcher. Entire contract is a single function.
+
+A Function can be represented using the function entry block. The body of the function is the code reachable from that entry
+block. The Function class could just store the entry block and analyze the function using that. However, the contracts with
+custom dispatchers might contain the function body as part of its method dispatcher. For example, the dispatcher
+could contain validations, variable assignments that are common for multiple functions in it. In order to represent these functions,
+Tealer replaces basic blocks that are part of the dispatcher but not part of the function with basic blocks that contain the
+error instruction. As a result, the function code that is part of the dispatcher can also be analyzed.
+
+The entry block of a function for contracts with compiler generated method dispatcher is not in the middle of a subroutine.
+In practice, the custom method dispatcher also does not have entry block in the middle of the subroutine. It
+is possible to implement to the method dispatcher such that some blocks of a subroutine are part of one function and
+other blocks are part of another function. However, it is not common in practice.
+
+Tealer assumes the custom method dispatcher does not have a function with entry block in the middle of the subroutine. Main reason
+for this assumption is to simplify the design. __main__ CFG is disconnected from other CFGs and if the method dispatcher is only part
+of this CFG, it is easy to divide and create copies of the __main__ CFG for each function and keep a single copy of
+subroutines CFG.
+
+With M functions and N subroutines, the Teal contract will be represented with `N + M + 1` CFGs: M functions + N subroutines and __main__
+CFG.
+
+For functions whose dispatcher contains function body, all blocks from contract entry block to the function entry block will be
+preserved and blocks that are part of the dispatcher and not part of the function will be replaced by custom Err instruction.
+
+
+NOTE: The following documentation is not accurate with the implementation. The implementation should be updated to achieve the following
+functionality/design.
+
+Functions whose dispatcher does not contain function body, blocks part of the dispatcher will not be preserved.
+
+Construction of Functions:
+
+Inputs: contract(Teal), user configuration(UserConfig).
+
+    UserConfig.has_functions(bool): True if the contract has more than one function otherwise False
+    UserConfig.identified_functions(bool): True if the functions are already identified and if Tealer does not have to use heuristics to
+        identify them, otherwise False. If False, Tealer tries to identify the dispatcher and the functions.
+    UserConfig.functions(List[Tuple[List[str], bool]]): List of functions. Each function is a Tuple of dispatcher_path(List[str]) and
+        dispatcher_has_function_body(bool).
+            - The dispatcher path is the list of basic block ids from the __main__ entry block id to the function
+            entry block.
+            - The dispatcher_has_function_body is True when the dispatcher blocks contain validations, variable declarations, etc..
+
+Output: List of functions of the contract (List[Function]).
+
+Assumptions:
+    - The entry block of a function is not part of a subroutine.
+
+* if not UserConfig.has_functions:
+    return copy of the __main__ CFG as Function.
+* if not UserConfig.identified_functions:
+    run heuristics and identify the dispatcher. returned value has same type as UserConfig.functions
+* remove duplicates from the new functions and UserConfig.functions
+* loop over the new functions and UserConfig.functions
+    * if dispatcher_has_function_body:
+        * Construct CFG with blocks form __main__ CFG entry to function entry block.
+        * Replace unrelated dispatcher blocks with custom err instruction block.
+    * else:
+        * Construct a copy of __main__ CFG without the dispatcher blocks.
+"""
+
+import inspect
+import logging
+from typing import TYPE_CHECKING, List, Dict, Optional
+from collections import defaultdict
+
+from tealer.teal.functions import Function
+
+from tealer.teal.parse_teal import (
+    first_pass,
+    second_pass,
+    create_bb,
+    fourth_pass,
+    identify_subroutine_blocks,
+)
+from tealer.teal.instructions.instructions import Callsub, TealerCustomErrInstruction
+from tealer.analyses.dataflow.transaction_context import all_constraints
+from tealer.analyses.dataflow.transaction_context.generic import DataflowTransactionContext
+from tealer.analyses.utils.stack_ast_builder import construct_stack_ast, compute_equations
+from tealer.exceptions import TealerException
+from tealer.teal.subroutine import Subroutine
+from tealer.teal.basic_blocks import BasicBlock
+
+if TYPE_CHECKING:
+    from tealer.teal.teal import Teal
+    from tealer.teal.instructions.instructions import Instruction, Label
+
+
+def copy_main_cfg(teal: "Teal") -> List["BasicBlock"]:  # pylint: disable=too-many-locals
+
+    source_code = ""
+    original_instructions: List["Instruction"] = []
+    original_blocks: List["BasicBlock"] = []
+
+    for bi in sorted(teal.main.blocks, key=lambda bi: bi.idx):
+        # sorting groups the blocks which are next to each other in the source code.
+        for ins in bi.instructions:
+            source_code += "\n".join(ins.comments_before_ins) + "\n" + ins.source_code + "\n"
+            original_instructions.append(ins)
+        source_code += "\n"
+        original_blocks.append(bi)
+
+    # similar to parse_teal.parse_teal
+    instructions: List["Instruction"] = []
+    labels: Dict[str, "Label"] = {}
+    subroutine_callsubs: Dict[str, List[Callsub]] = defaultdict(list)
+
+    lines = source_code.splitlines()
+
+    _, _ = first_pass(lines, labels, subroutine_callsubs, instructions)
+    second_pass(instructions, labels)
+
+    all_bbs: List["BasicBlock"] = []
+    create_bb(instructions, all_bbs)
+
+    fourth_pass(all_bbs)
+
+    for ins_copy, ins_orig in zip(instructions, original_instructions):
+        ins_copy.line = ins_orig.line
+        if isinstance(ins_copy, Callsub):
+            assert isinstance(ins_orig, Callsub)
+            ins_copy.called_subroutine = ins_orig.called_subroutine
+
+    all_bbs = sorted(all_bbs, key=lambda bi: bi.entry_instr.line)
+    for bb_copy, bb_orig in zip(all_bbs, original_blocks):
+        bb_copy.idx = bb_orig.idx
+
+    for bb in all_bbs:
+        bb.teal = teal
+        bb.tealer_comments.insert(0, f"block_id = {bb.idx}; cost = {bb.cost}")
+
+    return all_bbs
+
+
+def _apply_transaction_context_analysis(function: "Function") -> None:
+    logger = logging.getLogger("Tealer")
+    logger.debug("[+] Running Transaction context analysis")
+    group_indices_cls = all_constraints.GroupIndices
+    analyses_classes = [getattr(all_constraints, name) for name in dir(all_constraints)]
+    analyses_classes = [
+        c
+        for c in analyses_classes
+        if inspect.isclass(c)
+        and issubclass(c, DataflowTransactionContext)
+        and c != group_indices_cls
+    ]
+    # Run group indices analysis first as other analysis use them.
+    logger.debug(f'[+] Running txn field analysis "{group_indices_cls.__name__}"')
+    group_indices_cls(function).run_analysis()
+    for cl in analyses_classes:
+        logger.debug(f'[+] Running txn field analysis: "{cl.__name__}"')
+        cl(function).run_analysis()
+    # clear cache
+    construct_stack_ast.cache_clear()  # construct stack ast is not used after transaction_context_analysis.
+    compute_equations.cache_clear()  # compute_equations is not used after transaction_context_analysis.
+
+
+# pylint: disable=too-many-locals, too-many-branches
+def construct_function(
+    teal: "Teal", dispatch_path: List[str], function_name: Optional[str] = None
+) -> "Function":
+    function_blocks = copy_main_cfg(teal)
+    entry = function_blocks[0]
+    dispatch_path_blocks: List["BasicBlock"] = []
+    current_valid_next_blocks = [entry]
+    for bid in dispatch_path:
+        block_index = int(bid[1:])  # B{index}
+        for bi in current_valid_next_blocks:
+            if bi.idx == block_index:
+                if bi in dispatch_path_blocks:
+                    raise TealerException(f"Dispatch path is a loop: {dispatch_path}")
+                dispatch_path_blocks.append(bi)
+                current_valid_next_blocks = bi.next
+                break
+        else:
+            raise TealerException(f"Invalid dispatch path: {dispatch_path}, {bid} is not valid")
+
+    for i, bi in enumerate(dispatch_path_blocks[:-1]):
+        valid_next = dispatch_path_blocks[i + 1]
+        for j, bi_next in enumerate(list(bi.next)):
+            if bi_next != valid_next:
+                err_block = BasicBlock()
+                err_instruction = TealerCustomErrInstruction()
+                # TODO: use a more meaningful idx??
+                err_instruction.line = (bi_next.entry_instr.line << 16) + bi.exit_instr.line
+                err_instruction.source_code = "TealerErr"
+                err_block.idx = (bi_next.idx << 16) + (bi_next.idx)
+                err_block.add_instruction(err_instruction)
+                err_block.teal = teal
+                err_block.tealer_comments.insert(0, "Tealer Custom Err Block")
+
+                bi.next[j] = err_block
+                err_block.add_prev(bi)
+                bi_next.prev.remove(bi)
+                # print("bi_next.idx", bi_next.idx, bi_next.prev)
+
+                # instruction edges are not changed here.
+                # instruction edges should not be used. Prefer using basic_block .next and .prev.
+    entry = dispatch_path_blocks[0]
+    function_main_blocks = identify_subroutine_blocks(entry)
+    if function_name is None:
+        function_name = "_".join(dispatch_path)
+    function_main_name = f"__main__.{function_name}"
+    # print(function_name)
+    # print(function_main_blocks)
+    # for block in function_main_blocks:
+    #     print(block.idx, block.prev, block.next)
+    function_main = Subroutine(function_main_name, entry, function_main_blocks)
+    for bi in function_main_blocks:
+        bi.subroutine = function_main
+    function_main.contract = teal
+
+    used_subroutines: List[Subroutine] = []
+    worklist: List[Subroutine] = [function_main]
+    while worklist:
+        for sub in worklist[0].called_subroutines:
+            if sub not in used_subroutines:
+                used_subroutines.append(sub)
+                if sub not in worklist:
+                    worklist.append(sub)
+        worklist = worklist[1:]
+
+    function_subroutines: Dict[str, Subroutine] = {sub.name: sub for sub in used_subroutines}
+
+    all_function_blocks: List[BasicBlock] = []
+    for sub in [function_main] + used_subroutines:
+        all_function_blocks += sub.blocks
+    # print("func_blocks =", all_function_blocks)
+    function_obj = Function(
+        function_name,
+        entry,
+        all_function_blocks,
+        teal,
+        function_main,
+        function_subroutines,
+    )
+
+    # for block in all_function_blocks:
+    #     print("block:", repr(block),  block.__hash__())
+    #     for prev_b in prev_blocks_global(block):
+    #         print("prev_b:", repr(prev_b), prev_b.__hash__())
+    #     for next_b in next_blocks_global(block):
+    #         print("next_b:", repr(next_b), next_b.__hash__())
+
+    # for block in sorted(all_function_blocks, key=lambda b: b.idx):
+    #     print(repr(block))
+    #     print(block)
+    # exit(1)
+
+    # TODO: Analysis fails with KeyError when one of the basic blocks is part of __main__ and a subroutine CFG. Handle this case.
+    _apply_transaction_context_analysis(function_obj)
+    return function_obj
diff --git a/tealer/teal/parse_teal.py b/tealer/teal/parse_teal.py
index 58adbff..34f846b 100644
--- a/tealer/teal/parse_teal.py
+++ b/tealer/teal/parse_teal.py
@@ -24,11 +24,12 @@
 contract represented by sequence of the basic blocks.
 
 """
+# pylint: disable=too-many-lines
 
-import inspect
 import sys
 import logging
 from typing import Optional, Dict, List, Tuple
+from collections import defaultdict
 
 from tealer.teal.basic_blocks import BasicBlock
 from tealer.teal.subroutine import Subroutine
@@ -50,7 +51,7 @@
     Txn,
     Method,
 )
-from tealer.teal.instructions.instructions import ContractType
+
 from tealer.teal.instructions.parse_instruction import parse_line, ParseError
 from tealer.teal.instructions.transaction_field import TransactionField, ApplicationID
 from tealer.teal.instructions.asset_holding_field import AssetHoldingField
@@ -58,51 +59,41 @@
 from tealer.teal.instructions.app_params_field import AppParamsField
 from tealer.teal.instructions.acct_params_field import AcctParamsField
 from tealer.teal.teal import Teal
-from tealer.analyses.dataflow import all_constraints
-from tealer.analyses.dataflow.generic import DataflowTransactionContext
-from tealer.analyses.utils.stack_ast_builder import construct_stack_ast, compute_equations
 from tealer.utils.arc4_abi import get_method_selector
+from tealer.utils.teal_enums import ExecutionMode
 
 
 logger_parsing = logging.getLogger("Parsing")
 logging.basicConfig()
 
 
-def _detect_contract_type(instructions: List[Instruction]) -> ContractType:
-    """Determine type of contract given it's instructions.
+def _detect_execution_mode(instructions: List[Instruction]) -> ExecutionMode:
+    """Determine type of contract given its instructions.
 
-    It isn't possible to determine how a contract might be used, whether
-    as an application or a signature in all cases. This function uses the
-    fact that there are certain instructions in teal that are only valid if used
-    in a certain kind of contract. So, this function looks for instructions
-    that are valid in only one type and return that as the type for the given
-    contract. if all instructions in the contract are valid for both types
-    of contracts, then this function returns ContractType.ANY as it isn't
-    sure how the contract might be used.
+    If any of the instructions is supported in only one execution mode then return that as
+    contract's execution mode. Otherwise, return ExecutionMode.ANY
 
     Args:
         instructions: List of all the instructions present in the contract.
 
     Returns:
-        Type of the contract indicated by ContractType enum symbol.
-        ``STATEFULL`` if there's a instruction only valid in applications,
-        ``STATELESS`` if there's a instruction only valid in signatures,
-        ``ANY`` if there isn't any such instruction.
+        Returns the execution mode of the contract: Stateful, Stateless or Any.
     """
 
     for ins in instructions:
-        if ins.mode != ContractType.ANY:
+        if ins.mode != ExecutionMode.ANY:
             return ins.mode
-    return ContractType.ANY
+    return ExecutionMode.ANY
 
 
 def create_bb(instructions: List[Instruction], all_bbs: List[BasicBlock]) -> None:
-    """Construct basic blocks and add basic block sequential links.
+    """Construct basic blocks and add basic block default edges.
 
-    This function is the third pass of the teal parser. Instructions are
+    Third pass of the teal parser. Instructions are
     divided into basic blocks based on the following rules:
 
-    * A new basic block is created for each Label instruction.
+    * Label instruction is destination for multiple instructions. A new
+        basic block is created for each Label instruction.
     * Instructions B, Err, Return, Callsub, Retsub are exit instructions
         of a basic block. A new basic block is created for instructions
         following them.
@@ -110,56 +101,56 @@ def create_bb(instructions: List[Instruction], all_bbs: List[BasicBlock]) -> Non
         of the basic block. A new basic block is created for instructions
         following them.
 
-    This function also adds sequential links between the constructed basic
-    blocks. Two sequential basic blocks are linked if the exit instruction
-    of the first basic block is sequentially previous instruction to the
-    entry instruction of the second basic block.
-
-    This is a "in place" function, given arguments are modified with the
-    data this function is supposed to return.
+    This function also adds default edges between the constructed basic
+    blocks. Two basic blocks have default edge if the entry instruction
+    of the second basic block is default next instruction for the exit instruction
+    of the first basic block.
 
     Args:
-        instructions: List of parsed instruction objects.
-        all_bbs: List of BasicBlock objects representing the teal contract.
-            This is an "in place" argument, the basic blocks created in
-            the function are appended to this list in the order they are created.
+        instructions: List of instructions.
+        all_bbs: (in-place) List of BasicBlocks representing the teal contract.
     """
 
-    bb: Optional[BasicBlock] = BasicBlock()
-    if bb:  # to make mypy happy
-        all_bbs.append(bb)
+    bb = BasicBlock()
+    all_bbs.append(bb)
     for ins in instructions:
 
-        if isinstance(ins, Label) or not bb:
-            # use bb if it is empty instead of creating new BasicBlock.
-            if bb is None or len(bb.instructions) != 0:
-                next_bb = BasicBlock()
-                all_bbs.append(next_bb)
-                if bb:
-                    bb.add_next(next_bb)
-                    next_bb.add_prev(bb)
-                bb = next_bb
+        # if instruction is label and basic block is not empty:
+        #   Create a new basic block and add default edge
+        # if basic block is empty, no need to create new one.
+        if isinstance(ins, Label) and len(bb.instructions) != 0:
+            next_bb = BasicBlock()
+            all_bbs.append(next_bb)
+            bb.add_next(next_bb)
+            next_bb.add_prev(bb)
+            bb = next_bb
+
         bb.add_instruction(ins)
         ins.bb = bb
 
-        if ins.callsub_ins is not None and ins.bb is not None:
-            # callsub is before this instruction in the code. so, bb should have been assigned
-            # already
-            callsub_basic_block = ins.callsub_ins.bb
-            if callsub_basic_block is not None:
-                ins.bb.callsub_block = callsub_basic_block
-                callsub_basic_block.sub_return_point = ins.bb
-
-        if len(ins.next) > 1 and not isinstance(ins, Retsub):
-            if not isinstance(ins.next[0], Label):
-                next_bb = BasicBlock()
-                all_bbs.append(next_bb)
-                bb.add_next(next_bb)
-                next_bb.add_prev(bb)
-                bb = next_bb
-
-        if len(ins.next) == 0 or isinstance(ins, (B, Err, Return, Callsub, Retsub)):
-            bb = None
+        # `BZ`, `BNZ`, `match` and `switch` have more than one next instruction.
+        # `Callsub` has only one next instruction and it is the default next instruction
+        # For these instructions, execution continues on the next instruction by default.
+        if len(ins.next) > 1 or isinstance(ins, Callsub):
+            # if the instruction is the last instruction, do not create a new *empty* block.
+            if ins == instructions[-1]:
+                continue
+            next_bb = BasicBlock()
+            all_bbs.append(next_bb)
+            # add sequential link
+            bb.add_next(next_bb)
+            next_bb.add_prev(bb)
+            bb = next_bb
+
+        # Err, Return, Retsub will have `len(ins.next) == 0` and `B`.
+        if len(ins.next) == 0 or isinstance(ins, B):
+            # if the instruction is the last instruction, do not create a *empty* block.
+            if ins == instructions[-1]:
+                continue
+            next_bb = BasicBlock()
+            all_bbs.append(next_bb)
+            # Do not add any edges. There are no default edges and Jump edges are added in the next pass.
+            bb = next_bb
 
 
 def _add_instruction_comments(ins: Instruction) -> None:
@@ -172,52 +163,36 @@ def _add_instruction_comments(ins: Instruction) -> None:
         ins.tealer_comments.append(f"method-selector: {method_selector}")
 
 
-def _first_pass(  # pylint: disable=too-many-branches
+def first_pass(  # pylint: disable=too-many-branches
     lines: List[str],
     labels: Dict[str, Label],
-    rets: Dict[str, List[Instruction]],
+    subroutines: Dict[str, List[Callsub]],
     instructions: List[Instruction],
 ) -> Tuple[List[Intcblock], List[Bytecblock]]:
-    """Parse instructions and add sequential instruction links.
-
-    This function is the first pass of the teal parser. Source code
-    lines are parsed into corresponding Instruction objects and non-jump
-    instructions are linked. Adding instructions links is linking two
-    instruction objects as previous and next. Non-jump instruction links
-    are links between instructions that are previous, next in terms of
-    execution flow as well as previous, next in terms of source code i.e
-    links that are directly evident from just those two instructions that
-    are continous in source code. Jump links are links between branch
-    instructions and their destination, callsub and their destination,
-    retsub and their return points, etc. This function adds non-jump or
-    sequential instruction links.
-
-    This is a "in place" function, given arguments are modified with the
-    data this function is supposed to return.
+    """Parse instructions and add default instruction edges.
+
+    First pass of the teal parser. Source code lines are parsed into corresponding Instruction objects and default
+    edges are added to the instructions.
+    * default edges are edges between two continuous instructions where execution might pass from the first
+        instruction to the second instruction.
+    * Jump edges are edges between two non-continuous instructions where execution might pass from the first instruction
+        to the second.
 
     Args:
         lines: List of source code lines of a teal contract.
-        labels: Dict map from teal label string to the parsed label instruction.
-            instance. This argument is an "in place" argument and will be
-            populated by the function while parsing.
-        rets: Dict map from teal label string of a subroutine to list of it's
-            return point instructions. subroutines are supported from teal v4 and
-            can be called with callsub instruction using the label of the subroutine.
-            The execution flow is passed from the subroutine to next instruction of
-            the callsub after executing the subroutine. This variable stores all those
-            return points(next instruction after callsub) for each subroutine label
-            in the contract.
-            This argument is an "in place" argument and will be populated by the
-            function while parsing.
-        instructions: List of parsed instruction objects.
-            This argument is also an "in place" argument and will be populated
-            by the function while parsing.
+        labels: (in-place) Map from teal label string to the parsed label instruction.
+            instance.
+        subroutines: (in-place) Map from subroutine name to callsub instructions calling the subroutine.
+        instructions: (in-place) List of parsed instructions.
+
+    Returns:
+        intcblock_ins: List of `intcblock` instructions in the contract.
+        bytecblock_ins: List of `bytecblock` instructions in the contract.
     """
 
-    # First pass over the intructions list: Add non-jump instruction links and collect meta-data
+    # First pass over the intructions list: Add default edges and collect other information.
     idx = 0
     prev: Optional[Instruction] = None  # Flag: last instruction was an unconditional jump
-    call: Optional[Callsub] = None  # Flag: last instruction was a callsub
     intcblock_ins: List[Intcblock] = []  # List of intcblock instructions present in the contract
     bytecblock_ins: List[Bytecblock] = []  # List of bytecblock instructions present in the contract
 
@@ -252,65 +227,51 @@ def _first_pass(  # pylint: disable=too-many-branches
         if isinstance(ins, Label):
             labels[ins.label] = ins
 
-        # If the prev. ins. was anything other than an unconditional jump, then link the two instructions
+        # If the prev. ins. was anything **other** than an unconditional jump or unconditional exit:
+        #   then add default between the two instructions
         if prev:
             ins.add_prev(prev)
             prev.add_next(ins)
 
-        # If the prev. inst was a callsub, add the current instruction as a return point for the callsub label
-        if call:
-            call.return_point = ins
-            if call.label in rets.keys():
-                rets[call.label].append(ins)
-            else:
-                rets[call.label] = [ins]
-            ins.callsub_ins = call  # ins is the return point when call is executed.
-
-        # Now prepare for the next-line instruction
-        # A flag that says that this was an unconditional jump
+        # A flag that says that this was an unconditional jump or unconditional exit instructions.
+        # `Callsub`` is not an unconditional jump instruction.
+        # `B` is unconditional jump.
+        # `Err, Return` is unconditional exit of the program.
+        # `Retsub` is unconditional exit of the subroutine.
         prev = ins
-        if isinstance(ins, (B, Err, Return, Callsub, Retsub)):
+        if isinstance(ins, (B, Err, Return, Retsub)):
             prev = None
 
-        # A flag that says that this was a callsub
-        call = None
+        # if ins is callsub, add the label to subroutines and store callsub instruction.
         if isinstance(ins, Callsub):
-            call = ins
-            if call.label not in rets.keys():
-                rets[call.label] = []
+            subroutines[ins.label].append(ins)
 
         # Finally, add the instruction to the instruction list
         instructions.append(ins)
     return intcblock_ins, bytecblock_ins
 
 
-def _second_pass(  # pylint: disable=too-many-branches
+def second_pass(  # pylint: disable=too-many-branches
     instructions: List[Instruction],
     labels: Dict[str, Label],
-    rets: Dict[str, List[Instruction]],
 ) -> None:
-    """Add jump or non-sequential instruction links.
+    """Add jump edges between instructions.
 
-    This function is second pass of the teal parser. Adding jump links is
-    linking two instruction objects where execution flow is passed
-    from the first instruction to the second instruction because of
-    a jump. A jump is passing execution from one instruction to the
-    next that are not sequential. A execution jump might happen because
-    of branch, callsub, retsub instructions in TEAL. This function links
-    such pairs of instructions as previous, next.
+    Second pass of the teal parser.
+    * Jump edges are edges between two non-continuous instructions where execution might pass from the first instruction
+        to the second.
+    * Jump instructions: `B`, `BZ`, `BNZ`, `match`, `switch`.
 
     Args:
-        instructions: List of parsed instruction objects.
-        labels: Dict map from teal label string to the parsed label instruction.
-        rets: Dict map from teal label string of a subroutine to list of it's
-            return point instructions.
+        instructions: List of instructions.
+        labels: Map from teal label string to the parsed label instruction.
     """
     logger_parsing.debug("Second Pass")
     # Second pass over the instructions list: Add instruction links for jumps
     for ins in instructions:
 
-        # If a labeled jump, link the ins. to its label
-        if isinstance(ins, (B, BZ, BNZ, Callsub)):
+        # If a labeled jump to a single instruction, link the ins to its label
+        if isinstance(ins, (B, BZ, BNZ)):
             ins.add_next(labels[ins.label])
             labels[ins.label].add_prev(ins)
 
@@ -320,94 +281,28 @@ def _second_pass(  # pylint: disable=too-many-branches
                 ins.add_next(labels[ins_label])
                 labels[ins_label].add_prev(ins)
 
-    # link retsub instructions to return points of corresponding subroutines
-    retsubs: Dict[str, List[Retsub]] = {}  # map each subroutine label to list of it's retsubs
-    for subroutine in rets:
-        label = labels[subroutine]
-        retsubs[subroutine] = []
-        logger_parsing.debug(f"    Label = {label}")
-        # use dfs to find all retsub instructions starting from subroutine label instruction
-        stack: List[Instruction] = []
-        visited: List[Instruction] = []
-
-        stack.append(label)
-        while len(stack) > 0:
-            ins = stack.pop()
-            logger_parsing.debug(f"     Ins = {ins}")
-            visited.append(ins)
-
-            if isinstance(ins, Retsub):
-                retsubs[subroutine].append(ins)
-                continue
-
-            if isinstance(ins, Callsub):
-                # don't follow callsub path, which in itself is another subroutine
-                if ins.return_point is None:
-                    continue
-                next_ins = ins.return_point
-                if next_ins not in visited:
-                    stack.append(next_ins)
-            else:
-                for next_ins in ins.next:
-                    logger_parsing.debug(f"     next_ins = {next_ins}")
-
-                    if next_ins not in visited:
-                        stack.append(next_ins)
-
-    # link retsub to return points
-    for subroutine in rets:
-        for return_point in rets[subroutine]:
-            for retsub_ins in retsubs[subroutine]:
-                retsub_ins.add_next(return_point)
-                return_point.add_prev(retsub_ins)
-
 
-def _fourth_pass(instructions: List[Instruction]) -> None:  # pylint: disable=too-many-branches
-    """Add jump or non-sequential basic block links.
+def fourth_pass(basic_blocks: List[BasicBlock]) -> None:  # pylint: disable=too-many-branches
+    """Add jump edges between basic blocks.
 
-    This function is the fourth pass of the teal parser. Jump links
-    are added between two basic blocks if there is a jump link from
+    Fourth pass of the teal parser. Jump edges
+    are added between two basic blocks if there is a jump edge from
     exit instruction of the first basic block to the entry instruction
     of the second basic block.
 
     Args:
-        instructions: List of parsed instruction objects.
+        basic_blocks: List of basic blocks.
     """
 
-    # Fourth pass over the instructiions list: Add jump-based basic block links
-    for ins in instructions:
-        # A branching instruction with more than one target (other than a retsub)
-        if len(ins.next) > 1 and not isinstance(ins, (Retsub, Switch, Match)):
-            branch = ins.next[1]
-            if branch.bb and ins.bb:
-                branch.bb.add_prev(ins.bb)
-            if ins.bb and branch.bb:
-                ins.bb.add_next(branch.bb)
-        # A single-target branching instruction (b or callsub or bz/bnz appearing as the last instruction in the list)
-        if isinstance(ins, (B, Callsub)) or (
-            ins == instructions[-1] and isinstance(ins, (BZ, BNZ))
-        ):
-            dst = ins.next[0].bb
-            if dst and ins.bb:
-                dst.add_prev(ins.bb)
-            if ins.bb and dst:
-                ins.bb.add_next(dst)
-        # A retsub
-        if isinstance(ins, Retsub):
-            for branch in ins.next:
-                if branch.bb and ins.bb:
-                    branch.bb.add_prev(ins.bb)
-                if ins.bb and branch.bb:
-                    ins.bb.add_next(branch.bb)
-        # switch and match
-        if isinstance(ins, (Switch, Match)):
-            bb = ins.bb
-            for next_ins in ins.next:
-                if next_ins.bb and bb:
-                    if next_ins.bb not in bb.next:
-                        bb.add_next(next_ins.bb)
-                    if bb not in next_ins.bb.prev:
-                        next_ins.bb.add_prev(bb)
+    # Fourth pass over the basic blocks: Add jump edges between basic blocks
+    for bb in basic_blocks:
+        ins = bb.exit_instr
+        for next_ins in ins.next:
+            next_bb = next_ins.bb
+            if next_bb not in bb.next:
+                assert bb not in next_bb.prev
+                bb.add_next(next_bb)
+                next_bb.add_prev(bb)
 
 
 def _add_basic_blocks_idx(bbs: List[BasicBlock]) -> List[BasicBlock]:
@@ -418,6 +313,9 @@ def _add_basic_blocks_idx(bbs: List[BasicBlock]) -> List[BasicBlock]:
 
     Args:
         bbs: List of BasicBlock objects representing the teal contract.
+
+    Returns:
+        Returns :bbs: after sorting and updating their indexes(idx).
     """
 
     bbs = sorted(bbs, key=lambda x: x.entry_instr.line)
@@ -426,16 +324,14 @@ def _add_basic_blocks_idx(bbs: List[BasicBlock]) -> List[BasicBlock]:
     return bbs
 
 
-def _identify_subroutine_blocks(entry_block: "BasicBlock") -> List["BasicBlock"]:
-    """find all the basic blocks part of a subroutine given it's label instruction.
+def identify_subroutine_blocks(entry_block: "BasicBlock") -> List["BasicBlock"]:
+    """find all the basic blocks part of a subroutine using DFS.
 
     Args:
-        label ("Label"): label instruction of the subroutine.
-        bbs (List["BasicBlock"]): CFG of the contract.
+        entry_block: Entry block of the subroutine.
 
     Returns:
-        "BasicBlock": Entry block of the subroutine.
-        List["BasicBlock"]: list of all basic blocks part of a subroutine.
+        Returns the list of all basic blocks part of a subroutine.
     """
 
     subroutines_blocks: List["BasicBlock"] = []
@@ -446,21 +342,8 @@ def _identify_subroutine_blocks(entry_block: "BasicBlock") -> List["BasicBlock"]
         bb = stack.pop()
         subroutines_blocks.append(bb)
 
-        # check for retsub before exploring as retsubs are connected to return points
-        if isinstance(bb.exit_instr, Retsub):
-            continue
-
-        # callsub return point is part of the subroutine
-        if isinstance(bb.exit_instr, Callsub):
-            return_point = bb.exit_instr.return_point
-
-            if return_point and return_point.bb not in subroutines_blocks:
-                if return_point.bb is not None:
-                    stack.append(return_point.bb)
-            continue
-
         for next_bb in bb.next:
-            if next_bb not in subroutines_blocks:
+            if next_bb not in subroutines_blocks and next_bb not in stack:
                 stack.append(next_bb)
 
     return subroutines_blocks
@@ -516,9 +399,9 @@ def _verify_version(ins_list: List[Instruction], program_version: int) -> bool:
                         file=sys.stderr,
                     )
                     error = True
-        if ins.mode == ContractType.STATEFULL:
+        if ins.mode == ExecutionMode.STATEFUL:
             stateful_ins.append(ins)
-        elif ins.mode == ContractType.STATELESS:
+        elif ins.mode == ExecutionMode.STATELESS:
             stateless_ins.append(ins)
 
     if stateless_ins and stateful_ins:
@@ -537,25 +420,6 @@ def _verify_version(ins_list: List[Instruction], program_version: int) -> bool:
     return error
 
 
-def _apply_transaction_context_analysis(teal: "Teal") -> None:
-    group_indices_cls = all_constraints.GroupIndices
-    analyses_classes = [getattr(all_constraints, name) for name in dir(all_constraints)]
-    analyses_classes = [
-        c
-        for c in analyses_classes
-        if inspect.isclass(c)
-        and issubclass(c, DataflowTransactionContext)
-        and c != group_indices_cls
-    ]
-    # Run group indices analysis first as other analysis use them.
-    group_indices_cls(teal).run_analysis()
-    for cl in analyses_classes:
-        cl(teal).run_analysis()
-    # clear cache
-    construct_stack_ast.cache_clear()  # construct stack ast is not used after transaction_context_analysis.
-    compute_equations.cache_clear()  # compute_equations is not used after transaction_context_analysis.
-
-
 def _fill_intc_bytec_info(
     intcblock_ins: List[Intcblock],
     bytecblock_ins: List[Bytecblock],
@@ -569,6 +433,12 @@ def _fill_intc_bytec_info(
 
     This is the case for most of the contracts as intcblock/bytecblock instructions are generated
     internally by the assembler or PyTeal compiler.
+
+    Args:
+        intcblock_ins: List of all "intcblock" instructions in the contract.
+        bytecblock_ins: List of all "bytecblock" instructions in the contract.
+        entry_block: Entry block of the contract.
+        teal: The contract.
     """
     if len(intcblock_ins) == 1 and intcblock_ins[0].bb == entry_block:
         teal.set_int_constants(intcblock_ins[0].constants)
@@ -577,52 +447,48 @@ def _fill_intc_bytec_info(
         teal.set_byte_constants(bytecblock_ins[0].constants)
 
 
-def parse_teal(  # pylint: disable=too-many-locals
+def parse_teal(  # pylint: disable=too-many-locals,too-many-branches,too-many-statements
     source_code: str, contract_name: str = ""
 ) -> Teal:
     """Parse algorand smart contracts written in teal.
 
     Parsing teal cotracts consists of four passes:
 
-    #. Parses instructions and adds sequential instruction links.
-    #. Adds jump based instruction links.
-    #. Constructs basic blocks and adds sequential basic block links.
-    #. Adds jump based basic block links.
-
-    This function also performs basic version checks on the instructions
-    and fields.
-
-    Teal object is created with the parsed instructions, basic blocks and
-    subroutines identified in the contract.
+    #. Parses instructions and adds default edges between instructions.
+    #. Adds jump edges between links.
+    #. Constructs basic blocks and adds default edges between basic block.
+    #. Adds jump edges between basic blocks.
 
     Args:
         source_code: TEAL source code of the contract.
+        contract_name: Name of the contract.
 
     Returns:
-        Teal object representing the given contract created with the related
-        information.
+        Teal representing the given contract.
     """
 
     instructions: List[Instruction] = []  # Parsed instructions list
     labels: Dict[str, Label] = {}  # Global map of label names to label instructions
-    rets: Dict[str, List[Instruction]] = {}  # Lists of return points corresponding to labels
+    # Map from subroutine name to callsub instructions calling it
+    subroutine_callsubs: Dict[str, List[Callsub]] = defaultdict(list)
 
     lines = source_code.splitlines()
 
-    intcblock_ins, bytecblock_ins = _first_pass(lines, labels, rets, instructions)
-    logger_parsing.debug(f"rets = {rets}")
-    _second_pass(instructions, labels, rets)
+    intcblock_ins, bytecblock_ins = first_pass(lines, labels, subroutine_callsubs, instructions)
+    logger_parsing.debug(f"subroutine_callsubs = {subroutine_callsubs}")
+    second_pass(instructions, labels)
     logger_parsing.debug("instruction and nexts")
     for ins in instructions:
         logger_parsing.debug(f"     {ins}, next: {ins.next}")
+
     # Third pass over the instructions list: Construct the basic blocks and sequential links
     all_bbs: List[BasicBlock] = []
     create_bb(instructions, all_bbs)
 
-    _fourth_pass(instructions)
+    fourth_pass(all_bbs)
 
     all_bbs = _add_basic_blocks_idx(all_bbs)
-    mode = _detect_contract_type(instructions)
+    mode = _detect_execution_mode(instructions)
 
     version = 1
     if isinstance(instructions[0], Pragma):
@@ -630,38 +496,73 @@ def parse_teal(  # pylint: disable=too-many-locals
 
     _verify_version(instructions, version)
 
-    subroutines: Dict[str, "Subroutine"] = {}
-    if version >= 4:
-        for subroutine_label in rets:
-            label_ins = labels[subroutine_label]
-            if label_ins.bb is None:
-                # TODO: Move the `is None` check to `Instruction.bb` property.
-                continue
-            subroutine_entry_block = label_ins.bb
-            # add tealer comment "Subroutine: {label}" to the subroutine entry block
-            subroutine_entry_block.tealer_comments.append(f"Subroutine {subroutine_label}")
+    all_reachable_blocks: List["BasicBlock"] = []
+    subroutines_and_blocks: Dict[str, List["BasicBlock"]] = {}
+    for subroutine_name in subroutine_callsubs:
+        subroutine_entry_block = labels[subroutine_name].bb
+        subroutine_blocks = identify_subroutine_blocks(subroutine_entry_block)
+        subroutines_and_blocks[subroutine_name] = subroutine_blocks
+        all_reachable_blocks += subroutine_blocks
 
-            subroutine_blocks = _identify_subroutine_blocks(subroutine_entry_block)
-            subroutines[subroutine_label] = Subroutine(
-                subroutine_label, subroutine_entry_block, subroutine_blocks
-            )
+    contract_entry_block = all_bbs[0]
+    main_entry_point_blocks = identify_subroutine_blocks(contract_entry_block)
+    all_reachable_blocks += main_entry_point_blocks
 
-    main_entry_point_blocks = _identify_subroutine_blocks(all_bbs[0])
-    main_program_name = ""
-    main_program = Subroutine(main_program_name, all_bbs[0], main_entry_point_blocks)
-    teal = Teal(instructions, all_bbs, version, mode, main_program, subroutines)
-
-    # set teal instance to it's basic blocks
+    subroutines: Dict[str, "Subroutine"] = {}
+    for subroutine_name in subroutine_callsubs:
+        label_ins = labels[subroutine_name]
+        subroutine_entry_block = label_ins.bb
+        # add tealer comment "Subroutine: {label}" to the subroutine entry block
+        subroutine_entry_block.tealer_comments.append(f"Subroutine {subroutine_name}")
+        # list all blocks of the subroutine using DFS
+        subroutine_blocks = subroutines_and_blocks[subroutine_name]
+        subroutine_obj = Subroutine(subroutine_name, subroutine_entry_block, subroutine_blocks)
+        # set callsub blocks calling the subroutine
+        callsub_blocks = [ins.bb for ins in subroutine_callsubs[subroutine_name]]
+        callsub_blocks = [bb for bb in callsub_blocks if bb in all_reachable_blocks]
+        subroutine_obj.caller_blocks = callsub_blocks
+        # for each callsub instruction, set the called subroutine
+        for ins in subroutine_callsubs[subroutine_name]:
+            ins.called_subroutine = subroutine_obj
+
+        # set subroutine to each basic block
+        for bi in subroutine_blocks:
+            bi.subroutine = subroutine_obj
+        subroutines[subroutine_name] = subroutine_obj
+
+    main_program_name = "__main__"
+    main_program = Subroutine(main_program_name, contract_entry_block, main_entry_point_blocks)
+    for bi in main_entry_point_blocks:
+        bi.subroutine = main_program
+
+    # TODO: Handle unreachable basic blocks.
+    # Note: PyTeal generated contracts have unreachable code.
+    for bi in all_bbs:
+        if bi not in all_reachable_blocks:
+            # bi is unreachable
+            for bnext in bi.next:
+                bnext.prev.remove(bi)
+                bi.next.remove(bnext)
+
+            for ins_next in bi.exit_instr.next:
+                ins_next.prev.remove(bi.exit_instr)
+                bi.exit_instr.next.remove(ins_next)
+
+            for ins in bi.instructions:
+                instructions.remove(ins)
+
+    all_reachable_blocks = sorted(list(set(all_reachable_blocks)), key=lambda bi: bi.idx)
+    teal = Teal(version, mode, instructions, all_reachable_blocks, main_program, subroutines)
+
+    # set teal instance for it's basic blocks
     for bb in teal.bbs:
         bb.teal = teal
-        # Add tealer comment of cost and id
         bb.tealer_comments.insert(0, f"block_id = {bb.idx}; cost = {bb.cost}")
 
-    for subroutine in [main_program] + teal.subroutines_list:
+    for subroutine in [main_program] + list(subroutines.values()):
         subroutine.contract = teal
 
     teal.contract_name = contract_name
-    _fill_intc_bytec_info(intcblock_ins, bytecblock_ins, all_bbs[0], teal)
-    _apply_transaction_context_analysis(teal)
+    _fill_intc_bytec_info(intcblock_ins, bytecblock_ins, teal.main.entry, teal)
 
     return teal
diff --git a/tealer/teal/subroutine.py b/tealer/teal/subroutine.py
index dac3a94..c5fd6fe 100644
--- a/tealer/teal/subroutine.py
+++ b/tealer/teal/subroutine.py
@@ -8,7 +8,7 @@
     from tealer.teal.teal import Teal
 
 
-class Subroutine:
+class Subroutine:  # pylint: disable=too-many-instance-attributes
     """Represent a Teal subroutine.
 
     Main entry point code, code that is not part of any subroutine, is represented as a separate subroutine.
@@ -22,30 +22,56 @@ def __init__(self, name: str, entry: "BasicBlock", blocks: List["BasicBlock"]) -
             b for b in blocks if len(b.next) == 0 or isinstance(b.exit_instr, Retsub)
         ]
         self._contract: Optional["Teal"] = None
+        self._caller_callsub_blocks: List["BasicBlock"] = []
+        self._return_point_blocks: List["BasicBlock"] = []
 
     @property
     def name(self) -> str:
-        """Name of the subroutine"""
+        """Name of the subroutine
+
+        Returns:
+            Returns the name of the subroutine. The name of the CFG with non-subroutine blocks is "__main__"
+        """
         return self._name
 
     @property
     def entry(self) -> "BasicBlock":
-        """Entry block of the subroutine"""
+        """Entry block of the subroutine.
+
+        Returns:
+            entry block of the subroutine.
+        """
         return self._entry
 
     @property
     def exit_blocks(self) -> List["BasicBlock"]:
-        """Exit blocks of the subroutine."""
+        """Exit blocks of the subroutine.
+
+        Returns:
+            Returns the exit blocks of the subroutine. Exit blocks include blocks which return from the subroutine(retsub)
+            and also blocks which exit from the contract.
+        """
         return self._exit_blocks
 
     @property
     def blocks(self) -> List["BasicBlock"]:
-        """List of all basic blocks of the subroutine"""
+        """List of all basic blocks of the subroutine.
+
+        Returns:
+            Returns list of all basic blocks that are part of this subroutine.
+        """
         return self._blocks
 
     @property
     def contract(self) -> "Teal":
-        """The Teal contract this subroutine is a part of"""
+        """The Teal contract this subroutine is a part of
+
+        Returns:
+            Returns the contract.
+
+        Raises:
+            TealerException: Raises error if contract is not set during parsing.
+        """
         if self._contract is None:
             raise TealerException(f"Contract of subroutine {self._name} is not set")
         return self._contract
@@ -53,3 +79,48 @@ def contract(self) -> "Teal":
     @contract.setter
     def contract(self, contract_obj: "Teal") -> None:
         self._contract = contract_obj
+
+    @property
+    def caller_blocks(self) -> List["BasicBlock"]:
+        """BasicBlock with callsub instructions which call this subroutine.
+
+        Returns:
+            List of caller callsub basic blocks. This blocks have "callsub {self.name}"
+            as exit instruction.
+        """
+        return self._caller_callsub_blocks
+
+    @caller_blocks.setter
+    def caller_blocks(self, caller_callsub_blocks: List["BasicBlock"]) -> None:
+        """Set caller_blocks and return point blocks
+
+        Args:
+            caller_callsub_blocks: basic blocks with callsub instruction calling this subroutine.
+        """
+        self._caller_callsub_blocks = caller_callsub_blocks
+        self._return_point_blocks = [
+            bi.next[0] for bi in caller_callsub_blocks if len(bi.next) == 1
+        ]
+
+    @property
+    def return_point_blocks(self) -> List["BasicBlock"]:
+        return self._return_point_blocks
+
+    @property
+    def retsub_blocks(self) -> List["BasicBlock"]:
+        """List of basic blocks of the subroutine containing `Retsub`.
+
+        Returns:
+            Returns the list of retsub blocks. This blocks return the execution from the
+            subroutine to the caller.
+        """
+        return [b for b in self._exit_blocks if isinstance(b.exit_instr, Retsub)]
+
+    @property
+    def called_subroutines(self) -> List["Subroutine"]:
+        """List of subroutines called by this subroutine.
+
+        Returns:
+            Returns a list of subroutines called by the subroutine.
+        """
+        return list(set(bi.called_subroutine for bi in self._blocks if bi.is_callsub_block))
diff --git a/tealer/teal/teal.py b/tealer/teal/teal.py
index c88b7c9..fcc3d80 100644
--- a/tealer/teal/teal.py
+++ b/tealer/teal/teal.py
@@ -9,51 +9,15 @@
     Teal: Class to represent a contract.
 """
 
-from pathlib import Path
-from typing import List, Any, Optional, Type, TYPE_CHECKING, Tuple, Dict
-
-from tealer.detectors.abstract_detector import AbstractDetector, DetectorClassification
-from tealer.printers.abstract_printer import AbstractPrinter
+from typing import List, TYPE_CHECKING, Tuple, Dict, Optional
 
 from tealer.teal.basic_blocks import BasicBlock
 from tealer.teal.subroutine import Subroutine
-from tealer.teal.instructions.instructions import Instruction, ContractType
-from tealer.exceptions import TealerException
+from tealer.teal.instructions.instructions import Instruction
+from tealer.utils.teal_enums import ContractType, ExecutionMode
 
 if TYPE_CHECKING:
-    from tealer.utils.output import SupportedOutput
-
-# from slither: slither/slither.py
-def _check_common_things(
-    thing_name: str, cls: Any, base_cls: Any, instance_list: List[Any]
-) -> None:
-    """Check if the class is correct subclass and is unique.
-
-    check if :cls: is subclass of :base_cls: and it's instance is not already
-    present in :instance_list:. if either of the conditions fail, then an
-    exception is raised.
-
-    Args:
-        thing_name: name of the feature(add-on) that will on the teal
-            contract("detector" or "printer"). Used for error reporting.
-        cls: Class representing the feature.
-        base_cls: Base class for the feature.
-        instance_list: List of objects already registered to run.
-
-    Raises:
-        TealerException: raises exception if the :cls: is not subclass of
-            :base_cls: or if an object of :cls: is present in the
-            :instance_list:.
-    """
-
-    if not issubclass(cls, base_cls) or cls is base_cls:
-        raise TealerException(
-            f"You can't register {cls.__name__} as a {thing_name}."
-            f"You need to pass a class that inherits from {base_cls.__name__}"
-        )
-
-    if any(isinstance(obj, cls) for obj in instance_list):
-        raise TealerException(f"You can't register {cls.__name__} twice.")
+    from tealer.teal.functions import Function
 
 
 # disable pylint errors for now. errors should be resolved after completing refactoring.
@@ -78,35 +42,47 @@ class Teal:  # pylint: disable=too-many-instance-attributes,too-many-public-meth
 
     def __init__(  # pylint: disable=too-many-arguments
         self,
+        version: int,
+        mode: ExecutionMode,
         instructions: List[Instruction],
         bbs: List[BasicBlock],
-        version: int,
-        mode: ContractType,
         main: Subroutine,
-        # subroutines: List[List["BasicBlock"]],
         subroutines: Dict[str, Subroutine],
     ):
-        self._instructions = instructions
-        self._bbs = bbs
         self._version = version
         self._mode = mode
-        self._main = main
-        self._subroutines = subroutines
         self._int_constants: List[int] = []
         self._byte_constants: List[str] = []
 
+        self._instructions = instructions
+        self._bbs = bbs
+        self._main = main
+        self._subroutines = subroutines
+        self._functions: Dict[str, "Function"] = {}
+
         self._contract_name: str = ""
-        self._detectors: List[AbstractDetector] = []
-        self._printers: List[AbstractPrinter] = []
+        self._contract_type: ContractType = (
+            ContractType.ApprovalProgram
+            if mode == ExecutionMode.STATEFUL
+            else ContractType.LogicSig
+        )
 
     @property
     def instructions(self) -> List[Instruction]:
-        """List of instructions of the contract"""
+        """List of instructions of the contract
+
+        Returns:
+            List of instructions of the contract
+        """
         return self._instructions
 
     @property
     def bbs(self) -> List[BasicBlock]:
-        """CFG of the contract"""
+        """List of all basic blocks of the contract
+
+        Returns:
+            List of all basic blocks of the contract
+        """
         return self._bbs
 
     @property
@@ -116,6 +92,9 @@ def version(self) -> int:
         Version of the contract is based on whether the first instruction of the
         contract is #pragma version instruction or not. if it is, then version will
         be the teal version specified or else version will be 1.
+
+        Returns:
+            Returns the contract version.
         """
 
         return self._version
@@ -125,36 +104,77 @@ def version(self, ver: int) -> None:
         self._version = ver
 
     @property
-    def mode(self) -> ContractType:
-        """Type of the contract: Stateless, Stateful or Any.
+    def mode(self) -> ExecutionMode:
+        """Execution mode of the contract: Stateless, Stateful or Any.
 
         Mode is determined based on the instructions of the contract. if there are
         any instructions that are only supported in one kind of contract then that
         will be the contract type. if there aren't any such type of instructions, then
         this will be "Any".
+
+        Returns:
+            Returns the execution mode this contract can be executed successfully.
         """
 
         return self._mode
 
     @mode.setter
-    def mode(self, m: ContractType) -> None:
+    def mode(self, m: ExecutionMode) -> None:
         self._mode = m
 
+    @property
+    def contract_type(self) -> ContractType:
+        """Type of the contract: ApprovalProgram, ClearStateProgram or LogicSig
+
+        Returns:
+            Returns the type of the contract
+        """
+        return self._contract_type
+
+    @contract_type.setter
+    def contract_type(self, contract_type: ContractType) -> None:
+        self._contract_type = contract_type
+
     @property
     def main(self) -> "Subroutine":
-        "Returns subroutine representing the contract entry-point"
+        """Returns subroutine representing the contract entry-point
+
+        Returns:
+            Returns the subroutine object representing the non-subroutine blocks.
+        """
         return self._main
 
     @property
     def subroutines(self) -> Dict[str, "Subroutine"]:
-        """Returns dict of subroutine names and corresponding subroutine obj."""
+        """Returns dict of subroutine names and corresponding subroutine obj.
+
+        Returns:
+            Returns the subroutines in the contract. keys are the subroutine's name
+            and the value is the subroutine.
+        """
         return self._subroutines
 
     @property
     def subroutines_list(self) -> List["Subroutine"]:
-        """Returns list of all contract's subroutines"""
+        """Returns list of all contract's subroutines
+
+        Returns:
+            Returns the list of all subroutines in the contract.
+        """
         return list(self._subroutines.values())
 
+    @property
+    def functions(self) -> Dict[str, "Function"]:
+        return self._functions
+
+    @functions.setter
+    def functions(self, functions: Dict[str, "Function"]) -> None:
+        self._functions = functions
+
+    @property
+    def functions_list(self) -> List["Function"]:
+        return list(self._functions.values())
+
     @property
     def contract_name(self) -> str:
         return self._contract_name
@@ -164,7 +184,15 @@ def contract_name(self, name: str) -> None:
         self._contract_name = name
 
     def subroutine(self, name: str) -> Optional["Subroutine"]:
-        """Return subroutine with id/name `name`, return none if subroutine does not exist."""
+        """Return subroutine with id/name `name`, return none if subroutine does not exist.
+
+        Args:
+            name: name of the subroutine.
+
+        Returns:
+            Returns the subroutine object given its name. Returns None if the subroutine is
+            not in identified subroutines list.
+        """
         return self._subroutines.get(name, None)
 
     def get_int_constant(self, index: int) -> Tuple[bool, int]:
@@ -184,6 +212,9 @@ def get_int_constant(self, index: int) -> Tuple[bool, int]:
             intcblock only stores 3 values in the constant space and a intc instruction refers to 5th value. Execution
             will fail at runtime in this case. Return value will be `False` in this case as well.
 
+        Args:
+            index: The index into the int constants filled by the intcblock instruction.
+
         Returns:
             bool: True if Tealer was able to determine the value referred by that instruction or else False
             int: value referred by intc instruction at that index.
@@ -193,7 +224,17 @@ def get_int_constant(self, index: int) -> Tuple[bool, int]:
         return True, self._int_constants[index]
 
     def get_byte_constant(self, index: int) -> Tuple[bool, str]:
-        """Return []byte value stored by bytecblock instruction"""
+        """Return []byte value stored by bytecblock instruction
+
+        Behavior is same as `get_int_constant`.
+
+        Args:
+            index: The index into the byte constants filled by the bytecblock instruction.
+
+        Returns:
+            bool: True if Tealer was able to determine the value referred by that instruction or else False
+            int: value referred by bytec instruction at that index.
+        """
         if len(self._byte_constants) <= index:
             return False, ""
         return True, self._byte_constants[index]
@@ -203,101 +244,3 @@ def set_int_constants(self, int_constants: List[int]) -> None:
 
     def set_byte_constants(self, byte_constants: List[str]) -> None:
         self._byte_constants = byte_constants
-
-    # from slither: Slither Class in slither/slither.py
-    @property
-    def detectors(self) -> List[AbstractDetector]:
-        """return list of registered detectors."""
-        return self._detectors
-
-    # from slither: Slither Class in slither/slither.py
-    @property
-    def detectors_high(self) -> List[AbstractDetector]:
-        """return list of registered detectors with impact high"""
-        return [d for d in self._detectors if d.IMPACT == DetectorClassification.HIGH]
-
-    # from slither: Slither Class in slither/slither.py
-    @property
-    def detectors_medium(self) -> List[AbstractDetector]:
-        """return list of registered detectors with impact medium"""
-        return [d for d in self._detectors if d.IMPACT == DetectorClassification.MEDIUM]
-
-    # from slither: Slither Class in slither/slither.py
-    @property
-    def detectors_low(self) -> List[AbstractDetector]:
-        """return list of registered detectors with impact low"""
-        return [d for d in self._detectors if d.IMPACT == DetectorClassification.LOW]
-
-    # from slither: Slither Class in slither/slither.py
-    @property
-    def detectors_informational(self) -> List[AbstractDetector]:
-        """return list of registered detectors with impact informational"""
-        return [d for d in self._detectors if d.IMPACT == DetectorClassification.INFORMATIONAL]
-
-    # from slither: Slither Class in slither/slither.py
-    @property
-    def detectors_optimization(self) -> List[AbstractDetector]:
-        """return list of registered detectors with impact optimization"""
-        return [d for d in self._detectors if d.IMPACT == DetectorClassification.OPTIMIZATION]
-
-    # from slither: Slither Class in slither/slither.py
-    @property
-    def printers(self) -> List[AbstractPrinter]:
-        """return list of registered printers."""
-        return self._printers
-
-    # from slither: Slither Class in slither/slither.py
-    def register_detector(self, detector_class: Type[AbstractDetector]) -> None:
-        """Register detector to run on the contract.
-
-        Args:
-            detector_class: Class representing the detector.
-        """
-
-        _check_common_things("detector", detector_class, AbstractDetector, self._detectors)
-
-        instance = detector_class(self)
-        self._detectors.append(instance)
-
-    # from slither: Slither Class in slither/slither.py
-    def register_printer(self, printer_class: Type[AbstractPrinter]) -> None:
-        """Register printer to run on the contract.
-
-        Args:
-            printer_class: Class representing the printer.
-        """
-
-        _check_common_things("printer", printer_class, AbstractPrinter, self._printers)
-
-        instance = printer_class(self)
-        self._printers.append(instance)
-
-    # from slither: Slither Class in slither/slither.py
-    def run_detectors(self) -> List["SupportedOutput"]:
-        """Run all the registered detectors.
-
-        Returns:
-            List of results, each result corresponds to the output
-            of a single detector.
-        """
-
-        results = [d.detect() for d in self._detectors]
-
-        return results
-
-    # from slither: Slither Class in slither/slither.py
-    def run_printers(self, dest: Optional[Path] = None) -> List:
-        """Run all the registered printers.
-
-        Args:
-            dest: Optional. :dest: is used by printers to determine
-                the destination directory to save the output files.
-                if :dest: is None, current directory is used as destination
-                directory.
-
-        Returns:
-            List of results, each result corresponds to the output
-            of a single printer.
-        """
-
-        return [p.print(dest) for p in self._printers]
diff --git a/tealer/tealer.py b/tealer/tealer.py
new file mode 100644
index 0000000..a447588
--- /dev/null
+++ b/tealer/tealer.py
@@ -0,0 +1,204 @@
+import logging
+from typing import List, TYPE_CHECKING, Dict, Any, Type
+
+
+from tealer.detectors.abstract_detector import AbstractDetector, DetectorClassification
+from tealer.printers.abstract_printer import AbstractPrinter
+from tealer.exceptions import TealerException
+
+
+if TYPE_CHECKING:
+    from tealer.teal.teal import Teal
+    from tealer.utils.output import ListOutput
+    from tealer.execution_context.transactions import GroupTransaction
+
+
+# from slither: slither/slither.py
+def _check_common_things(
+    thing_name: str, cls: Any, base_cls: Any, instance_list: List[Any]
+) -> None:
+    """Check if the class is correct subclass and is unique.
+
+    check if :cls: is subclass of :base_cls: and it's instance is not already
+    present in :instance_list:. if either of the conditions fail, then an
+    exception is raised.
+
+    Args:
+        thing_name: name of the feature(add-on) that will on the teal
+            contract("detector" or "printer"). Used for error reporting.
+        cls: Class representing the feature.
+        base_cls: Base class for the feature.
+        instance_list: List of objects already registered to run.
+
+    Raises:
+        TealerException: raises exception if the :cls: is not subclass of
+            :base_cls: or if an object of :cls: is present in the
+            :instance_list:.
+    """
+
+    if not issubclass(cls, base_cls) or cls is base_cls:
+        raise TealerException(
+            f"You can't register {cls.__name__} as a {thing_name}."
+            f"You need to pass a class that inherits from {base_cls.__name__}"
+        )
+
+    if any(isinstance(obj, cls) for obj in instance_list):
+        raise TealerException(f"You can't register {cls.__name__} twice.")
+
+
+class Tealer:
+    """Base class for the tool"""
+
+    def __init__(
+        self,
+        contracts: Dict[str, "Teal"],
+        groups: List["GroupTransaction"],
+        output_group: bool = False,
+    ):
+        self._contracts = contracts
+        self._groups = groups
+
+        self._detectors: List[AbstractDetector] = []
+        self._printers: List[AbstractPrinter] = []
+        # debug parameter to decide on the type of output reported by the detectors. TODO: Remove later.
+        self._output_group: bool = output_group
+
+    @property
+    def contracts(self) -> Dict[str, "Teal"]:
+        return self._contracts
+
+    @property
+    def contracts_list(self) -> List["Teal"]:
+        return list(self._contracts.values())
+
+    @property
+    def groups(self) -> List["GroupTransaction"]:
+        return self._groups
+
+    @property
+    def output_group(self) -> bool:
+        return self._output_group
+
+    # from slither: Slither Class in slither/slither.py
+    @property
+    def detectors(self) -> List[AbstractDetector]:
+        """return list of registered detectors.
+
+        Returns:
+            Returns the list of registered detectors.
+        """
+        return self._detectors
+
+    # from slither: Slither Class in slither/slither.py
+    @property
+    def detectors_high(self) -> List[AbstractDetector]:
+        """return list of registered detectors with impact high
+
+        Returns:
+            Returns the list of registered detectors with impact high.
+        """
+        return [d for d in self._detectors if d.IMPACT == DetectorClassification.HIGH]
+
+    # from slither: Slither Class in slither/slither.py
+    @property
+    def detectors_medium(self) -> List[AbstractDetector]:
+        """return list of registered detectors with impact medium
+
+        Returns:
+            Returns the list of registered detectors with impact medium.
+        """
+        return [d for d in self._detectors if d.IMPACT == DetectorClassification.MEDIUM]
+
+    # from slither: Slither Class in slither/slither.py
+    @property
+    def detectors_low(self) -> List[AbstractDetector]:
+        """return list of registered detectors with impact low
+
+        Returns:
+            Returns the list of registered detectors with impact low.
+        """
+        return [d for d in self._detectors if d.IMPACT == DetectorClassification.LOW]
+
+    # from slither: Slither Class in slither/slither.py
+    @property
+    def detectors_informational(self) -> List[AbstractDetector]:
+        """return list of registered detectors with impact informational
+
+        Returns:
+            Returns the list of registered detectors with impact informational.
+        """
+        return [d for d in self._detectors if d.IMPACT == DetectorClassification.INFORMATIONAL]
+
+    # from slither: Slither Class in slither/slither.py
+    @property
+    def detectors_optimization(self) -> List[AbstractDetector]:
+        """return list of registered detectors with impact optimization
+
+        Returns:
+            Returns the list of registered detectors with impact optimization.
+        """
+        return [d for d in self._detectors if d.IMPACT == DetectorClassification.OPTIMIZATION]
+
+    # from slither: Slither Class in slither/slither.py
+    @property
+    def printers(self) -> List[AbstractPrinter]:
+        """return list of registered printers.
+
+        Returns:
+            Returns the list of registered printers.
+        """
+        return self._printers
+
+    # from slither: Slither Class in slither/slither.py
+    def register_detector(self, detector_class: Type[AbstractDetector]) -> None:
+        """Register detector to run on the contract.
+
+        Args:
+            detector_class: Class representing the detector.
+        """
+
+        _check_common_things("detector", detector_class, AbstractDetector, self._detectors)
+
+        instance = detector_class(self)
+        self._detectors.append(instance)
+
+    # from slither: Slither Class in slither/slither.py
+    def register_printer(self, printer_class: Type[AbstractPrinter]) -> None:
+        """Register printer to run on the contract.
+
+        Args:
+            printer_class: Class representing the printer.
+        """
+
+        _check_common_things("printer", printer_class, AbstractPrinter, self._printers)
+        # Run on the first contract for now. TODO: decide on how printers should behave in group context.
+        instance = printer_class(list(self.contracts.values())[0])
+        self._printers.append(instance)
+
+    # from slither: Slither Class in slither/slither.py
+    def run_detectors(self) -> List["ListOutput"]:
+        """Run all the registered detectors.
+
+        Returns:
+            List of results, each result corresponds to the output
+            of a single detector.
+        """
+
+        results = []
+        logger = logging.getLogger("Tealer")
+        for d in self._detectors:
+            logger.debug(f'[+] Running detector "{d.NAME}"')
+            results.append(d.detect())
+
+        return results
+
+    # from slither: Slither Class in slither/slither.py
+    def run_printers(self) -> List:
+        """Run all the registered printers.
+
+        Returns:
+            List of results, each result corresponds to the output
+            of a single printer.
+        """
+
+        return [p.print() for p in self._printers]
diff --git a/tealer/utils/algoexplorer.py b/tealer/utils/algoexplorer.py
index f2c8950..9333e0e 100644
--- a/tealer/utils/algoexplorer.py
+++ b/tealer/utils/algoexplorer.py
@@ -46,7 +46,18 @@
 
 
 def disassemble_using_algo_explorer(code: str) -> str:
-    """Disassemble the AVM bytecode to teal using Algorand node v2 api."""
+    """Disassemble the AVM bytecode to teal using Algorand node v2 api.
+
+    Args:
+        code: Compiled bytecode of the contract.
+
+    Returns:
+        Returns disassemble Teal code of :code:.
+
+    Raises:
+        TealerException: Raises error if the api returned any status code other than the
+            success code (200).
+    """
     base = "https://node.algoexplorerapi.io/"
     url = "v2/teal/disassemble"
     headers = {"Content-Type": "application/x-binary"}
@@ -79,7 +90,19 @@ def get_indexer(network: str) -> IndexerClient:
 
 
 def get_application_using_app_id(network: str, app_id: int) -> str:
-    """Downloads approval program using the application id"""
+    """Downloads approval program using the application id
+
+    Args:
+        network: Algorand network identifier.
+        app_id: Application Id.
+
+    Returns:
+        Returns disassembled Teal code of the :app_id: application fetched from the :network:.
+
+    Raises:
+        TealerException: Raises error if the function cannot find the application :app_id: in the
+            :network:.
+    """
     try:
         response = get_indexer(network).applications(application_id=app_id)  # type: ignore
     except IndexerHTTPError as e:
@@ -97,6 +120,19 @@ def logic_sig_from_contract_account(network: str, account_address: str) -> str:
 
     In order to find the logic sig, find a transaction from the contract account and
     get the logic sig from that.
+
+    Args:
+        network: Algorand network identifier.
+        account_address: An Algorand account address derived from a LogicSig.
+
+    Returns:
+        Returns disassembled Teal code of the contract controlling the contract account :account_address:.
+        Contract is searched in the :network:.
+
+    Raises:
+        TealerException: Raises error if the function cannot find a transaction from the :account_address: in the
+            :network:. Happens if the account_address is invalid, account has not made any transactions or the
+            search has timed out.
     """
     try:
         results = get_indexer(network).search_transactions(  # type: ignore
@@ -126,7 +162,20 @@ def logic_sig_from_contract_account(network: str, account_address: str) -> str:
 
 
 def logic_sig_from_txn_id(network: str, txn_id: str) -> str:
-    """Fetch logic-sig used to sign `txn_id` transaction"""
+    """Fetch logic-sig used to sign `txn_id` transaction
+
+    Args:
+        network: Algorand network identifier.
+        txn_id: Transaction id of a transaction on :network:, the transaction is signed by a logic-sig.
+
+    Returns:
+        Returns disassembled Teal code of the contract that signed the transaction :txn_id:.
+        The transaction is searched in the :network:.
+
+    Raises:
+        TealerException: Raises error if the function cannot find the transaction :txn_id: in the
+            :network:
+    """
     try:
         txn_info = get_indexer(network).transaction(txn_id)  # type: ignore
     except IndexerHTTPError as e:
diff --git a/tealer/utils/analyses.py b/tealer/utils/analyses.py
index a3f13f4..d2299b3 100644
--- a/tealer/utils/analyses.py
+++ b/tealer/utils/analyses.py
@@ -1,32 +1,47 @@
-from typing import Optional, Type as TypingType, List, Tuple, Union
+from typing import Optional, List, Tuple, Union, TYPE_CHECKING
 
-from tealer.teal.basic_blocks import BasicBlock
-from tealer.teal.global_field import ZeroAddress
 from tealer.teal.instructions.instructions import (
     PushInt,
-    Txn,
-    Global,
     Instruction,
-    Addr,
     Int,
-    Eq,
-    Neq,
-    Return,
-    BZ,
-    BNZ,
-    Assert,
     Byte,
     PushBytes,
     IntcInstruction,
     BytecInstruction,
 )
-from tealer.teal.instructions.transaction_field import TransactionField, OnCompletion, ApplicationID
 
 from tealer.exceptions import TealerException
 
+if TYPE_CHECKING:
+    from tealer.teal.basic_blocks import BasicBlock
+    from tealer.teal.functions import Function
+
 
 def is_int_push_ins(ins: Instruction) -> Tuple[bool, Optional[Union[int, str]]]:
-    """Return true if :ins: pushes a int literal on to the stack"""
+    """Return true if :ins: pushes a int literal on to the stack
+
+    Args:
+        ins: An instruction.
+
+    Returns:
+        Returns "pushes_int" and "value".
+
+        pushes_int is True if the instruction pushes an uint64 value during execution.
+        Otherwise, it is False.
+
+        value is always None if pushes_int is False.
+        value is None when pushes_int is True but the Tealer cannot compute the pushed value.
+        value will be not None if Tealer can compute it.
+
+        value will be a string if it is a named constant. The named constants are converted to
+        integers by the assembler. Tealer directly returns the string instead of finding the compiled
+        integer.
+
+        value will be an int if the value is an integer in the Teal code.
+
+    Raises:
+        TealerException: Raises error if ins.bb or ins.bb.teal are not initialized properly.
+    """
     if isinstance(ins, Int) or isinstance(  # pylint: disable=consider-merging-isinstance
         ins, PushInt
     ):
@@ -44,7 +59,24 @@ def is_int_push_ins(ins: Instruction) -> Tuple[bool, Optional[Union[int, str]]]:
 
 
 def is_byte_push_ins(ins: Instruction) -> Tuple[bool, Optional[str]]:
-    """Return true if :ins: pushes a byte literal on to the stack."""
+    """Return true if :ins: pushes a byte literal on to the stack.
+
+    Args:
+        ins: An instruction.
+
+    Returns:
+        Returns "pushes_byte_value" and "value".
+
+        pushes_byte_value is True if the instruction pushes an uint64 value during execution.
+        Otherwise, it is False.
+
+        value is always None if pushes_byte_value is False.
+        value is None when pushes_byte_value is True but the Tealer cannot compute the pushed value.
+        value will be the string if Tealer can compute it.
+
+    Raises:
+        TealerException: Raises error if ins.bb or ins.bb.teal are not initialized properly.
+    """
     if isinstance(ins, (Byte, PushBytes)):
         return True, ins.value
     if isinstance(ins, BytecInstruction):
@@ -59,304 +91,70 @@ def is_byte_push_ins(ins: Instruction) -> Tuple[bool, Optional[str]]:
     return False, None
 
 
-def check_if_txn_field_against_address(
-    ins1: Instruction, ins2: Instruction, tx_field: TypingType[TransactionField]
-) -> bool:
-    """Check if the (ins1, ins2) is on the form
-    TXN [tx_field]
-    GLOBAL ZeroAddress | Addr
-
-    Args:
-        ins1: first instruction.
-        ins2: second instruction,,
-        tx_field: Type of transaction field that is checked against
+def next_blocks_global(function: "Function", block: "BasicBlock") -> List["BasicBlock"]:
+    """Return basic blocks next to this block in the global CFG.
 
-    Returns:
-        True if (ins1,ins2) is (TXN tx_field, Global ZeroAddress | Addr).
-    """
-    if isinstance(ins1, Txn) and isinstance(ins1.field, tx_field):
-        if isinstance(ins2, Global) and isinstance(ins2.field, ZeroAddress):
-            return True
-        if isinstance(ins2, Addr):
-            return True
-    return False
-
-
-def check_txn_field_address_equality(
-    ins: Instruction, stack: List[Instruction], tx_field: TypingType[TransactionField]
-) -> bool:
-    """Check if the (ins1), [ins2, ins3] is on the form
-    TXN [tx_field]
-    GLOBAL ZeroAddress | Addr
-    EQ|NEQ
-
-    or
-    GLOBAL ZeroAddress | Addr
-    TXN [tx_field]
-    EQ|NEQ
+    global CFG is the single CFG representing the entire contract with callsub blocks connected
+    to the subroutine entry blocks and retsub blocks of the subroutine connected to return point block.
 
     Args:
-        ins1: first instruction.
-        stack: list of additional instruction,
-        tx_field: Type of transaction field that is checked against
+        function: The function
+        block: A basic block.
 
     Returns:
-        True if (ins1), [ins2, ins3] follows the pattern.
-        False if stack does not have +two elements, or if the pattern is not followed
+        Returns the next basic blocks of :block: in the global CFG. The global CFG is the CFG where
+        callsub_blocks are connected to the subroutine entry blocks.
     """
-    if isinstance(ins, (Eq, Neq)) and len(stack) >= 2:
-        one = stack[-1]
-        two = stack[-2]
-        if check_if_txn_field_against_address(
-            one, two, tx_field
-        ) or check_if_txn_field_against_address(two, one, tx_field):
-            return True
-    return False
-
-
-def detect_missing_txn_check(
-    tx_field: TypingType[TransactionField],
-    bb: BasicBlock,
-    current_path: List[BasicBlock],
-    paths_without_check: List[List[BasicBlock]],
-) -> None:
-    """Find execution paths with missing txn check. The check is on the form
-
-    TXN [tx_field]
-    GLOBAL ZeroAddress | Addr
-    EQ|NEQ
-
-    or
-    GLOBAL ZeroAddress | Addr
-    TXN [tx_field]
-    EQ|NEQ
-
-    The function starts from bb, and iterate until analyzing all the paths.
-    The results are saved in paths_without_check
-
-    Args:
-        bb: Current basic block being checked(whose execution is simulated.)
-        current_path: Basic block already explored.
-        paths_without_check:
-            This is a
-            "in place" argument. Vulnerable paths found by this function are
-            appended to this list.
-    """
-
-    # check for loops
-    if bb in current_path:
-        return
-
-    current_path = current_path + [bb]
-
-    stack: List["Instruction"] = []
-
-    for ins in bb.instructions:
-        if isinstance(ins, Return):
-            if len(ins.prev) == 1:
-                prev = ins.prev[0]
-                is_int_push, value = is_int_push_ins(prev)
-                if is_int_push and value == 0:
-                    return
-
-            paths_without_check.append(current_path)
-            return
-
-        if check_txn_field_address_equality(ins, stack, tx_field):
-            return
-
-        stack.append(ins)
-
-    for next_bb in bb.next:
-        detect_missing_txn_check(tx_field, next_bb, current_path, paths_without_check)
+    if block.is_retsub_block:
+        return function.return_point_blocks(block.subroutine)
+    if block.is_callsub_block:
+        return [block.called_subroutine.entry]
+    return block.next
 
 
-def is_oncompletion_check(ins1: Instruction, ins2: Instruction, checked_values: List[str]) -> bool:
-    """Check if the instructions form OnCompletion check with value in the checked values.
+def prev_blocks_global(function: "Function", block: "BasicBlock") -> List["BasicBlock"]:
+    """Return basic blocks previous to this block in the global CFG.
 
-    OnCompletion transaction field stores the type of the application transaction
-    that invoked the contract execution.
+    global CFG is the single CFG representing the entire contract with callsub blocks connected
+    to the subroutine entry blocks and retsub blocks of the subroutine connected to return point block.
 
     Args:
-        ins1: First instruction of the execution sequence that is supposed
-            to form this comparison check .
-        ins2: Second instruction in the execution sequence, will be executed
-            right after :ins1:.
-        checked_values: List of values accepted on the on completion check
+        function: The function
+        block: A basic block.
 
     Returns:
-        True if the given instructions :ins1:, :ins2: form a OnCompletion check
-        using the checked values. True if :ins1: is txn OnCompletion and
-        :ins2: is int (checked_values).
+        Returns the previous basic blocks of :block: in the global CFG. The global CFG is the CFG where
+        callsub_blocks are connected to the subroutine entry blocks.
     """
-    # Adds integer values along with names for Named Integer Constants of txn OnCompletion
-    ENUM_NAMES_TO_INT = {
-        "NoOp": 0,
-        "OptIn": 1,
-        "CloseOut": 2,
-        "ClearState": 3,
-        "UpdateApplication": 4,
-        "DeleteApplication": 5,
-    }
-    integer_checked_values: List[int] = []
-    for named_constant in checked_values:
-        if named_constant in ENUM_NAMES_TO_INT:
-            integer_checked_values.append(ENUM_NAMES_TO_INT[named_constant])
-
-    if isinstance(ins1, Txn) and isinstance(ins1.field, OnCompletion):
-        is_int_push, value = is_int_push_ins(ins2)
-        return is_int_push and (value in checked_values or value in integer_checked_values)
-    return False
-
-
-def is_application_creation_check(ins1: Instruction, ins2: Instruction) -> bool:
-    """Check if the instructions form application creation check.
-
-    ApplicationID will be 0 at the time of creation as a result the condition
-    txn ApplicationID == int 0 is generally used to do intialization operations
-    at the time of application creation. Updating or Deleting application isn't
-    possible if the transaction is a application creation check. Using this check
-    allows the UpdateApplication, DeleteApplication detector to not explore(pruning)
-    paths where this check is true.
+    assert block.teal is not None
+    if block == block.subroutine.entry:
+        # if the block is the entry of the subroutine, return all blocks calling the subroutine
+        if block.subroutine != function.main:
+            return function.caller_blocks(block.subroutine)
+        # the block is the main entry block of the contract
+        return []
+    if block.is_sub_return_point:
+        # if the block is the return point of the subroutine, return all retsub blocks of the subroutine
+        return block.callsub_block.called_subroutine.retsub_blocks
+    # if its a normal block return previous blocks in the CFG.
+    return block.prev
+
+
+# TODO: Rename this to is_leaf_block_global
+def leaf_block_global(block: "BasicBlock") -> bool:
+    """Return True if block is a leaf block in the global CFG.
+
+    global CFG is the single CFG representing the entire contract with callsub blocks connected
+    to the subroutine entry blocks and retsub blocks of the subroutine connected to return point block.
 
     Args:
-        ins1: First instruction of the execution sequence that is supposed
-            to form a comparison check for application creation.
-        ins2: Second instruction in the execution sequence, will be executed
-            right after :ins1:.
+        block: A basic block.
 
     Returns:
-        True if the given instructions :ins1:, :ins2: form a application creation
-        check i.e True if :ins1: is txn ApplicationID and :ins2: is
-        int 0.
+        Returns True if :block: is a leaf block in the global CFG. otherwise, False.
     """
-
-    if isinstance(ins1, Txn) and isinstance(ins1.field, ApplicationID):
-        is_int_push, value = is_int_push_ins(ins2)
-        return is_int_push and value == 0
-    return False
-
-
-def detect_missing_on_completion(  # pylint: disable=too-many-branches, too-many-locals
-    bb: BasicBlock,
-    current_path: List[BasicBlock],
-    paths_without_check: List[List[BasicBlock]],
-    on_completion_check_to_have: str,
-    on_completion_checks_to_follow: List[str],
-) -> None:
-    """Find execution paths with missing on completion check.
-
-    This function recursively explores the Control Flow Graph(CFG) of the
-    contract and reports execution paths with missing on completion
-    check. All paths must have some forms of
-
-    txn OnCompletion
-    int [on_completion_check_to_have]
-
-    If there are txn OnCompletion, where the associated int is from
-
-    This function is "in place", modifies arguments with the data it is on_completion_checks_to_follow,
-    We follow only one branch (based on the BZ/BNZ)
-
-    Args:
-        bb: Current basic block being checked(whose execution is simulated.)
-        current_path: Current execution path being explored.
-        paths_without_check:
-            Execution paths with missing UpdateApplication check. This is a
-            "in place" argument. Vulnerable paths found by this function are
-            appended to this list.
-        on_completion_check_to_have: completion check to have
-        on_completion_checks_to_follow: completion checks to follow
-    """
-
-    # check for loops
-    if bb in current_path:
-        return
-
-    current_path = current_path + [bb]
-
-    # prev_was_oncompletion = False
-    # prev_was_int = False
-    prev_was_equal = False
-    prev_was_txn_application_id = False
-    skip_false = False
-    skip_true = False
-    stack: List[Instruction] = []
-
-    for ins in bb.instructions:
-
-        if isinstance(ins, Return):
-            if len(ins.prev) == 1:
-                prev = ins.prev[0]
-                is_int_push, value = is_int_push_ins(prev)
-                if is_int_push and value == 0:
-                    return
-
-            paths_without_check.append(current_path)
-            return
-
-        # return if a mutually exclusive condition is asserted
-        # i.e txn OnCompletion is asserted against one of the other possible value or
-        # if application creation check is asserted
-        if isinstance(ins, Assert) and prev_was_equal:
-            return
-
-        skip_false = isinstance(ins, BNZ) and prev_was_equal
-        skip_true = isinstance(ins, BZ) and prev_was_equal
-        prev_was_equal = False
-
-        # detect the following pattern of application creation check and prune branches
-        # where the application creation check is true
-        # txn ApplicationID
-        # [bz | bnz] LABEL
-        skip_false = skip_false or (isinstance(ins, BZ) and prev_was_txn_application_id)
-        skip_true = skip_true or (isinstance(ins, BNZ) and prev_was_txn_application_id)
-        prev_was_txn_application_id = False
-
-        if isinstance(ins, Txn) and isinstance(ins.field, ApplicationID):
-            prev_was_txn_application_id = True
-
-        if isinstance(ins, Eq) and len(stack) >= 2:
-            one = stack[-1]
-            two = stack[-2]
-            if is_oncompletion_check(
-                one, two, [on_completion_check_to_have]
-            ) or is_oncompletion_check(two, one, [on_completion_check_to_have]):
-                return
-            # TODO: evaluate if we really need to following, or just consider
-            # Any check where on_completion_check_to_have is not present
-            if is_oncompletion_check(
-                one, two, on_completion_checks_to_follow
-            ) or is_oncompletion_check(two, one, on_completion_checks_to_follow):
-                prev_was_equal = True
-            if is_application_creation_check(one, two) or is_application_creation_check(two, one):
-                prev_was_equal = True
-
-        stack.append(ins)
-
-    if skip_false:
-        detect_missing_on_completion(
-            bb.next[0],
-            current_path,
-            paths_without_check,
-            on_completion_check_to_have,
-            on_completion_checks_to_follow,
-        )
-        return
-    if skip_true:
-        detect_missing_on_completion(
-            bb.next[1],
-            current_path,
-            paths_without_check,
-            on_completion_check_to_have,
-            on_completion_checks_to_follow,
-        )
-        return
-    for next_bb in bb.next:
-        detect_missing_on_completion(
-            next_bb,
-            current_path,
-            paths_without_check,
-            on_completion_check_to_have,
-            on_completion_checks_to_follow,
-        )
+    # Retsub blocks will not have any next blocks but are not considered as leaf blocks in global CFG.
+    # Callsub block will have a next block, which is executed after executing the subroutine. However, when
+    # Callsub block is the last block in the source code and the subroutine exits the program using return instruction,
+    # Then callsub block will not have a next block but it is not considered a leaf block in global CFG.
+    return len(block.next) == 0 and not block.is_retsub_block and not block.is_callsub_block
diff --git a/tealer/utils/code_complexity.py b/tealer/utils/code_complexity.py
index 58c701d..13ee3d7 100644
--- a/tealer/utils/code_complexity.py
+++ b/tealer/utils/code_complexity.py
@@ -9,8 +9,12 @@
         it's Control Flow Graph(CFG) :cfg:.
 
 """
+# pylint: skip-file
+# mypy: ignore-errors
+# TODO: Functions in this module are not used anywhere. Have to decide on how to calculate the code complexity.
 
 from typing import List, TYPE_CHECKING
+from tealer.utils.analyses import next_blocks_global, prev_blocks_global
 
 if TYPE_CHECKING:
     from tealer.teal.basic_blocks import BasicBlock
@@ -29,7 +33,7 @@ def _compute_number_edges(cfg: List["BasicBlock"]) -> int:
 
     n = 0
     for bb in cfg:
-        n += len(bb.next)
+        n += len(next_blocks_global(bb))
     return n
 
 
@@ -61,7 +65,7 @@ def _compute_strongly_connected_components(cfg: List["BasicBlock"]) -> List[List
     def visit(bb: "BasicBlock") -> None:
         if not visited[bb]:
             visited[bb] = True
-            for next_bb in bb.next:
+            for next_bb in next_blocks_global(bb):
                 visit(next_bb)
             l.append(bb)
 
@@ -72,7 +76,7 @@ def assign(bb: "BasicBlock", root: List["BasicBlock"]) -> None:
         if not assigned[bb]:
             assigned[bb] = True
             root.append(bb)
-            for prev_bb in bb.prev:
+            for prev_bb in prev_blocks_global(bb):
                 assign(prev_bb, root)
 
     for bb in l:
diff --git a/tealer/utils/command_line/__init__.py b/tealer/utils/command_line/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tealer/utils/command_line.py b/tealer/utils/command_line/command_output.py
similarity index 65%
rename from tealer/utils/command_line.py
rename to tealer/utils/command_line/command_output.py
index f83ce96..02e00f8 100644
--- a/tealer/utils/command_line.py
+++ b/tealer/utils/command_line/command_output.py
@@ -1,21 +1,4 @@
-"""Output util functions used by command line argument handlers.
-
-This module defines functions which are mostly used while handling
-or displaying information based on the user chosen command line
-arguments.
-
-Functions:
-    output_detectors(detector_classes: List[Type[AbstractDetector]]) -> None:
-        Print information of given detectors in the form of a table.
-
-    output_printers(printer_classes: List[Type[AbstractPrinter]]) -> None:
-        Print information of given printers in the form of a table.
-
-"""
-import inspect
-
-from typing import List, Type, Tuple
-from pkg_resources import iter_entry_points  # type: ignore
+from typing import List, Type
 from prettytable import PrettyTable
 
 from tealer.detectors.abstract_detector import (
@@ -24,77 +7,6 @@
     classification_txt,
 )
 from tealer.printers.abstract_printer import AbstractPrinter
-from tealer.detectors import all_detectors
-from tealer.printers import all_printers
-from tealer.exceptions import TealerException
-
-
-def collect_plugins() -> Tuple[List[Type[AbstractDetector]], List[Type[AbstractPrinter]]]:
-    """collect detectors and printers installed in form of plugins.
-
-    plugins are collected using the entry point group `teal_analyzer.plugin`.
-    The entry point of each plugin has to return tuple containing list of detectors and
-    list of printers defined in the plugin when called.
-
-    Returns:
-        (Tuple[List[Type[AbstractDetector]], List[Type[AbstractPrinter]]]): detectors and
-        printers added in the form of plugins.
-
-    """
-    detector_classes: List[Type[AbstractDetector]] = []
-    printer_classes: List[Type[AbstractPrinter]] = []
-    for entry_point in iter_entry_points(group="teal_analyzer.plugin", name=None):
-        make_plugin = entry_point.load()
-
-        plugin_detectors, plugin_printers = make_plugin()
-        detector = None
-        if not all(issubclass(detector, AbstractDetector) for detector in plugin_detectors):
-            raise TealerException(
-                f"Error when loading plugin {entry_point}, {detector} is not a detector"
-            )
-        printer = None
-        if not all(issubclass(printer, AbstractPrinter) for printer in plugin_printers):
-            raise TealerException(
-                f"Error when loading plugin {entry_point}, {printer} is not a printer"
-            )
-
-        detector_classes += plugin_detectors
-        printer_classes += plugin_printers
-
-    return detector_classes, printer_classes
-
-
-# from slither: slither/__main__.py
-def get_detectors_and_printers() -> Tuple[
-    List[Type[AbstractDetector]], List[Type[AbstractPrinter]]
-]:
-    """Get list of detectors and printers available to tealer.
-
-    Detectors, Printers are considered available to tealer either if they
-    are defined in the tealer itself or if they are defined in one of the
-    tealer plugins installed in the system.
-
-    Returns:
-        list of detectors, list of printers defined in tealer and plugins
-        combined.
-    """
-
-    detector_classes = [getattr(all_detectors, name) for name in dir(all_detectors)]
-    detector_classes = [
-        d for d in detector_classes if inspect.isclass(d) and issubclass(d, AbstractDetector)
-    ]
-
-    printer_classes = [getattr(all_printers, name) for name in dir(all_printers)]
-    printer_classes = [
-        d for d in printer_classes if inspect.isclass(d) and issubclass(d, AbstractPrinter)
-    ]
-
-    plugins_detectors, plugins_printers = collect_plugins()
-
-    detector_classes += plugins_detectors
-    printer_classes += plugins_printers
-
-    return detector_classes, printer_classes
 
 
 def _sort_detector_classes(
@@ -242,7 +154,13 @@ def extract_help(cls: Type[AbstractDetector]) -> str:
 
 
 def output_wiki(detector_classes: List[Type[AbstractDetector]], filter_wiki: str) -> None:
-    """Generate dectector documentation for github wiki."""
+    """Generate dectector documentation for github wiki.
+
+    Args:
+        detector_classes: List of detector classes.
+        filter_wiki: filter string used to filter the detector classes. if this string
+            is in a detector's name, then that detector is not included in the list.
+    """
     detectors_list = _sort_detector_classes(detector_classes)
 
     for detector in detectors_list:
diff --git a/tealer/utils/command_line/common.py b/tealer/utils/command_line/common.py
new file mode 100644
index 0000000..25f4633
--- /dev/null
+++ b/tealer/utils/command_line/common.py
@@ -0,0 +1,366 @@
+"""
+NOTE: Incorrect or Inaccurate documentation. ignore for now.
+
+tealer init
+
+positional_arguments: (+), list of filesystem paths
+
+optional_argument: --group-config yaml file
+
+tealer detect
+
+
+
+subcommands
+
+- `init`
+- `detect`
+- `print`
+
+#### init
+
+positional arguments:
+    programs        list of space-separated directories/files.
+
+optional argument:
+    --group-config  yaml configuration file.
+
+Purpose:
+
+program_files = []
+for program in programs:
+    if program is directory:
+        list all files with .teal extension and add them to program_files.
+    if program is file:
+        add it to program_files
+
+if group_config is not null:
+    read the config
+else:
+    create a empty config
+
+if a program file is already in group_config:
+    remove it from program_files
+
+For each program in program_files:
+    analyze the program and append its information to the group_config.
+
+Output updated group-config file
+"""
+
+import sys
+
+# from pathlib import Path
+import inspect
+import argparse
+
+from typing import List, Type, Tuple, TYPE_CHECKING, Dict
+from pkg_resources import iter_entry_points  # type: ignore
+
+from tealer.detectors.abstract_detector import (
+    AbstractDetector,
+)
+from tealer.printers.abstract_printer import AbstractPrinter
+from tealer.detectors import all_detectors
+from tealer.printers import all_printers
+from tealer.exceptions import TealerException
+
+# from tealer.utils.command_line.group_config import (
+#     read_config_from_file,
+# )
+from tealer.teal.parse_teal import parse_teal
+from tealer.teal.parse_functions import construct_function
+from tealer.utils.teal_enums import ContractType, contract_type_from_txt
+from tealer.tealer import Tealer
+
+# from tealer.utils.output import subroutine_to_dot
+from tealer.execution_context.transactions import (
+    Transaction,
+    GroupTransaction,
+    fill_group_relative_indexes,
+)
+from tealer.utils.command_line.group_config import USER_CONFIG_TRANSACTION_TYPES
+
+
+if TYPE_CHECKING:
+    from tealer.teal.functions import Function
+    from tealer.teal.teal import Teal
+    from tealer.utils.command_line.group_config import (
+        GroupConfig,
+        GroupConfigFunctionCall,
+    )
+
+
+def collect_plugins() -> Tuple[List[Type[AbstractDetector]], List[Type[AbstractPrinter]]]:
+    """collect detectors and printers installed in form of plugins.
+
+    plugins are collected using the entry point group `teal_analyzer.plugin`.
+    The entry point of each plugin has to return tuple containing list of detectors and
+    list of printers defined in the plugin when called.
+
+    Returns:
+        (Tuple[List[Type[AbstractDetector]], List[Type[AbstractPrinter]]]): detectors and
+        printers added in the form of plugins.
+
+    Raises:
+        TealerException: raises exception if a plugin's detector is not a subclass of AbstractDetector
+            or if a plugin's printer is not a subclass of AbstractPrinter.
+    """
+    detector_classes: List[Type[AbstractDetector]] = []
+    printer_classes: List[Type[AbstractPrinter]] = []
+    for entry_point in iter_entry_points(group="teal_analyzer.plugin", name=None):
+        make_plugin = entry_point.load()
+
+        plugin_detectors, plugin_printers = make_plugin()
+        detector = None
+        if not all(issubclass(detector, AbstractDetector) for detector in plugin_detectors):
+            raise TealerException(
+                f"Error when loading plugin {entry_point}, {detector} is not a detector"
+            )
+        printer = None
+        if not all(issubclass(printer, AbstractPrinter) for printer in plugin_printers):
+            raise TealerException(
+                f"Error when loading plugin {entry_point}, {printer} is not a printer"
+            )
+
+        detector_classes += plugin_detectors
+        printer_classes += plugin_printers
+
+    return detector_classes, printer_classes
+
+
+def get_detectors_and_printers() -> Tuple[
+    List[Type[AbstractDetector]], List[Type[AbstractPrinter]]
+]:
+    """Get list of detectors and printers available to tealer.
+
+    Detectors, Printers are considered available to tealer either if they
+    are defined in the tealer itself or if they are defined in one of the
+    tealer plugins installed in the system.
+
+    Returns:
+        list of detectors, list of printers defined in tealer and plugins
+        combined.
+    """
+
+    detector_classes = [getattr(all_detectors, name) for name in dir(all_detectors)]
+    detector_classes = [
+        d for d in detector_classes if inspect.isclass(d) and issubclass(d, AbstractDetector)
+    ]
+
+    printer_classes = [getattr(all_printers, name) for name in dir(all_printers)]
+    printer_classes = [
+        d for d in printer_classes if inspect.isclass(d) and issubclass(d, AbstractPrinter)
+    ]
+
+    plugins_detectors, plugins_printers = collect_plugins()
+
+    detector_classes += plugins_detectors
+    printer_classes += plugins_printers
+
+    return detector_classes, printer_classes
+
+
+# pylint: disable=too-many-branches
+def validate_command_line_options(args: argparse.Namespace) -> None:
+    """Validate command line options and print message and exit the program
+
+    - Only one of --init or --detect or --print must be selected
+    - init
+        - --contracts is required
+        - --group-config is optional
+    - detect
+        - Only one of --contracts or --group_config is selected
+
+    Args:
+        args: Command line arguments
+    """
+
+    def print_and_exit(message: str) -> None:
+        print(f"CommandLineError: {message}")
+        sys.exit(1)
+
+    no_of_subcommands = (
+        int(args.init is True)
+        + int(args.detectors_to_run is not None)
+        + int(args.printers_to_run is not None)
+        + int(args.regex is not None)
+    )
+    if no_of_subcommands != 1:
+        print('CommandLineError: "User must select only one of --init, --detect or --print"')
+        sys.exit(1)
+
+    if args.init:
+        if args.contracts is None:
+            print_and_exit(
+                "--contracts is required for --init. Use optional --group-config option to add new contracts to existing configuration"
+            )
+    elif args.detectors_to_run is not None:
+        if args.contracts is None and args.group_config is None:
+            print_and_exit("--detect requires one of --contracts or --group-config")
+        if args.contracts is not None and args.group_config is not None:
+            print_and_exit(
+                "--detect takes only one of --contracts or --group-config. Use --group-config with --init to add new contracts to existing configuration."
+            )
+    elif args.printers_to_run is not None:
+        if args.contracts is None and args.group_config is None:
+            print_and_exit("--print requires one of --contracts or --group-config")
+        if args.contracts is not None and args.group_config is not None:
+            print_and_exit(
+                "--print takes only one of --contracts or --group-config. Use --group-config with --init to add new contracts to existing configuration."
+            )
+
+        if args.json is not None:
+            print_and_exit("--json is not supported for --print.")
+
+    # TODO: update args.network. args.network defaults to mainnet and is never None.
+    # if args.network is not None and args.contracts is None:
+    #     print_and_exit("--network is not supported when --contracts is not supplied.")
+
+    if args.detectors_to_run is None:
+        if (
+            args.detectors_to_exclude is not None
+            or args.exclude_stateless
+            or args.exclude_stateful
+            or args.filter_paths is not None
+        ):
+            print_and_exit(
+                "--exclude, --exclude-stateless, --exclude-stateful and --filter-paths options are only available when --detect is selected."
+            )
+
+
+# def handle_detect(args: argparse.Namespace) -> None:
+# group_config = read_config_from_file(Path(args.group_config))
+# tealer = init_tealer_from_config(group_config)
+
+# for contract_name, contract in tealer.contracts.items():
+#     for function_name, function in contract.functions.items():
+#         print("None")
+# dot_file_name = f"{contract_name}_{function_name}.dot"
+# with open(dot_file_name, "w", encoding="utf-8") as f:
+#     f.write(subroutine_to_dot(function.main))
+
+# output = tealer
+
+
+# def list_contract_files(directory: Path) -> List[Path]:
+#     teal_files = []
+#     for file in directory.iterdir():
+#         if file.is_file() and file.name.endswith(".teal"):
+#             teal_files.append(directory / file)
+#     return teal_files
+
+
+def _get_function_from_config(
+    function_call_config: "GroupConfigFunctionCall", contracts: Dict[str, "Teal"]
+) -> "Function":
+    if function_call_config.contract not in contracts:
+        raise TealerException(f"{function_call_config.contract} not found in listed contracts")
+    contract_obj = contracts[function_call_config.contract]
+    if function_call_config.function not in contract_obj.functions:
+        raise TealerException(
+            f"{function_call_config.function} not found in {contract_obj.contract_name} functions."
+        )
+    return contract_obj.functions[function_call_config.function]
+
+
+# pylint: disable=too-many-locals
+def init_tealer_from_config(config: "GroupConfig") -> "Tealer":
+    contracts: Dict[str, "Teal"] = {}
+    for contract_config in config.contracts:
+        with open(contract_config.file_path, encoding="utf-8") as f:
+            teal = parse_teal(f.read(), contract_config.name)
+        given_contract_type = contract_type_from_txt(contract_config.contract_type)
+        teal.contract_type = given_contract_type
+        # ignore version and subroutines
+        contract_functions: Dict[str, "Function"] = {}
+        for function_config in contract_config.functions:
+            func = construct_function(teal, function_config.dispatch_path, function_config.name)
+            contract_functions[function_config.name] = func
+
+        teal.functions = contract_functions
+        contracts[contract_config.name] = teal
+
+    group_objs_list: List[GroupTransaction] = []
+    for txn_config in config.groups:
+        txn_id_to_obj: Dict[str, "Transaction"] = {}
+        for txn in txn_config.transactions:
+            txn_obj = Transaction()
+            txn_obj.transacton_id = txn.txn_id
+            txn_obj.type = USER_CONFIG_TRANSACTION_TYPES[txn.txn_type]
+            if txn.has_logic_sig is not None:
+                txn_obj.has_logic_sig = txn.has_logic_sig
+            txn_obj.absoulte_index = txn.absolute_index
+
+            if txn.application is not None:
+                app_function = _get_function_from_config(txn.application, contracts)
+                if app_function.contract.contract_type == ContractType.LogicSig:
+                    raise TealerException(
+                        f"{txn.application.contract} is a logic-sig but is given as an application."
+                    )
+                txn_obj.application = app_function
+
+            if txn.logic_sig is not None:
+                logic_sig_function = _get_function_from_config(txn.logic_sig, contracts)
+                if logic_sig_function.contract.contract_type != ContractType.LogicSig:
+                    raise TealerException(
+                        f"{txn.logic_sig.contract} is an application but is given as a logic-sig."
+                    )
+                txn_obj.logic_sig = logic_sig_function
+                txn_obj.has_logic_sig = True
+
+            if txn.txn_id in txn_id_to_obj:
+                raise TealerException(f"{txn.txn_id} is repeated in the same group.")
+            txn_id_to_obj[txn.txn_id] = txn_obj
+
+        group_obj = GroupTransaction()
+        group_obj.operation_name = txn_config.operation
+        group_obj.transactions = list(txn_id_to_obj.values())
+
+        for txn in txn_config.transactions:
+            txn_obj = txn_id_to_obj[txn.txn_id]
+            txn_obj.group_transaction = group_obj
+            if txn.relative_indexes is not None:
+                for other_txn_id in txn.relative_indexes:
+                    if other_txn_id not in txn_id_to_obj:
+                        raise TealerException(
+                            f"other_txn_id: {other_txn_id} is not present in the same group"
+                        )
+                    offset = txn.relative_indexes[other_txn_id]
+                    txn_obj.relative_indexes[offset] = txn_id_to_obj[other_txn_id]
+
+            if txn_obj.absoulte_index is not None:
+                if txn_obj.absoulte_index in group_obj.absolute_indexes:
+                    raise TealerException(
+                        f"Two transactions have same absolute index {txn_obj.transacton_id}, {group_obj.absolute_indexes[txn_obj.absoulte_index].transacton_id}"
+                    )
+                group_obj.absolute_indexes[txn_obj.absoulte_index] = txn_obj
+
+        fill_group_relative_indexes(group_obj)
+        group_objs_list.append(group_obj)
+
+    return Tealer(contracts, group_objs_list, output_group=True)
+
+
+def init_tealer_from_single_contract(contract_src: str, contract_name: str) -> "Tealer":
+    teal = parse_teal(contract_src, contract_name)
+    contracts: Dict[str, "Teal"] = {contract_name: teal}
+    contract_functions: Dict[str, "Function"] = {}
+    contract_functions[contract_name] = construct_function(teal, ["B0"], contract_name)
+    teal.functions = contract_functions
+
+    txn_obj = Transaction()
+    if teal.contract_type == ContractType.LogicSig:
+        txn_obj.transacton_id = contract_name
+        txn_obj.has_logic_sig = True
+        txn_obj.logic_sig = contract_functions[contract_name]
+    else:
+        # contract is either Approval or ClearState
+        txn_obj.application = contract_functions[contract_name]
+
+    group_obj = GroupTransaction()
+    group_obj.operation_name = contract_name
+    group_obj.transactions = [txn_obj]
+    group_objs_list = [group_obj]
+
+    return Tealer(contracts, group_objs_list)
diff --git a/tealer/utils/command_line/group_config.py b/tealer/utils/command_line/group_config.py
new file mode 100644
index 0000000..3d101c5
--- /dev/null
+++ b/tealer/utils/command_line/group_config.py
@@ -0,0 +1,327 @@
+import sys
+from dataclasses import dataclass
+from pathlib import Path
+from typing import List, Dict, Any, Optional
+import yaml
+
+from tealer.utils.teal_enums import TransactionType
+
+
+GROUP_CONFIG_CONTRACT_TYPES = [
+    "LogicSig",
+    "ApprovalProgram",
+    "ClearStateProgram",
+]
+
+USER_CONFIG_TRANSACTION_TYPES = {
+    "pay": TransactionType.Pay,
+    "keyreg": TransactionType.KeyReg,
+    "acfg": TransactionType.Acfg,
+    "axfer": TransactionType.Axfer,
+    "afrz": TransactionType.Afrz,
+    "appl": TransactionType.Appl,
+    "txn": TransactionType.Any,
+}
+
+
+class InvalidGroupConfiguration(Exception):
+    pass
+
+
+def check_fields_are_present(required_fields: List[str], config: Dict[str, Any]) -> List[str]:
+    absent_fields: List[str] = []
+    for field in required_fields:
+        if field not in config:
+            absent_fields.append(field)
+    return absent_fields
+
+
+@dataclass
+class GroupConfigFunction:
+    name: str
+    dispatch_path: List[str]
+
+    @staticmethod
+    def from_yaml(function: Dict[str, Any]) -> "GroupConfigFunction":
+        if "name" not in function:
+            raise InvalidGroupConfiguration("function name is not given")
+        name: str = function["name"]
+        absent_fields = check_fields_are_present(["dispatch_path"], function)
+        if absent_fields:
+            raise InvalidGroupConfiguration(
+                f'Function name: {name}\n\nFollowing Required fields are absent: {", ".join(absent_fields)}'
+            )
+
+        dispatch_path: List[str] = function["dispatch_path"]
+        for block_id in dispatch_path:
+            if not block_id.startswith("B") or not block_id[1:].isdigit():
+                raise InvalidGroupConfiguration(
+                    f"Function name: {name}\nIncorrect block id in dispatch path: {dispatch_path}"
+                )
+
+        return GroupConfigFunction(name, dispatch_path)
+
+    def to_yaml(self) -> Dict[str, Any]:
+        return {"name": self.name, "dispatch_path": self.dispatch_path}
+
+
+@dataclass
+class GroupConfigContract:
+    name: str
+    file_path: Path
+    contract_type: str
+    version: int
+    subroutines: List[str]
+    functions: List[GroupConfigFunction]
+
+    @staticmethod
+    def from_yaml(contract: Dict[str, Any]) -> "GroupConfigContract":
+        if "name" not in contract:
+            raise InvalidGroupConfiguration("contract name is not given")
+        name: str = contract["name"]
+
+        required_fields = ["file_path", "type", "version", "subroutines", "functions"]
+        absent_fields: List[str] = []
+        for field in required_fields:
+            if field not in contract:
+                absent_fields.append(field)
+        if absent_fields:
+            raise InvalidGroupConfiguration(
+                f'Contract name: {name}\n\nFollowing Required fields are absent: {", ".join(absent_fields)}'
+            )
+
+        file_path: Path = Path(contract["file_path"])
+        contract_type: str = contract["type"]
+        version: int = contract["version"]
+        subroutines: List[str] = contract["subroutines"]
+
+        if contract_type not in GROUP_CONFIG_CONTRACT_TYPES:
+            raise InvalidGroupConfiguration(
+                f"Contract name: {name}\n Invalid contract type: {contract_type}"
+            )
+
+        functions: List[Dict[str, Any]] = contract["functions"]
+        parsed_functions: List[GroupConfigFunction] = []
+
+        for function in functions:
+            try:
+                parsed_functions.append(GroupConfigFunction.from_yaml(function))
+            except InvalidGroupConfiguration as err:
+                # pylint: disable=raise-missing-from
+                raise InvalidGroupConfiguration(f"Contract name: {name}\n{err}")
+
+        return GroupConfigContract(
+            name, file_path, contract_type, version, subroutines, parsed_functions
+        )
+
+    def to_yaml(self) -> Dict[str, Any]:
+        return {
+            "name": self.name,
+            "file_path": self.file_path,
+            "type": self.contract_type,
+            "version": self.version,
+            "subroutines": self.subroutines,
+            "functions": [function.to_yaml() for function in self.functions],
+        }
+
+
+@dataclass
+class GroupConfigFunctionCall:
+    contract: str
+    function: str
+
+    @staticmethod
+    def from_yaml(function_call: Dict[str, Any]) -> "GroupConfigFunctionCall":
+        absent_fields = check_fields_are_present(["contract", "function"], function_call)
+        if absent_fields:
+            raise InvalidGroupConfiguration(
+                f'Function call:\n\nFollowing Required fields are absent: {", ".join(absent_fields)}'
+            )
+        contract = function_call["contract"]
+        function = function_call["function"]
+
+        return GroupConfigFunctionCall(contract, function)
+
+    def to_yaml(self) -> Dict[str, Any]:
+        return {
+            "contract": self.contract,
+            "function": self.function,
+        }
+
+
+@dataclass
+class GroupConfigTransaction:
+    txn_id: str
+    txn_type: str
+    application: Optional[GroupConfigFunctionCall] = None
+    has_logic_sig: Optional[bool] = None
+    logic_sig: Optional[GroupConfigFunctionCall] = None
+    absolute_index: Optional[int] = None
+    relative_indexes: Optional[Dict[str, int]] = None
+
+    @staticmethod
+    def from_yaml(transaction: Dict[str, Any]) -> "GroupConfigTransaction":
+        absent_fields = check_fields_are_present(["txn_id", "txn_type"], transaction)
+        if absent_fields:
+            raise InvalidGroupConfiguration(
+                f'Transaction:\n\nFollowing Required fields are absent: {", ".join(absent_fields)}'
+            )
+
+        txn_id = transaction["txn_id"]
+        txn_type = transaction["txn_type"]
+        if txn_type not in USER_CONFIG_TRANSACTION_TYPES:
+            raise InvalidGroupConfiguration(
+                f"Transaction: Unknown transaction type {txn_type} of transaction {txn_id}"
+            )
+        application = transaction.get("application")
+        has_logic_sig = transaction.get("has_logic_sig")
+        logic_sig = transaction.get("logic_sig")
+        absolute_index = transaction.get("absolute_index")
+        relative_indexes = transaction.get("relative_indexes")
+
+        if application is not None:
+            application = GroupConfigFunctionCall.from_yaml(application)
+
+        if logic_sig is not None:
+            logic_sig = GroupConfigFunctionCall.from_yaml(logic_sig)
+
+        if relative_indexes is not None:
+            parsed_relative_indexes: Dict[str, int] = {}
+            for relative_index in relative_indexes:
+                absent_fields = check_fields_are_present(["other_txn_id", "offset"], relative_index)
+                if absent_fields:
+                    raise InvalidGroupConfiguration(
+                        f'Transaction: {txn_id}\n\nFollowing Required fields are absent in relative_indexes: {", ".join(absent_fields)}'
+                    )
+                parsed_relative_indexes[relative_index["other_txn_id"]] = relative_index["offset"]
+            relative_indexes = parsed_relative_indexes
+
+        return GroupConfigTransaction(
+            txn_id,
+            txn_type,
+            application,
+            has_logic_sig,
+            logic_sig,
+            absolute_index,
+            relative_indexes,
+        )
+
+    def to_yaml(self) -> Dict[str, Any]:
+        output: Dict[str, Any] = {
+            "txn_id": self.txn_id,
+            "txn_type": self.txn_type,
+        }
+        if self.application is not None:
+            output["application"] = self.application.to_yaml()
+        if self.logic_sig is not None:
+            output["logic_sig"] = self.logic_sig.to_yaml()
+        if self.has_logic_sig is not None:
+            output["has_logic_sig"] = self.has_logic_sig
+        if self.absolute_index is not None:
+            output["absolute_index"] = self.absolute_index
+        if self.relative_indexes is not None:
+            relative_indexes = []
+            for txn_id, offset in self.relative_indexes.items():
+                relative_indexes.append(
+                    {
+                        "other_txn_id": txn_id,
+                        "offset": offset,
+                    }
+                )
+            output["relative_indexes"] = relative_indexes
+
+        return output
+
+
+@dataclass
+class GroupConfigGroup:
+    operation: str
+    transactions: List[GroupConfigTransaction]
+
+    @staticmethod
+    def from_yaml(group: Dict[str, Any]) -> "GroupConfigGroup":
+        absent_fields = check_fields_are_present(["operation", "transactions"], group)
+        if absent_fields:
+            raise InvalidGroupConfiguration(
+                f'Group:\n\nFollowing Required fields are absent: {", ".join(absent_fields)}'
+            )
+
+        operation = group["operation"]
+        parsed_transactions = []
+        for transaction in group["transactions"]:
+            parsed_transactions.append(GroupConfigTransaction.from_yaml(transaction))
+
+        return GroupConfigGroup(operation, parsed_transactions)
+
+    def to_yaml(self) -> Dict[str, Any]:
+        return {
+            "operation": self.operation,
+            "transactions": [transaction.to_yaml() for transaction in self.transactions],
+        }
+
+
+@dataclass
+class GroupConfig:
+    name: str
+    contracts: List[GroupConfigContract]
+    groups: List[GroupConfigGroup]
+
+    @staticmethod
+    def from_yaml(config: Dict[str, Any]) -> "GroupConfig":
+        absent_fields = check_fields_are_present(["name", "contracts", "groups"], config)
+        if absent_fields:
+            raise InvalidGroupConfiguration(
+                f'Config:\n\nFollowing Required fields are absent: {", ".join(absent_fields)}'
+            )
+        name = config["name"]
+        contracts = []
+        for contract in config["contracts"]:
+            contracts.append(GroupConfigContract.from_yaml(contract))
+        groups = []
+        for group in config["groups"]:
+            groups.append(GroupConfigGroup.from_yaml(group))
+
+        return GroupConfig(name, contracts, groups)
+
+    def to_yaml(self) -> Dict[str, Any]:
+        return {
+            "name": self.name,
+            "contracts": [contract.to_yaml() for contract in self.contracts],
+            "groups": [group.to_yaml() for group in self.groups],
+        }
+
+
+def write_to_yaml_file(file: Path, output: Dict[str, Any]) -> None:
+    with open(file, "w", encoding="utf-8") as f:
+        # yaml = YAML()
+        # yaml.indent(sequence=4, offset=2)
+        # yaml.dump(output, f)
+        yaml.safe_dump(output, f, sort_keys=False)
+
+
+def read_config_from_file(file: Path) -> GroupConfig:
+    with open(file, encoding="utf-8") as f:
+        d = yaml.safe_load(f.read())
+    try:
+        parsed_config = GroupConfig.from_yaml(d)
+        # given file_path is relative path of the contract from config file.
+        for contract in parsed_config.contracts:
+            contract.file_path = file.parent / contract.file_path
+        return parsed_config
+    except InvalidGroupConfiguration as err:
+        print(f"\nInvalidGroupConfiguration:\n\n{err}")
+        print(err)
+        sys.exit(1)
+
+
+# if __name__ == "__main__":
+# filename = "tests/group_transactions/ans/ans_config.yaml"
+# with open("tests/group_transactions/ans_config.yaml") as f:
+# try:
+# if 1:
+# parsed_config = read_config_from_file(filename)
+# write_to_yaml_file(Path("test.yaml"), parsed_config.to_yaml())
+# except InvalidGroupConfiguration as err:
+# print(f"\nInvalidGroupConfiguration:\n\n{err}")
+# print(err)
+# sys.exit(1)
diff --git a/tealer/utils/output.py b/tealer/utils/output.py
index dff77ce..826a5d8 100644
--- a/tealer/utils/output.py
+++ b/tealer/utils/output.py
@@ -5,7 +5,7 @@
 and printers.
 
 Functions:
-    full_cfg_to_dot(bbs: List[BasicBlock], config: Optional[CFGDotConfig]=None, filename: Optional[Path]=None) -> None:
+    full_cfg_to_dot(teal: "Teal", config: Optional[CFGDotConfig]=None, filename: Optional[Path]=None) -> None:
         Exports dot representation of CFG represented by :bbs: in
         dot format to given filename.
 
@@ -18,20 +18,27 @@
         For now, it is an alias for ExecutionPaths.
 
 """
-
+import abc
 import html
 import re
+import os
 from pathlib import Path
 from typing import List, TYPE_CHECKING, Dict, Callable, Optional
-from dataclasses import dataclass
+from dataclasses import dataclass, field
 
 from tealer.teal.instructions.instructions import BZ, BNZ, Callsub, Retsub
 
 if TYPE_CHECKING:
     from tealer.teal.basic_blocks import BasicBlock
     from tealer.teal.subroutine import Subroutine
+    from tealer.teal.functions import Function
     from tealer.teal.teal import Teal
     from tealer.teal.instructions.instructions import Instruction
+    from tealer.detectors.abstract_detector import AbstractDetector
+    from tealer.execution_context.transactions import GroupTransaction, Transaction
+
+
+ROOT_OUTPUT_DIRECTORY = Path("tealer-export")
 
 
 @dataclass
@@ -49,6 +56,7 @@ class CFGDotConfig:  # pylint: disable=too-many-instance-attributes
     remaining_edges_color: str = "BLACK"
     comments_cell_border_size: int = 2  # size of basic block comments cell
     bb_border_color: Callable[["BasicBlock"], str] = lambda _x: "BLACK"
+    custom_background_color: Dict["Instruction", str] = field(default_factory=dict)
 
 
 def _instruction_to_dot(ins: "Instruction", config: CFGDotConfig) -> str:
@@ -106,9 +114,11 @@ def _instruction_to_dot(ins: "Instruction", config: CFGDotConfig) -> str:
         source_code_comments = "<BR/>".join(f"{comment}" for comment in sanitized_comments)
         source_code_comments += "<BR/>"
 
+    color = config.custom_background_color.get(ins, "BLACK")
+
     cell_i = (
         "<TR>"
-        '<TD ALIGN="LEFT" BALIGN="LEFT" COLOR="BLACK">'
+        f'<TD ALIGN="LEFT" BALIGN="LEFT" COLOR="{color}">'
         f"{tealer_comments}"
         f"{source_code_comments}"
         f"{ins.line}. {ins_str}"
@@ -166,7 +176,7 @@ def graph_edge_str(src_bb: "BasicBlock", dest_bb: "BasicBlock", edge_color: str)
         table_rows.append(_instruction_to_dot(ins, config))
 
     graph_edges: List[str] = []
-    if config.color_edges and isinstance(bb.exit_instr, (BZ, BNZ, Callsub)):
+    if config.color_edges and isinstance(bb.exit_instr, (BZ, BNZ)):
         if isinstance(bb.exit_instr, (BZ, BNZ)):
             # color graph edges if exit instruction is BZ or BNZ.
             if len(bb.next) == 1:
@@ -180,10 +190,6 @@ def graph_edge_str(src_bb: "BasicBlock", dest_bb: "BasicBlock", edge_color: str)
             if default_branch is not None:
                 graph_edges.append(graph_edge_str(bb, default_branch, config.default_branch_color))
             graph_edges.append(graph_edge_str(bb, jump_branch, config.jump_branch_color))
-        elif isinstance(bb.exit_instr, Callsub):
-            # make callsub instruction -> subroutine edge orange.
-            callsub_branch = bb.next[0]
-            graph_edges.append(graph_edge_str(bb, callsub_branch, config.callsub_edge_color))
     else:
         for next_bb in bb.next:
             graph_edges.append(graph_edge_str(bb, next_bb, config.remaining_edges_color))
@@ -200,42 +206,41 @@ def graph_edge_str(src_bb: "BasicBlock", dest_bb: "BasicBlock", edge_color: str)
 def subroutine_to_dot(subroutine: "Subroutine", config: Optional[CFGDotConfig] = None) -> str:
     if config is None:
         config = CFGDotConfig()
-    # ignore edges from callsub and retsub.
+    # ignore edges from callsub to the return point block because we place an empty node to represent
+    # the called subroutine in between the callsub block and the return point.
     # replacing the ignore_edge function directly should be ok (?) for now
-    config.ignore_edge = lambda bi, _: isinstance(bi.exit_instr, (Callsub, Retsub))
+    config.ignore_edge = lambda bi, _: isinstance(bi.exit_instr, (Callsub))
 
-    def empty_subroutine_box(
-        callsub_block: "BasicBlock",
-        return_point_block: "BasicBlock",
-        called_subroutine: "Subroutine",
-    ) -> str:
+    def empty_subroutine_box(callsub_block: "BasicBlock") -> str:
         # Include a empty box between callsub and it's return point. Empty box represents
         # the subroutine called by `callsub`.
+        called_subroutine = callsub_block.called_subroutine
+        return_point_block = callsub_block.sub_return_point
+        if return_point_block is None:
+            # happens when callsub is the last instruction in the CFG and subroutine always exits the program.
+            return_point_block_idx = "none"
+        else:
+            return_point_block_idx = str(return_point_block.idx)
         content = (
             f'"Subroutine {called_subroutine.name}"'  # TODO: add other information in the future
         )
-        node_name = f"x{callsub_block.idx}_{return_point_block.idx}"
+        node_name = f"x{callsub_block.idx}_{return_point_block_idx}"
         edge1 = f"{callsub_block.idx}:s -> {node_name}:n;\n"  # callsub to empty box
-        # empty box to return point block
-        edge2 = (
-            f"{node_name}:s -> {return_point_block.idx}:{return_point_block.entry_instr.line}:n;\n"
-        )
+        edge2 = ""
+        if return_point_block is not None:
+            # edge from empty box to return point block
+            edge2 = f"{node_name}:s -> {return_point_block.idx}:{return_point_block.entry_instr.line}:n;\n"
+
         return f"{node_name}[label={content},style=dashed,shape=box,fontname=bold] {edge1}{edge2}"
 
     nodes_dot: List[str] = []
     for bi in subroutine.blocks:
         nodes_dot.append(_bb_to_dot(bi, config))
-        if isinstance(bi.exit_instr, Callsub):
+        if bi.is_callsub_block:
             # add empty box to represent the graph of called subroutine
             # add edge from callsub to that box and box to callsub return point.
             # ignoring recursion here. adds empty box even if callsub calls the same subroutine.
-            return_point_ins = bi.exit_instr.return_point
-            assert return_point_ins is not None
-            return_point_bb = return_point_ins.bb
-            assert return_point_bb is not None
-            called_subroutine = subroutine.contract.subroutine(bi.exit_instr.label)
-            assert called_subroutine is not None
-            nodes_dot.append(empty_subroutine_box(bi, return_point_bb, called_subroutine))
+            nodes_dot.append(empty_subroutine_box(bi))
 
     nodes_str = "\n".join(nodes_dot)
 
@@ -255,26 +260,28 @@ def all_subroutines_to_dot(
     Args:
         teal: contract instance
         dest: destination directory to save the dot files.
+        config: CFGDotConfig to use for generating the dot files. if None, function uses
+            default settings.
         filename_prefix: prefix to add before each filename to distinguish files generated
             by multiple calls to this function.
     """
     if filename_prefix:  # not empty string
         filename_prefix = f"{filename_prefix}_"
-    main_entry_sub_filename = f"{teal.contract_name}_{filename_prefix}contract_shortened_cfg.dot"
+    main_entry_sub_filename = f"{filename_prefix}contract_shortened_cfg.dot"
 
     with open(dest / Path(main_entry_sub_filename), "w", encoding="utf-8") as f:
         f.write(subroutine_to_dot(teal.main, config))
         print(f"Exported contract's shortened cfg to: {dest / Path(main_entry_sub_filename)}")
 
     for sub_name, subroutine in teal.subroutines.items():
-        filename = f"{teal.contract_name}_{filename_prefix}subroutine_{sub_name}_cfg.dot"
+        filename = f"{filename_prefix}subroutine_{sub_name}_cfg.dot"
         with open(dest / Path(filename), "w", encoding="utf-8") as f:
             f.write(subroutine_to_dot(subroutine, config))
             print(f'Exported cfg of "{sub_name}" subroutine to: {dest / Path(filename)}')
 
 
 def full_cfg_to_dot(  # pylint: disable=too-many-locals
-    bbs: List["BasicBlock"], config: Optional[CFGDotConfig] = None, filename: Optional[Path] = None
+    teal: "Teal", config: Optional[CFGDotConfig] = None, filename: Optional[Path] = None
 ) -> Optional[str]:
     """Export control flow graph to a dot file.
 
@@ -283,16 +290,17 @@ def full_cfg_to_dot(  # pylint: disable=too-many-locals
     as rows.
 
     Args:
-        bbs: list of basic blocks representing the control
-            flow graph.
+        teal: The contract.
         config: optional configuration for dot output.
         filename: name of the file to save the dot representation
             of control flow graph in.
 
+    Returns:
+        Returns dot representation of the CFG if filename is not given. If filename is given,
+        writes the dot representation to the file.
     """
 
-    teal = bbs[0].teal
-    assert teal is not None
+    bbs = teal.bbs
     subroutine_block_idx = set(bb.idx for bb in teal.bbs if bb in teal.main.blocks)
     # responsible for "box" around each subroutine.
     subroutine_clusters: List[str] = []
@@ -310,6 +318,10 @@ def full_cfg_to_dot(  # pylint: disable=too-many-locals
         """
         subroutine_clusters.append(subgraph_dot)
 
+    # format: `{source_node}:s -> {dest_node}{dest_port}:n [color=""];`
+    def graph_edge_str(src_bb: "BasicBlock", dest_bb: "BasicBlock", edge_color: str) -> str:
+        return f'{src_bb.idx}:s -> {dest_bb.idx}:{dest_bb.entry_instr.line}:n [color="{edge_color}"];\n'
+
     # default config
     if not config:
         config = CFGDotConfig()
@@ -321,9 +333,25 @@ def full_cfg_to_dot(  # pylint: disable=too-many-locals
             else subroutine_blocks_border_color
         )
 
+    # ignore callsub block to return point edges. Add edges from callsub blocks to subroutine entry and retsubs to return points
+    config.ignore_edge = lambda bi, _: isinstance(bi.exit_instr, (Callsub))
+
     bb_nodes_dot: List[str] = []
     for bb in bbs:
         bb_nodes_dot.append(_bb_to_dot(bb, config))
+        if bb.is_callsub_block:
+            # add edge from callsub block to subroutine entry
+            bb_nodes_dot.append(
+                graph_edge_str(bb, bb.called_subroutine.entry, config.callsub_edge_color)
+            )
+            # add edge from retsub blocks to return point
+            return_point_block = bb.sub_return_point
+            if return_point_block is None:
+                continue
+            for src_bb in bb.called_subroutine.retsub_blocks:
+                bb_nodes_dot.append(
+                    graph_edge_str(src_bb, return_point_block, config.remaining_edges_color)
+                )
 
     subroutine_clusters_str = "\n".join(subroutine_clusters)
     bb_nodes_str = "\n".join(bb_nodes_dot)
@@ -343,105 +371,150 @@ def full_cfg_to_dot(  # pylint: disable=too-many-locals
     return None
 
 
-class ExecutionPaths:  # pylint: disable=too-many-instance-attributes
-    """Detector output class to store list of execution paths.
+def detector_terminal_description(detector: "AbstractDetector") -> str:
+    """Return description for the detector that is printed to terminal before listing vulnerable paths.
 
     Args:
-        cfg: Control Flow Graph of the teal contract.
-        description: Description of the execution path detected by
-            the detector.
-        filename: The dot representation of execution paths will be
-            saved in the filenames starting with :filename: prefix.
+        detector: A detector object.
+
+    Returns:
+        Returns description for the :detector: that can be printed on the terminal or used as general description
+        for the detector.
     """
+    return (
+        f'\nCheck: "{detector.NAME}", Impact: {detector.IMPACT}, Confidence: {detector.CONFIDENCE}\n'
+        f"Description: {detector.DESCRIPTION}\n\n"
+        f"Wiki: {detector.WIKI_URL}\n"
+    )
+
 
-    def __init__(self, cfg: List["BasicBlock"], description: str, filename: str):
-        self._cfg = cfg
-        self._description = description
-        self._filename = filename
-        self._paths: List[List["BasicBlock"]] = []
-        self._check: str = ""
-        self._impact: str = ""
-        self._confidence: str = ""
-        self._help: str = ""
+def detector_ouptut_dir(destination: Path, detector: "AbstractDetector") -> Path:
+    return destination / Path(detector.NAME)
+
+
+class Output(abc.ABC):
+    @property
+    @abc.abstractmethod
+    def detector(self) -> "AbstractDetector":
+        pass
+
+    @abc.abstractmethod
+    def filter_paths(self, filter_regex: str) -> None:
+        pass
+
+    @abc.abstractmethod
+    def to_json(self) -> Dict:
+        pass
+
+    @abc.abstractmethod
+    def generate_output(self, dest: Path) -> bool:
+        """
+        Generate the output
 
-    def add_path(self, path: List["BasicBlock"]) -> None:
-        """Add given execution path to current list of execution paths.
 
         Args:
-            path: new execution path detected by the detector which
-                will be added to the list of execution paths.
+            dest: The files will be saved in the given :dest: destination directory.
+
+        Returns:
+            Returns true if something was generated - False if there is nothing to be written
         """
 
-        self._paths.append(path)
+        return False  # this statement is needed for darglint
 
-    @property
-    def paths(self) -> List[List["BasicBlock"]]:
-        """List of execution paths stored in the result."""
-        return self._paths
 
-    @property
-    def cfg(self) -> List["BasicBlock"]:
-        """Control Flow of the teal contract."""
-        return self._cfg
+class InstructionsOutput(Output):
+    def __init__(
+        self, teal: "Teal", detector: "AbstractDetector", instructions: List[List["Instruction"]]
+    ):
+        self._teal = teal
+        self._detector = detector
+        self.instructions: List[List["Instruction"]] = instructions
 
     @property
-    def description(self) -> str:
-        """Description of execution paths stored in this result"""
-        return self._description
+    def detector(self) -> "AbstractDetector":
+        return self._detector
 
-    @property
-    def check(self) -> str:
-        """Name of the detector whose result is being represented."""
-        return self._check
+    def filter_paths(self, filter_regex: str) -> None:
+        pass
 
-    @check.setter
-    def check(self, c: str) -> None:
-        self._check = c
+    def to_json(self) -> Dict:
+        result = {
+            "type": "InstructionsOutput",
+            "count": len(self.instructions),
+            "description": detector_terminal_description(self.detector),
+            "check": self.detector.NAME,
+            "impact": str(self.detector.IMPACT),
+            "confidence": str(self.detector.CONFIDENCE),
+            "help": self.detector.WIKI_RECOMMENDATION.strip(),
+            "paths": [str(ins) for ins in self.instructions],
+        }
+        return result
 
-    @property
-    def impact(self) -> str:
-        """Impact of the detector whose result is being represented."""
-        return self._impact
+    def generate_output(self, dest: Path) -> bool:
+        """
+        Generate the output
 
-    @impact.setter
-    def impact(self, i: str) -> None:
-        self._impact = i
 
-    @property
-    def confidence(self) -> str:
-        """Confidence of the detector whose result is being represented."""
-        return self._confidence
+        Args:
+            dest: Not use (no files are generated for InstructionsOutput)
+
+        Returns:
+            Returns true if something was generated - False if there is nothing to be written
+        """
+
+        if not self.instructions:
+            return False
+
+        print(detector_terminal_description(self.detector))
+        print("\tFollowing are the unoptimized instructions found:")
+
+        for ins in self.instructions:
+            print(ins)
 
-    @confidence.setter
-    def confidence(self, c: str) -> None:
-        self._confidence = c
+        return True
+
+
+class ExecutionPaths(Output):
+    """Detector output class to represent vulnerable execution paths."""
+
+    def __init__(self, teal: "Teal", detector: "AbstractDetector", paths: List[List["BasicBlock"]]):
+        self._teal = teal
+        self._detector = detector
+        self.paths: List[List["BasicBlock"]] = paths
 
     @property
-    def help(self) -> str:
-        """Help message to remove detected issues from the contract."""
-        return self._help
+    def detector(self) -> "AbstractDetector":
+        return self._detector
 
-    @help.setter
-    def help(self, h: str) -> None:
-        self._help = h
+    def _filename(self, path_index: int) -> Path:
+        return Path(f"{self.detector.NAME}-{path_index}.dot")
 
     @staticmethod
     def _short_notation(path_bbs: List["BasicBlock"]) -> str:
-        """Return short notation representation of path"""
+        """Return short notation representation of path
+
+        Args:
+            path_bbs: List of basic blocks in the path from the contract's entry.
+
+        Returns:
+            Returns short notation representation of the path.
+            if path has [B0, B2, B3, B5] then short notation is "0 -> 2 -> 3 -> 5"
+        """
+        # TODO: Change notation from "0 -> 2 -> 3 -> 5" to "B0 -> B2 -> B3 -> B5"
         return " -> ".join(map(str, [bb.idx for bb in path_bbs]))
 
     def filter_paths(self, filter_regex: str) -> None:
         if filter_regex == "":
             return
         filtered_paths: List[List["BasicBlock"]] = []
-        for path in self._paths:
+        for path in self.paths:
             if re.search(filter_regex, self._short_notation(path)) is None:
                 # short notation does not contain string matching the regex
                 filtered_paths.append(path)
-        self._paths = filtered_paths
+        self.paths = filtered_paths
         return
 
-    def write_to_files(self, dest: Path, all_paths_in_one: bool = False) -> None:
+    def generate_output(self, dest: Path) -> bool:
         """Export execution paths to dot files.
 
         The execution paths are highlighted in the dot representation
@@ -451,53 +524,41 @@ def write_to_files(self, dest: Path, all_paths_in_one: bool = False) -> None:
         Args:
             dest: The dot files will be saved in the given :dest: destination
                 directory.
-            all_paths_in_one: if this is set to True, all the execution
-                paths will be highlighted in a single file. if this is
-                False, each execution path is saved in a different file.
-                Default False.
+
+        Returns:
+            Returns true if something was written - False if there is nothing to be written
         """
 
-        print(self.description)
-        if len(self.paths) == 0:
-            print("\tDetector didn't find any vulnerable paths.")
-            print("-" * 100)
-            return
+        if not self.paths:
+            return False
+
+        print(detector_terminal_description(self.detector))
+
         # cfg_to_dot config
         config = CFGDotConfig()
         config.color_edges = False
         print("\tFollowing are the vulnerable paths found:")
-        if not all_paths_in_one:
-
-            for idx, path in enumerate(self._paths, start=1):
 
-                short = self._short_notation(path)
-                print(f"\n\t\t path: {short}")
-
-                filename = dest / Path(f"{self._filename}_{idx}.dot")
-                print(f"\t\t check file: {filename}")
-
-                config.bb_border_color = (
-                    lambda bb: "BLACK"
-                    if bb not in path  # pylint: disable=cell-var-from-loop
-                    else "RED"
-                )
-                full_cfg_to_dot(self.cfg, config, filename)
-            print("-" * 100)
-        else:
-            bbs_to_highlight = []
+        dest = detector_ouptut_dir(dest, self.detector)
+        # create output directory if not present
+        os.makedirs(dest, exist_ok=True)
 
-            for path in self._paths:
-                short = self._short_notation(path)
-                print(f"\t\t path: {short}")
-                for bb in path:
-                    if bb not in bbs_to_highlight:
-                        bbs_to_highlight.append(bb)
+        for idx, path in enumerate(self.paths, start=1):
+            short = self._short_notation(path)
+            print(f"\n\t\t path: {short}")
 
-            filename = dest / Path(f"{self._filename}.dot")
+            filename = dest / self._filename(idx)
             print(f"\t\t check file: {filename}")
 
-            config.bb_border_color = lambda bb: "BLACK" if bb not in bbs_to_highlight else "RED"
-            full_cfg_to_dot(self.cfg, config, filename)
+            config.bb_border_color = (
+                lambda bb: "BLACK"
+                if bb not in path  # pylint: disable=cell-var-from-loop
+                else "RED"
+            )
+            full_cfg_to_dot(self._teal, config, filename)
+        print("-" * 100)
+
+        return True
 
     def to_json(self) -> Dict:
         """Return json representation of detector result.
@@ -513,11 +574,11 @@ def to_json(self) -> Dict:
         result = {
             "type": "ExecutionPaths",
             "count": len(self.paths),
-            "description": self.description,
-            "check": self.check,
-            "impact": self.impact,
-            "confidence": self.confidence,
-            "help": self.help,
+            "description": detector_terminal_description(self.detector),
+            "check": self.detector.NAME,
+            "impact": str(self.detector.IMPACT),
+            "confidence": str(self.detector.CONFIDENCE),
+            "help": self.detector.WIKI_RECOMMENDATION.strip(),
         }
         paths = []
         for path in self.paths:
@@ -535,4 +596,82 @@ def to_json(self) -> Dict:
         return result
 
 
-SupportedOutput = ExecutionPaths
+class GroupTransactionOutput(Output):
+    def __init__(
+        self,
+        detector: "AbstractDetector",
+        group: "GroupTransaction",
+        transactions: Dict["Transaction", List["Function"]],
+    ) -> None:
+        self._detector = detector
+        self._group = group
+        self._transactions = transactions
+
+    @property
+    def detector(self) -> "AbstractDetector":
+        return self._detector
+
+    @property
+    def group_transaction(self) -> "GroupTransaction":
+        return self._group
+
+    @property
+    def transactions(self) -> Dict["Transaction", List["Function"]]:
+        return self._transactions
+
+    def filter_paths(self, filter_regex: str) -> None:
+        pass
+
+    def to_json(self) -> Dict:
+        transactions = {}
+        for txn in self._transactions:
+            contracts = []
+            for function in self._transactions[txn]:
+                contracts.append(
+                    {
+                        "contract": function.contract.contract_name,
+                        "function": function.function_name,
+                    }
+                )
+            transactions[txn.transacton_id] = contracts
+
+        result = {
+            "type": "GroupTransactionOutput",
+            "description": detector_terminal_description(self.detector),
+            "check": self.detector.NAME,
+            "impact": str(self.detector.IMPACT),
+            "confidence": str(self.detector.CONFIDENCE),
+            "help": self.detector.WIKI_RECOMMENDATION.strip(),
+            "operation": self._group.operation_name,
+            "transactions": transactions,
+        }
+        return result
+
+    def generate_output(self, dest: Path) -> bool:
+        """
+        Generate the output
+
+        Args:
+            dest: Not used, no files are generated for GroupTransactionOutput
+
+        Returns:
+            Returns true if something was generated - False if there is nothing to be written.
+        """
+        print(detector_terminal_description(self.detector))
+        print(
+            f"\tFollowing transactions of the operation {self._group.operation_name} are vulnerable:\n"
+        )
+
+        for txn in self._transactions:
+            print(f"\tTransaction {txn.transacton_id}")
+            if self._transactions[txn]:
+                # contract was given by the user.
+                for function in self._transactions[txn]:
+                    print(f"\t\tContract: {function.contract.contract_name}")
+                    print(f"\t\tFunction: {function.function_name}")
+                    print("\n")
+
+        return True
+
+
+ListOutput = List[Output]
diff --git a/tealer/utils/regex/__init__.py b/tealer/utils/regex/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tealer/utils/regex/regex.py b/tealer/utils/regex/regex.py
new file mode 100644
index 0000000..0034168
--- /dev/null
+++ b/tealer/utils/regex/regex.py
@@ -0,0 +1,207 @@
+import re
+from pathlib import Path
+from typing import List, Optional, Tuple, Set
+
+from tealer.teal.instructions.instructions import Instruction, Label
+from tealer.teal.instructions.parse_instruction import parse_line
+from tealer.teal.teal import Teal
+from tealer.utils.output import CFGDotConfig, full_cfg_to_dot
+
+
+# pylint: disable=too-few-public-methods
+class Regex:
+    def __init__(self, label: str, instructions: List[Instruction]) -> None:
+        self.label = label
+        self.instructions = instructions
+
+
+def parse_regex(regex: str) -> Regex:
+    """
+    Parse the regex
+
+    Args:
+        regex: regex as a string
+
+    Returns:
+        Regex as an object
+
+    Raises:
+        ValueError: if the text does not have the correct format
+    """
+    pattern = re.compile(r"^(.*?)=>\s*(.*?)$", re.DOTALL)
+    match = pattern.search(regex)
+    if match:
+        # Group 1 is the label, Group 2 is the content
+        label_found, content_found = match.groups()
+        label = label_found.strip()
+        instructions_text = content_found.strip().splitlines()
+
+        instructions: List[Instruction] = []
+        for line in instructions_text:
+            instruction = parse_line(line)
+            if instruction:
+                instructions.append(instruction)
+
+        return Regex(label, instructions)
+
+    raise ValueError("Invalid text format")
+
+
+def _find_label(instructions: List[Instruction], label: str) -> Optional[Instruction]:
+    # Assuming "*" is not a valid teal label ?
+    # Assuming the first instruction is the entry point
+    if label == "*":
+        return instructions[0]
+
+    match = [ins for ins in instructions if isinstance(ins, Label) and ins.label == label]
+
+    # assuming the same label can't be reused in a contract
+    assert len(match) <= 1
+
+    if match:
+        return match[0]
+
+    return None
+
+
+def _is_equal(a: Instruction, b: Instruction) -> bool:
+    if type(a) is not type(b):
+        return False
+
+    # We don't have current a good way to compare two instructions subfield
+    # So we use the string representation as a temp solution
+    # Note both objects are not expected to be the same, as one is coming from the teal contract
+    # The other one was generated based on the regex
+    return str(a) == str(b)
+
+
+def _is_match(current_instruction: Optional[Instruction], regex: List[Instruction]) -> bool:
+    for _, regex_ins in enumerate(regex):
+        if current_instruction is None:
+            return False
+        if not _is_equal(current_instruction, regex_ins):
+            return False
+        current_instruction = (
+            current_instruction.next[0]
+            if current_instruction.next and len(current_instruction.next) == 1
+            else None
+        )
+
+    return True
+
+
+def _find_instructions(
+    current_instruction: Instruction,
+    regex: List[Instruction],
+    visited: Set[Instruction],
+    matches: List[List[Instruction]],
+    covered: Set[Instruction],
+) -> bool:
+    if current_instruction in visited:
+        return False
+
+    visited.add(current_instruction)
+
+    reaches = False
+
+    if _is_match(current_instruction, regex):
+        match: List[Instruction] = []
+        current = current_instruction
+        for _ in range(0, len(regex) - 1):
+
+            if not current:
+                break
+            match.append(current)
+
+            if len(current.next) != 1:
+                print(f"Regex cannot work on branching instructions {current_instruction}")
+                return False
+            current = current.next[0]
+
+        match.append(current)
+        matches.append(match)
+        reaches = True
+
+    for next_ins in current_instruction.next:
+
+        if next_ins in covered:
+            continue
+
+        if _find_instructions(next_ins, regex, visited, matches, covered):
+            covered.add(current_instruction)
+            reaches = True
+
+    return reaches
+
+
+def match_regex(contract: Teal, regex: Regex) -> Tuple[List[List[Instruction]], Set[Instruction]]:
+    """
+    Match the regex in the contract
+
+    Args:
+        contract: target
+        regex: regex
+
+    Returns:
+        A tuple of (matches, covered), where
+        - matches: contains all the instructions that match the regex
+        - covered: contains all the instruction between the label and the matches
+    """
+    label = _find_label(contract.instructions, regex.label)
+
+    if not label:
+        print(f"{label} cannot be found in {contract}")
+        return [], set()
+
+    matches: List[List[Instruction]] = []
+    covered: Set[Instruction] = set()
+
+    _find_instructions(label, regex.instructions, set(), matches, covered)
+
+    return matches, covered
+
+
+def update_config(
+    config: CFGDotConfig, matches: List[List[Instruction]], covered: Set[Instruction]
+) -> None:
+    """
+    Update the config with the regex result. It color match in red, and covered in green
+
+    Args:
+        config: config
+        matches: instructions that match the regex
+        covered: instructions between the label and the match
+    """
+    for inss in matches:
+        for ins in inss:
+            config.custom_background_color[ins] = "#e0182b"  # red
+
+    for ins in covered:
+        config.custom_background_color[ins] = "#36d899"  # green
+
+
+def run_regex(teal: Teal, regex_Path: Path, export_path: Path) -> None:
+    """
+    Run the regex analysis and generate the dot file
+
+    Args:
+        teal: target
+        regex_Path: path to the regex
+        export_path: path to generate the dot file
+    """
+    with open(regex_Path, "r", encoding="utf8") as f_regex:
+        regex = parse_regex(f_regex.read())
+
+    matches, covered = match_regex(teal, regex)
+
+    if not matches:
+        print("Not match was found")
+        return
+    # Ensure the CFG generation does not crash
+    config = CFGDotConfig()
+    update_config(config, matches, covered)
+
+    with open(export_path, "w", encoding="utf8") as fp:
+        full_cfg_to_dot(teal, config, Path(fp.name))
+
+    print(f"Result generated in {export_path}")
diff --git a/tealer/utils/teal_enums.py b/tealer/utils/teal_enums.py
index bad2b30..cd3453a 100644
--- a/tealer/utils/teal_enums.py
+++ b/tealer/utils/teal_enums.py
@@ -25,7 +25,7 @@ def __str__(self) -> str:
         return self.name
 
 
-class TransactionTypeEnum(ComparableEnum):
+class TransactionType(ComparableEnum):
     Invalid = 0x0
     Pay = 0x1
     KeyReg = 0x02
@@ -34,6 +34,21 @@ class TransactionTypeEnum(ComparableEnum):
     Afrz = 0x5
     Appl = 0x6
 
+    Any = 0x7
+    Unknown = 0x8
+
+
+def transaction_type_from_txt(txn_type: str) -> TransactionType:
+    return {
+        "pay": TransactionType.Pay,
+        "keyreg": TransactionType.KeyReg,
+        "acfg": TransactionType.Acfg,
+        "axfer": TransactionType.Axfer,
+        "afrz": TransactionType.Afrz,
+        "appl": TransactionType.Appl,
+        "txn": TransactionType.Any,
+    }[txn_type]
+
 
 class TransactionOnCompletion(ComparableEnum):
     NoOp = 0x0
@@ -124,3 +139,49 @@ def transaction_type_to_tealer_type(value: Union[str, int]) -> "TealerTransactio
         value = ENUM_NAMES_TO_INT[value]
 
     return INT_TO_TYPE[value]
+
+
+class ExecutionMode(ComparableEnum):
+    """Execution mode of the instruction/function/contract.
+
+    Stateless: Contract do not have access to persistent storage, block information, etc.
+        All stateless contracts are LogicSigs.
+    Stateful: Contracts have access to persistent storage and are referred to as Applications.
+        A stateful contract can be an ApprovalProgram or a ClearStateProgram.
+    Any: Contract can be executed in any mode, either stateless or stateful.
+    """
+
+    STATELESS = 0
+    STATEFUL = 1
+    ANY = 2
+
+    def __str__(self) -> str:
+        return self.name.title()
+
+
+class ContractType(ComparableEnum):
+    """Type of the contract.
+
+    LogicSig: Contract is used to sign the transactions.
+    ApprovalProgram: Contract is deployed onto the chain and will be executed for calls with
+        on-complete value of NoOp, OptIn, CloseOut, UpdateApplication and DeleteApplication.
+    ClearStateProgram: Contract is deployed onto the chain and will be executed for calls with
+        on-complete value of ClearState.
+    """
+
+    LogicSig = 0
+    ApprovalProgram = 1
+    ClearStateProgram = 2
+
+    Unknown = 99
+
+    def __str__(self) -> str:
+        return self.name
+
+
+def contract_type_from_txt(contract_type: str) -> ContractType:
+    return {
+        "ApprovalProgram": ContractType.ApprovalProgram,
+        "ClearStateProgram": ContractType.ClearStateProgram,
+        "LogicSig": ContractType.LogicSig,
+    }[contract_type]


Test patch:
diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml
index 50ee364..430c6f0 100644
--- a/.github/workflows/pytest.yml
+++ b/.github/workflows/pytest.yml
@@ -60,3 +60,7 @@ jobs:
         pytest tests/transaction_context/test_group_indices.py
         pytest tests/transaction_context/test_transaction_types.py
         pytest tests/transaction_context/test_addr_fields.py
+
+    - name: Run regex tests
+      run: |
+        pytest tests/test_regex.py
\ No newline at end of file
diff --git a/.github/workflows/pytest310.yml b/.github/workflows/pytest310.yml
index 9b731b5..a632eb6 100644
--- a/.github/workflows/pytest310.yml
+++ b/.github/workflows/pytest310.yml
@@ -60,3 +60,7 @@ jobs:
       run: |
         pytest tests/transaction_context/test_group_sizes.py
         pytest tests/transaction_context/test_group_indices.py
+
+    - name: Run regex tests in 3.10
+      run: |
+        pytest tests/test_regex.py
\ No newline at end of file
diff --git a/scripts/test_algorand_contracts.sh b/scripts/test_algorand_contracts.sh
index 98325e0..367739a 100755
--- a/scripts/test_algorand_contracts.sh
+++ b/scripts/test_algorand_contracts.sh
@@ -6,7 +6,7 @@ cd smart-contracts || exit 255
 
 while IFS= read -r -d '' target
 do
-    if ! tealer "$target"; then
+    if ! tealer --contracts "$target" --print human-summary > /dev/null; then
         echo "tests failed"
         exit 1
     fi
diff --git a/tests/detectors/fee_check.py b/tests/detectors/fee_check.py
index 2bf50b2..31108d0 100644
--- a/tests/detectors/fee_check.py
+++ b/tests/detectors/fee_check.py
@@ -598,14 +598,15 @@
 
 CHECK_IN_ONE_PATH_VULNERABLE_PATHS: List[List[int]] = []
 
-
+# The commented out test contracts contain a basic block which is part of __main__ CFG and a subroutine.
+# TODO: Handle that case
 new_missing_fee_tests = [
-    (BASIC_1, MissingFeeCheck, BASIC_1_VULNERABLE_PATHS),
-    (BASIC_2, MissingFeeCheck, BASIC_2_VULNERABLE_PATHS),
+    # (BASIC_1, MissingFeeCheck, BASIC_1_VULNERABLE_PATHS),
+    # (BASIC_2, MissingFeeCheck, BASIC_2_VULNERABLE_PATHS),
     (UNKNOWN_FEE, MissingFeeCheck, UNKNOWN_FEE_VULNERABLE_PATHS),
     (BASIC_3, MissingFeeCheck, BASIC_3_VULNERABLE_PATHS),
     (LARGE_FEE, MissingFeeCheck, LARGE_FEE_VULNERABLE_PATHS),
-    (MULTIPLE_GROUP_SIZES_IN_SUBROUTINES, MissingFeeCheck, MULTIPLE_GROUP_SIZES_VULNERABLE_PATHS),
+    # (MULTIPLE_GROUP_SIZES_IN_SUBROUTINES, MissingFeeCheck, MULTIPLE_GROUP_SIZES_VULNERABLE_PATHS),
     (
         CHECKS_IN_MULTIPLE_SUBROUTINES,
         MissingFeeCheck,
diff --git a/tests/detectors/optimizations/constant_gtxn.py b/tests/detectors/optimizations/constant_gtxn.py
new file mode 100644
index 0000000..1d176d6
--- /dev/null
+++ b/tests/detectors/optimizations/constant_gtxn.py
@@ -0,0 +1,10 @@
+from tealer.detectors.optimizations.constant_gtxn import ConstantGtxn
+
+GTXN = """
+#pragma version 3
+int 1
+gtxns CloseRemainderTo
+int 1
+"""
+
+constant_gtxn = [(GTXN, ConstantGtxn, [[2, 3]])]
diff --git a/tests/detectors/optimizations/self_access.py b/tests/detectors/optimizations/self_access.py
new file mode 100644
index 0000000..ef04766
--- /dev/null
+++ b/tests/detectors/optimizations/self_access.py
@@ -0,0 +1,10 @@
+from tealer.detectors.optimizations.self_access import SelfAccess
+
+GTXN = """
+#pragma version 3
+txn GroupIndex
+gtxns CloseRemainderTo
+int 1
+"""
+
+self_access = [(GTXN, SelfAccess, [[2, 3]])]
diff --git a/tests/detectors/optimizations/sender_access.py b/tests/detectors/optimizations/sender_access.py
new file mode 100644
index 0000000..aa489a2
--- /dev/null
+++ b/tests/detectors/optimizations/sender_access.py
@@ -0,0 +1,10 @@
+from tealer.detectors.optimizations.sender_access import SenderAccess
+
+SENDER_ACCESS = """
+#pragma version 3
+int 1
+txna Accounts 0
+int 2
+"""
+
+sender_access = [(SENDER_ACCESS, SenderAccess, [[3]])]
diff --git a/tests/detectors/pyteal_can_close.py b/tests/detectors/pyteal_can_close.py
index 1f5efa9..6990d55 100644
--- a/tests/detectors/pyteal_can_close.py
+++ b/tests/detectors/pyteal_can_close.py
@@ -24,6 +24,13 @@ def echo(input: abi.Uint64, *, output: abi.Uint64) -> Expr:
         !=
         &&
         assert            // Assert(NoOp, CALL)
+
+    Args:
+        input: Input argument
+        output: Return value of the echo method.
+
+    Returns:
+        Body of the echo method as PyTeal expr.
     """
     return output.set(input.get())
 
diff --git a/tests/detectors/router_with_assembled_constants.py b/tests/detectors/router_with_assembled_constants.py
index f30ed4d..53cd0a4 100644
--- a/tests/detectors/router_with_assembled_constants.py
+++ b/tests/detectors/router_with_assembled_constants.py
@@ -55,6 +55,13 @@ def echo(input: abi.Uint64, *, output: abi.Uint64) -> Expr:
         !=
         &&
         assert            // Assert(NoOp, CALL)
+
+    Args:
+        input: Input argument
+        output: Return value of the echo method.
+
+    Returns:
+        Body of the echo method as PyTeal expr.
     """
     return output.set(input.get())
 
diff --git a/tests/group_transactions/__init__.py b/tests/group_transactions/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/group_transactions/ans/ans_config.yaml b/tests/group_transactions/ans/ans_config.yaml
new file mode 100644
index 0000000..3d659ef
--- /dev/null
+++ b/tests/group_transactions/ans/ans_config.yaml
@@ -0,0 +1,197 @@
+name: Algorand Name Service
+contracts:
+  # dot_algo_registry.py
+  - name: Registry
+    file_path: registry_approval.teal
+    type: ApprovalProgram
+    version: 6
+    subroutines:
+      - basictxnchecks_0
+      - checkcloseremndr_1
+      - checkappargssize_2
+      - checkappacctssize_3
+      - iswithingraceperiod_4
+      - resetdomainproperties_5
+    functions:
+      # fill the function information after finalizing the config structure
+      - name: on_creation
+        dispatch_path: [B0, B84]
+      - name: on_optin
+        dispatch_path: [B0, B1, B83]
+      - name: on_update
+        dispatch_path: [B0, B1, B2, B81]
+      - name: on_delete
+        dispatch_path: [B0, B1, B2, B3, B79]
+      - name: on_closeout
+        dispatch_path: [B0, B1, B2, B3, B4, B78]
+      - name: update_global_state
+        dispatch_path: [B0, B1, B2, B3, B4, B5, B6, B75]
+      - name: register_name
+        dispatch_path: [B0, B1, B2, B3, B4, B5, B6, B7, B59]
+      - name: update_name
+        dispatch_path: [B0, B1, B2, B3, B4, B5, B6, B7, B8, B56]
+      - name: remove_property
+        dispatch_path: [B0, B1, B2, B3, B4, B5, B6, B7, B8, B9, B49]
+      - name: renew_name
+        dispatch_path: [B0, B1, B2, B3, B4, B5, B6, B7, B8, B9, B10, B38]
+      - name: update_resolver_account
+        dispatch_path: [B0, B1, B2, B3, B4, B5, B6, B7, B8, B9, B10, B11, B35]
+      - name: initiate_transfer
+        dispatch_path: [B0, B1, B2, B3, B4, B5, B6, B7, B8, B9, B10, B11, B12, B30]
+      - name: accept_transfer
+        dispatch_path: [B0, B1, B2, B3, B4, B5, B6, B7, B8, B9, B10, B11, B12, B13, B22]
+      - name: withdraw_transfer
+        dispatch_path: [B0, B1, B2, B3, B4, B5, B6, B7, B8, B9, B10, B11, B12, B13, B14, B19]
+      - name: withdraw_funds
+        dispatch_path: [B0, B1, B2, B3, B4, B5, B6, B7, B8, B9, B10, B11, B12, B13, B14, B15, B17]
+  - name: Record
+    file_path: name_record.teal
+    type: LogicSig
+    version: 6
+    subroutines: []
+    functions:
+      - name: Record
+        dispatch_path: [B0]
+        entry: B0
+        exit: [B0]
+
+# group configurations
+groups:
+  # single transactions
+  # Registry
+  - - txn_id: T1
+      txn_type: appl
+      application:
+        contract: Registry
+        function: on_creation
+  - - txn_id: T1
+      txn_type: appl
+      application:
+        contract: Registry
+        function: on_optin
+  - - txn_id: T1
+      txn_type: appl
+      application:
+        contract: Registry
+        function: on_update
+  - - txn_id: T1
+      txn_type: appl
+      application:
+        contract: Registry
+        function: on_delete
+  - - txn_id: T1
+      txn_type: appl
+      application:
+        contract: Registry
+        function: on_closeout
+  - - txn_id: T1
+      txn_type: appl
+      application:
+        contract: Registry
+        function: update_global_state
+  - - txn_id: T1
+      txn_type: appl
+      application:
+        contract: Registry
+        function: update_name
+  - - txn_id: T1
+      txn_type: appl
+      application:
+        contract: Registry
+        function: remove_property
+  - - txn_id: T1
+      txn_type: appl
+      application:
+        contract: Registry
+        function: update_resolver_account
+  - - txn_id: T1
+      txn_type: appl
+      application:
+        contract: Registry
+        function: initiate_transfer
+  - - txn_id: T1
+      txn_type: appl
+      application:
+        contract: Registry
+        function: withdraw_transfer
+  - - txn_id: T1
+      txn_type: appl
+      application:
+        contract: Registry
+        function: withdraw_funds
+  # multiple transactions
+  # Registry
+  # Registry.register_name; group size 2;
+  - - txn_id: T1
+      txn_type: pay
+      absolute_index: 0
+    - txn_id: T2
+      txn_type: appl
+      application:
+        contract: Registry
+        function: register_name
+      absolute_index: 1
+  # Registry.register_name; group size 4;
+  - - txn_id: T1
+      txn_type: pay
+      absolute_index: 0
+    - txn_id: T2
+      txn_type: pay
+      absolute_index: 1
+    - txn_id: T3
+      txn_type: appl
+      application:
+        contract: Registry
+        function: on_optin
+      absolute_index: 2
+    - txn_id: T4
+      txn_type: appl
+      application:
+        contract: Registry
+        function: register_name
+      absolute_index: 3
+  # Registry.register_name; group size 4; with logic-sig
+  - - txn_id: T1
+      txn_type: pay
+      absolute_index: 0
+    - txn_id: T2
+      txn_type: pay
+      absolute_index: 1
+    - txn_id: T3
+      txn_type: appl
+      application:
+        contract: Registry
+        function: on_optin
+      logic_sig:
+        contract: Record
+        function: Record
+      absolute_index: 2
+    - txn_id: T4
+      txn_type: appl
+      application:
+        contract: Registry
+        function: register_name
+      absolute_index: 3
+  # Registry.renew_name
+  - - txn_id: T1
+      txn_type: pay
+      absolute_index: 0
+    - txn_id: T2
+      txn_type: appl
+      application:
+        contract: Registry
+        function: renew_name
+      absolute_index: 1
+  # Registry.accept_transfer
+  - - txn_id: T1
+      txn_type: pay
+      absolute_index: 0
+    - txn_id: T2
+      txn_type: pay
+      absolute_index: 1
+    - txn_id: T3
+      txn_type: appl
+      application:
+        contract: Registry
+        function: accept_transfer
+      absolute_index: 2
diff --git a/tests/group_transactions/ans/name_record.teal b/tests/group_transactions/ans/name_record.teal
new file mode 100644
index 0000000..3bd9f01
--- /dev/null
+++ b/tests/group_transactions/ans/name_record.teal
@@ -0,0 +1,388 @@
+#pragma version 4
+byte "Record"
+len
+int 3
+==
+bnz main_l22
+byte "Record"
+len
+int 4
+==
+bnz main_l13
+byte "Record"
+len
+int 5
+>=
+bnz main_l4
+err
+main_l4:
+gtxn 0 Amount
+int 5000000
+>=
+assert
+byte "Record"
+len
+int 64
+<=
+assert
+int 0
+store 0
+main_l5:
+load 0
+byte "Record"
+len
+<
+bnz main_l12
+global GroupSize
+int 2
+==
+global GroupSize
+int 4
+==
+||
+assert
+gtxn 0 Sender
+gtxn 1 Sender
+==
+assert
+gtxn 0 Receiver
+addr 6ZIOGDXGSQSL4YINHLKCHYRV64FSN4LTUIQ6A4VWYK36FXFF42VI2UV7SM
+==
+assert
+global GroupSize
+int 2
+==
+bnz main_l11
+global GroupSize
+int 4
+==
+bnz main_l10
+int 0
+return
+main_l9:
+int 1
+assert
+int 1
+b main_l31
+main_l10:
+gtxn 1 Receiver
+gtxn 2 Sender
+==
+gtxn 2 ApplicationID
+int 1337
+==
+&&
+gtxn 2 OnCompletion
+int OptIn
+==
+&&
+gtxn 3 ApplicationID
+int 1337
+==
+&&
+gtxn 3 Sender
+gtxn 0 Sender
+==
+&&
+gtxna 3 ApplicationArgs 0
+byte "register_name"
+==
+&&
+gtxna 3 ApplicationArgs 1
+byte "Record"
+==
+&&
+assert
+b main_l9
+main_l11:
+gtxn 1 ApplicationID
+int 1337
+==
+gtxna 1 ApplicationArgs 0
+byte "register_name"
+==
+&&
+gtxna 1 ApplicationArgs 1
+byte "Record"
+==
+&&
+assert
+b main_l9
+main_l12:
+byte "Record"
+load 0
+getbyte
+int 97
+>=
+byte "Record"
+load 0
+getbyte
+int 122
+<=
+&&
+byte "Record"
+load 0
+getbyte
+int 48
+>=
+byte "Record"
+load 0
+getbyte
+int 57
+<=
+&&
+||
+assert
+load 0
+int 1
++
+store 0
+b main_l5
+main_l13:
+gtxn 0 Amount
+int 50000000
+>=
+assert
+byte "Record"
+len
+int 64
+<=
+assert
+int 0
+store 0
+main_l14:
+load 0
+byte "Record"
+len
+<
+bnz main_l21
+global GroupSize
+int 2
+==
+global GroupSize
+int 4
+==
+||
+assert
+gtxn 0 Sender
+gtxn 1 Sender
+==
+assert
+gtxn 0 Receiver
+addr 6ZIOGDXGSQSL4YINHLKCHYRV64FSN4LTUIQ6A4VWYK36FXFF42VI2UV7SM
+==
+assert
+global GroupSize
+int 2
+==
+bnz main_l20
+global GroupSize
+int 4
+==
+bnz main_l19
+int 0
+return
+main_l18:
+int 1
+assert
+int 1
+b main_l31
+main_l19:
+gtxn 1 Receiver
+gtxn 2 Sender
+==
+gtxn 2 ApplicationID
+int 1337
+==
+&&
+gtxn 2 OnCompletion
+int OptIn
+==
+&&
+gtxn 3 ApplicationID
+int 1337
+==
+&&
+gtxn 3 Sender
+gtxn 0 Sender
+==
+&&
+gtxna 3 ApplicationArgs 0
+byte "register_name"
+==
+&&
+gtxna 3 ApplicationArgs 1
+byte "Record"
+==
+&&
+assert
+b main_l18
+main_l20:
+gtxn 1 ApplicationID
+int 1337
+==
+gtxna 1 ApplicationArgs 0
+byte "register_name"
+==
+&&
+gtxna 1 ApplicationArgs 1
+byte "Record"
+==
+&&
+assert
+b main_l18
+main_l21:
+byte "Record"
+load 0
+getbyte
+int 97
+>=
+byte "Record"
+load 0
+getbyte
+int 122
+<=
+&&
+byte "Record"
+load 0
+getbyte
+int 48
+>=
+byte "Record"
+load 0
+getbyte
+int 57
+<=
+&&
+||
+assert
+load 0
+int 1
++
+store 0
+b main_l14
+main_l22:
+gtxn 0 Amount
+int 150000000
+>=
+assert
+byte "Record"
+len
+int 64
+<=
+assert
+int 0
+store 0
+main_l23:
+load 0
+byte "Record"
+len
+<
+bnz main_l30
+global GroupSize
+int 2
+==
+global GroupSize
+int 4
+==
+||
+assert
+gtxn 0 Sender
+gtxn 1 Sender
+==
+assert
+gtxn 0 Receiver
+addr 6ZIOGDXGSQSL4YINHLKCHYRV64FSN4LTUIQ6A4VWYK36FXFF42VI2UV7SM
+==
+assert
+global GroupSize
+int 2
+==
+bnz main_l29
+global GroupSize
+int 4
+==
+bnz main_l28
+int 0
+return
+main_l27:
+int 1
+assert
+int 1
+b main_l31
+main_l28:
+gtxn 1 Receiver
+gtxn 2 Sender
+==
+gtxn 2 ApplicationID
+int 1337
+==
+&&
+gtxn 2 OnCompletion
+int OptIn
+==
+&&
+gtxn 3 ApplicationID
+int 1337
+==
+&&
+gtxn 3 Sender
+gtxn 0 Sender
+==
+&&
+gtxna 3 ApplicationArgs 0
+byte "register_name"
+==
+&&
+gtxna 3 ApplicationArgs 1
+byte "Record"
+==
+&&
+assert
+b main_l27
+main_l29:
+gtxn 1 ApplicationID
+int 1337
+==
+gtxna 1 ApplicationArgs 0
+byte "register_name"
+==
+&&
+gtxna 1 ApplicationArgs 1
+byte "Record"
+==
+&&
+assert
+b main_l27
+main_l30:
+byte "Record"
+load 0
+getbyte
+int 97
+>=
+byte "Record"
+load 0
+getbyte
+int 122
+<=
+&&
+byte "Record"
+load 0
+getbyte
+int 48
+>=
+byte "Record"
+load 0
+getbyte
+int 57
+<=
+&&
+||
+assert
+load 0
+int 1
++
+store 0
+b main_l23
+main_l31:
+return
diff --git a/tests/group_transactions/ans/registry_approval.teal b/tests/group_transactions/ans/registry_approval.teal
new file mode 100644
index 0000000..dd6b042
--- /dev/null
+++ b/tests/group_transactions/ans/registry_approval.teal
@@ -0,0 +1,1104 @@
+#pragma version 6
+txn ApplicationID
+int 0
+==
+bnz main_l48
+txn OnCompletion
+int OptIn
+==
+bnz main_l47
+txn OnCompletion
+int UpdateApplication
+==
+bnz main_l46
+txn OnCompletion
+int DeleteApplication
+==
+bnz main_l45
+txn OnCompletion
+int CloseOut
+==
+bnz main_l44
+txn OnCompletion
+int ClearState
+==
+bnz main_l43
+txna ApplicationArgs 0
+byte "update_global_state"
+==
+bnz main_l42
+txna ApplicationArgs 0
+byte "register_name"
+==
+bnz main_l34
+txna ApplicationArgs 0
+byte "update_name"
+==
+bnz main_l33
+txna ApplicationArgs 0
+byte "remove_property"
+==
+bnz main_l29
+txna ApplicationArgs 0
+byte "renew_name"
+==
+bnz main_l22
+txna ApplicationArgs 0
+byte "update_resolver_account"
+==
+bnz main_l21
+txna ApplicationArgs 0
+byte "initiate_transfer"
+==
+bnz main_l20
+txna ApplicationArgs 0
+byte "accept_transfer"
+==
+bnz main_l19
+txna ApplicationArgs 0
+byte "withdraw_transfer"
+==
+bnz main_l18
+txna ApplicationArgs 0
+byte "withdraw_funds"
+==
+bnz main_l17
+err
+main_l17:
+callsub basictxnchecks_0
+int 1
+==
+assert
+txn Sender
+byte "name_controller"
+app_global_get
+==
+assert
+itxn_begin
+int pay
+itxn_field TypeEnum
+txna Accounts 1
+itxn_field Receiver
+txna ApplicationArgs 1
+btoi
+itxn_field Amount
+int 1000
+itxn_field Fee
+itxn_submit
+int 1
+return
+main_l18:
+callsub iswithingraceperiod_4
+int 0
+==
+assert
+callsub basictxnchecks_0
+int 1
+==
+assert
+int 1
+byte "owner"
+app_local_get
+txn Sender
+==
+assert
+txn NumAppArgs
+int 1
+==
+assert
+txn NumAccounts
+int 1
+==
+assert
+int 1
+byte "transfer_price"
+int 0
+app_local_put
+int 1
+byte "transfer_to"
+byte ""
+app_local_put
+int 1
+return
+main_l19:
+callsub iswithingraceperiod_4
+int 0
+==
+assert
+callsub basictxnchecks_0
+int 1
+==
+assert
+int 2
+int 1
+callsub checkappargssize_2
+assert
+int 2
+int 1
+callsub checkappacctssize_3
+assert
+global GroupSize
+int 3
+==
+assert
+gtxn 0 Receiver
+int 1
+byte "owner"
+app_local_get
+==
+assert
+gtxn 0 RekeyTo
+global ZeroAddress
+==
+assert
+int 0
+callsub checkcloseremndr_1
+int 1
+==
+assert
+gtxn 0 Amount
+int 1
+byte "transfer_price"
+app_local_get
+==
+assert
+gtxn 0 Sender
+int 1
+byte "transfer_to"
+app_local_get
+==
+assert
+gtxn 0 Sender
+gtxn 1 Sender
+==
+assert
+gtxn 0 Sender
+gtxn 2 Sender
+==
+assert
+gtxn 1 Receiver
+global CurrentApplicationAddress
+==
+assert
+gtxn 1 Amount
+int 2000000
+==
+assert
+gtxn 1 RekeyTo
+global ZeroAddress
+==
+assert
+int 1
+callsub checkcloseremndr_1
+int 1
+==
+assert
+callsub resetdomainproperties_5
+int 1
+==
+assert
+int 1
+byte "owner"
+gtxn 0 Sender
+app_local_put
+int 1
+byte "value"
+gtxn 0 Sender
+app_local_put
+int 1
+byte "transfer_to"
+byte ""
+app_local_put
+int 1
+byte "transfer_price"
+int 0
+app_local_put
+int 1
+byte "subdomain"
+int 0
+app_local_put
+int 1
+return
+main_l20:
+callsub iswithingraceperiod_4
+int 0
+==
+assert
+callsub basictxnchecks_0
+int 1
+==
+assert
+int 0
+int 2
+callsub checkappargssize_2
+assert
+int 0
+int 2
+callsub checkappacctssize_3
+assert
+global GroupSize
+int 1
+==
+assert
+int 1
+byte "owner"
+app_local_get
+txn Sender
+==
+assert
+int 1
+byte "transfer_price"
+txna ApplicationArgs 1
+btoi
+app_local_put
+int 1
+byte "transfer_to"
+txna Accounts 2
+app_local_put
+int 1
+return
+main_l21:
+callsub iswithingraceperiod_4
+int 0
+==
+assert
+callsub basictxnchecks_0
+int 1
+==
+assert
+global GroupSize
+int 1
+==
+assert
+txn NumAppArgs
+int 1
+==
+assert
+int 1
+byte "owner"
+app_local_get
+txn Sender
+==
+assert
+int 1
+byte "value"
+txna Accounts 2
+app_local_put
+int 1
+return
+main_l22:
+callsub basictxnchecks_0
+int 1
+==
+int 1
+int 2
+callsub checkappargssize_2
+int 1
+==
+&&
+int 1
+int 1
+callsub checkappacctssize_3
+int 1
+==
+&&
+int 0
+callsub checkcloseremndr_1
+int 1
+==
+&&
+global GroupSize
+int 2
+==
+&&
+gtxn 0 TypeEnum
+int pay
+==
+&&
+gtxn 0 Sender
+int 1
+byte "owner"
+app_local_get
+==
+&&
+gtxn 0 Receiver
+global CurrentApplicationAddress
+==
+&&
+gtxn 1 Sender
+gtxn 0 Sender
+==
+&&
+assert
+int 1
+byte "name"
+app_local_get
+len
+int 3
+==
+bnz main_l28
+int 1
+byte "name"
+app_local_get
+len
+int 4
+==
+bnz main_l27
+int 1
+byte "name"
+app_local_get
+len
+int 5
+>=
+bnz main_l26
+main_l25:
+int 1
+byte "expiry"
+int 1
+byte "expiry"
+app_local_get
+txna ApplicationArgs 1
+btoi
+int 31536000
+*
++
+app_local_put
+int 1
+return
+main_l26:
+gtxn 0 Amount
+txna ApplicationArgs 1
+btoi
+int 5000000
+*
+==
+assert
+b main_l25
+main_l27:
+gtxn 0 Amount
+txna ApplicationArgs 1
+btoi
+int 50000000
+*
+==
+assert
+b main_l25
+main_l28:
+gtxn 0 Amount
+txna ApplicationArgs 1
+btoi
+int 150000000
+*
+==
+assert
+b main_l25
+main_l29:
+callsub iswithingraceperiod_4
+int 0
+==
+assert
+callsub basictxnchecks_0
+int 1
+==
+callsub iswithingraceperiod_4
+int 0
+==
+&&
+txn NumAppArgs
+int 2
+==
+&&
+txna ApplicationArgs 1
+byte "name"
+!=
+&&
+txna ApplicationArgs 1
+byte "owner"
+!=
+&&
+txna ApplicationArgs 1
+byte "expiry"
+!=
+&&
+txna ApplicationArgs 1
+byte "transfer_price"
+!=
+&&
+txna ApplicationArgs 1
+byte "transfer_to"
+!=
+&&
+txna ApplicationArgs 1
+byte "subdomain"
+!=
+&&
+txna ApplicationArgs 1
+byte "value"
+!=
+&&
+assert
+int 1
+byte "owner"
+app_local_get
+txn Sender
+==
+assert
+int 1
+global CurrentApplicationID
+txna ApplicationArgs 1
+app_local_get_ex
+store 3
+store 2
+load 3
+bnz main_l32
+int 0
+return
+main_l31:
+int 1
+return
+main_l32:
+int 1
+txna ApplicationArgs 1
+app_local_del
+b main_l31
+main_l33:
+callsub iswithingraceperiod_4
+int 0
+==
+assert
+callsub basictxnchecks_0
+int 1
+==
+assert
+txn NumAppArgs
+int 3
+==
+assert
+txn NumAccounts
+int 1
+==
+assert
+txna ApplicationArgs 1
+byte "name"
+!=
+assert
+txna ApplicationArgs 1
+byte "owner"
+!=
+assert
+txna ApplicationArgs 1
+byte "expiry"
+!=
+assert
+txna ApplicationArgs 1
+byte "transfer_price"
+!=
+assert
+txna ApplicationArgs 1
+byte "transfer_to"
+!=
+assert
+txna ApplicationArgs 1
+byte "value"
+!=
+assert
+int 1
+byte "owner"
+app_local_get
+txn Sender
+==
+assert
+int 1
+txna ApplicationArgs 1
+txna ApplicationArgs 2
+app_local_put
+int 1
+return
+main_l34:
+int 0
+callsub checkcloseremndr_1
+int 1
+==
+assert
+callsub basictxnchecks_0
+int 1
+==
+assert
+global GroupSize
+int 2
+==
+global GroupSize
+int 4
+==
+||
+assert
+gtxn 0 Sender
+gtxn 1 Sender
+==
+assert
+gtxn 0 Receiver
+global CurrentApplicationAddress
+==
+assert
+global GroupSize
+int 2
+==
+bnz main_l41
+global GroupSize
+int 4
+==
+bnz main_l40
+int 0
+return
+main_l37:
+int 1
+assert
+int 1
+txn ApplicationID
+byte "owner"
+app_local_get_ex
+store 1
+store 0
+load 1
+bnz main_l39
+main_l38:
+load 1
+int 0
+==
+global LatestTimestamp
+int 1
+byte "expiry"
+app_local_get
+>=
+||
+assert
+txna ApplicationArgs 1
+len
+int 3
+==
+gtxn 0 Amount
+int 150000000
+txna ApplicationArgs 2
+btoi
+int 1
+-
+int 150000000
+*
++
+>=
+&&
+txna ApplicationArgs 1
+len
+int 4
+==
+gtxn 0 Amount
+int 50000000
+txna ApplicationArgs 2
+btoi
+int 1
+-
+int 50000000
+*
++
+>=
+&&
+||
+txna ApplicationArgs 1
+len
+int 5
+>=
+gtxn 0 Amount
+int 5000000
+txna ApplicationArgs 2
+btoi
+int 1
+-
+int 5000000
+*
++
+>=
+&&
+||
+assert
+int 1
+byte "owner"
+txn Sender
+app_local_put
+int 1
+byte "expiry"
+global LatestTimestamp
+int 31536000
+txna ApplicationArgs 2
+btoi
+int 1
+-
+int 1
++
+*
++
+app_local_put
+int 1
+byte "subdomain"
+int 0
+app_local_put
+int 1
+byte "transfer_price"
+byte ""
+app_local_put
+int 1
+byte "transfer_to"
+byte ""
+app_local_put
+int 1
+byte "value"
+txn Sender
+app_local_put
+int 1
+byte "name"
+txna ApplicationArgs 1
+app_local_put
+int 1
+return
+main_l39:
+global LatestTimestamp
+int 1
+byte "expiry"
+app_local_get
+int 7776000
++
+>=
+assert
+b main_l38
+main_l40:
+int 2
+int 0
+callsub checkappacctssize_3
+int 3
+int 1
+callsub checkappacctssize_3
+&&
+int 1
+callsub checkcloseremndr_1
+int 1
+==
+&&
+int 3
+int 3
+callsub checkappargssize_2
+int 1
+==
+&&
+gtxn 1 Receiver
+gtxn 2 Sender
+==
+&&
+gtxn 2 ApplicationID
+global CurrentApplicationID
+==
+&&
+gtxn 2 OnCompletion
+int OptIn
+==
+&&
+gtxn 3 ApplicationID
+global CurrentApplicationID
+==
+&&
+gtxn 3 Sender
+gtxn 0 Sender
+==
+&&
+gtxna 3 ApplicationArgs 0
+byte "register_name"
+==
+&&
+assert
+b main_l37
+main_l41:
+int 1
+int 1
+callsub checkappacctssize_3
+int 1
+int 3
+callsub checkappargssize_2
+int 1
+==
+&&
+gtxn 1 ApplicationID
+global CurrentApplicationID
+==
+&&
+gtxn 1 Sender
+gtxn 0 Sender
+==
+&&
+gtxna 1 ApplicationArgs 0
+byte "register_name"
+==
+&&
+assert
+b main_l37
+main_l42:
+callsub basictxnchecks_0
+int 1
+==
+assert
+txn Sender
+byte "name_controller"
+app_global_get
+==
+assert
+txna ApplicationArgs 1
+txna ApplicationArgs 2
+app_global_put
+int 1
+return
+main_l43:
+int 0
+return
+main_l44:
+int 0
+return
+main_l45:
+callsub basictxnchecks_0
+int 1
+==
+assert
+txn Sender
+byte "name_controller"
+app_global_get
+==
+assert
+int 1
+return
+main_l46:
+callsub basictxnchecks_0
+int 1
+==
+assert
+txn Sender
+byte "name_controller"
+app_global_get
+==
+assert
+int 1
+return
+main_l47:
+int 1
+return
+main_l48:
+byte "name_controller"
+txn Sender
+app_global_put
+int 1
+return
+
+// basic_txn_checks
+basictxnchecks_0:
+int 0
+store 4
+basictxnchecks_0_l1:
+load 4
+global GroupSize
+<
+bz basictxnchecks_0_l3
+load 4
+gtxns RekeyTo
+global ZeroAddress
+==
+load 4
+gtxns NumApplications
+int 0
+==
+&&
+load 4
+gtxns NumAssets
+int 0
+==
+&&
+load 4
+gtxns Fee
+int 1000
+<=
+&&
+assert
+load 4
+int 1
++
+store 4
+b basictxnchecks_0_l1
+basictxnchecks_0_l3:
+int 1
+retsub
+
+// check_closeremndr
+checkcloseremndr_1:
+store 5
+load 5
+gtxns CloseRemainderTo
+global ZeroAddress
+==
+assert
+int 1
+retsub
+
+// check_app_args_size
+checkappargssize_2:
+store 7
+store 6
+load 6
+gtxns NumAppArgs
+load 7
+==
+assert
+int 1
+retsub
+
+// check_app_accts_size
+checkappacctssize_3:
+store 9
+store 8
+load 8
+gtxns NumAccounts
+load 9
+==
+assert
+int 1
+retsub
+
+// is_within_grace_period
+iswithingraceperiod_4:
+int 1
+txn ApplicationID
+byte "owner"
+app_local_get_ex
+store 1
+store 0
+load 1
+bnz iswithingraceperiod_4_l2
+int 0
+retsub
+iswithingraceperiod_4_l2:
+global LatestTimestamp
+int 1
+byte "expiry"
+app_local_get
+int 7776000
++
+<=
+global LatestTimestamp
+int 1
+byte "expiry"
+app_local_get
+>=
+&&
+bnz iswithingraceperiod_4_l4
+int 0
+retsub
+iswithingraceperiod_4_l4:
+int 1
+retsub
+
+// reset_domain_properties
+resetdomainproperties_5:
+int 1
+txn ApplicationID
+byte "discord"
+app_local_get_ex
+store 11
+store 10
+int 1
+txn ApplicationID
+byte "discord"
+app_local_get_ex
+store 11
+store 10
+load 11
+bnz resetdomainproperties_5_l19
+resetdomainproperties_5_l1:
+int 1
+txn ApplicationID
+byte "github"
+app_local_get_ex
+store 13
+store 12
+int 1
+txn ApplicationID
+byte "github"
+app_local_get_ex
+store 13
+store 12
+load 13
+bnz resetdomainproperties_5_l18
+resetdomainproperties_5_l2:
+int 1
+txn ApplicationID
+byte "twitter"
+app_local_get_ex
+store 15
+store 14
+int 1
+txn ApplicationID
+byte "twitter"
+app_local_get_ex
+store 15
+store 14
+load 15
+bnz resetdomainproperties_5_l17
+resetdomainproperties_5_l3:
+int 1
+txn ApplicationID
+byte "reddit"
+app_local_get_ex
+store 17
+store 16
+int 1
+txn ApplicationID
+byte "reddit"
+app_local_get_ex
+store 17
+store 16
+load 17
+bnz resetdomainproperties_5_l16
+resetdomainproperties_5_l4:
+int 1
+txn ApplicationID
+byte "telegram"
+app_local_get_ex
+store 19
+store 18
+int 1
+txn ApplicationID
+byte "telegram"
+app_local_get_ex
+store 19
+store 18
+load 19
+bnz resetdomainproperties_5_l15
+resetdomainproperties_5_l5:
+int 1
+txn ApplicationID
+byte "youtube"
+app_local_get_ex
+store 21
+store 20
+int 1
+txn ApplicationID
+byte "youtube"
+app_local_get_ex
+store 21
+store 20
+load 21
+bnz resetdomainproperties_5_l14
+resetdomainproperties_5_l6:
+int 1
+txn ApplicationID
+byte "avatar"
+app_local_get_ex
+store 23
+store 22
+int 1
+txn ApplicationID
+byte "avatar"
+app_local_get_ex
+store 23
+store 22
+load 23
+bnz resetdomainproperties_5_l13
+resetdomainproperties_5_l7:
+int 1
+txn ApplicationID
+byte "content"
+app_local_get_ex
+store 25
+store 24
+int 1
+txn ApplicationID
+byte "content"
+app_local_get_ex
+store 25
+store 24
+load 25
+bnz resetdomainproperties_5_l12
+resetdomainproperties_5_l8:
+int 1
+txn ApplicationID
+byte "ipaddress"
+app_local_get_ex
+store 27
+store 26
+int 1
+txn ApplicationID
+byte "ipaddress"
+app_local_get_ex
+store 27
+store 26
+load 27
+bnz resetdomainproperties_5_l11
+resetdomainproperties_5_l9:
+int 1
+txn ApplicationID
+byte "is_default"
+app_local_get_ex
+store 29
+store 28
+int 1
+txn ApplicationID
+byte "is_default"
+app_local_get_ex
+store 29
+store 28
+load 29
+bz resetdomainproperties_5_l20
+int 1
+byte "is_default"
+app_local_del
+b resetdomainproperties_5_l20
+resetdomainproperties_5_l11:
+int 1
+byte "ipaddress"
+app_local_del
+b resetdomainproperties_5_l9
+resetdomainproperties_5_l12:
+int 1
+byte "content"
+app_local_del
+b resetdomainproperties_5_l8
+resetdomainproperties_5_l13:
+int 1
+byte "avatar"
+app_local_del
+b resetdomainproperties_5_l7
+resetdomainproperties_5_l14:
+int 1
+byte "youtube"
+app_local_del
+b resetdomainproperties_5_l6
+resetdomainproperties_5_l15:
+int 1
+byte "telegram"
+app_local_del
+b resetdomainproperties_5_l5
+resetdomainproperties_5_l16:
+int 1
+byte "reddit"
+app_local_del
+b resetdomainproperties_5_l4
+resetdomainproperties_5_l17:
+int 1
+byte "twitter"
+app_local_del
+b resetdomainproperties_5_l3
+resetdomainproperties_5_l18:
+int 1
+byte "github"
+app_local_del
+b resetdomainproperties_5_l2
+resetdomainproperties_5_l19:
+int 1
+byte "discord"
+app_local_del
+b resetdomainproperties_5_l1
+resetdomainproperties_5_l20:
+int 1
+retsub
diff --git a/tests/group_transactions/basic/__init__.py b/tests/group_transactions/basic/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/group_transactions/basic/app_1.py b/tests/group_transactions/basic/app_1.py
new file mode 100644
index 0000000..118e36f
--- /dev/null
+++ b/tests/group_transactions/basic/app_1.py
@@ -0,0 +1,409 @@
+# pylint: disable=undefined-variable
+# type: ignore[name-defined]
+from pathlib import Path
+from pyteal import *  # pylint: disable=wildcard-import, unused-wildcard-import
+
+
+def app1_case_1() -> Expr:
+    return Return(Int(1))
+
+
+def app1_case_2() -> Expr:
+    return Return(Int(1))
+
+
+def app1_case_3() -> Expr:
+    return Return(Int(1))
+
+
+def app1_case_4() -> Expr:
+    return Return(Int(1))
+
+
+def app1_case_5() -> Expr:
+    return Seq(
+        [
+            Assert(
+                And(
+                    Txn.rekey_to() == Global.zero_address(),
+                    Txn.fee() < Int(10000),
+                    Txn.close_remainder_to() == Global.zero_address(),
+                    Txn.asset_close_to() == Global.zero_address(),
+                    Txn.on_completion() != OnComplete.UpdateApplication,
+                    Txn.on_completion() != OnComplete.DeleteApplication,
+                )
+            ),
+            Return(Int(1)),
+        ]
+    )
+
+
+def app1_case_6() -> Expr:
+    return Seq(
+        [
+            Assert(
+                And(
+                    Gtxn[Txn.group_index()].rekey_to() == Global.zero_address(),
+                    Gtxn[Txn.group_index()].fee() < Int(10000),
+                    Gtxn[Txn.group_index()].close_remainder_to() == Global.zero_address(),
+                    Gtxn[Txn.group_index()].asset_close_to() == Global.zero_address(),
+                    Gtxn[Txn.group_index()].on_completion() != OnComplete.UpdateApplication,
+                    Gtxn[Txn.group_index()].on_completion() != OnComplete.DeleteApplication,
+                )
+            ),
+            Return(Int(1)),
+        ]
+    )
+
+
+def app1_case_7() -> Expr:
+    return Seq(
+        [
+            Assert(
+                And(
+                    Gtxn[0].rekey_to() == Global.zero_address(),
+                    Gtxn[1].rekey_to() == Global.zero_address(),
+                    Gtxn[Int(0)].fee() < Int(100000),
+                    Gtxn[1].fee() < Int(1000),
+                    Gtxn[Int(0)].close_remainder_to() == Global.zero_address(),
+                    Gtxn[1].close_remainder_to() == Global.zero_address(),
+                    Gtxn[Int(0)].asset_close_to() == Global.zero_address(),
+                    Gtxn[1].asset_close_to() == Global.zero_address(),
+                    Gtxn[0].on_completion() != OnComplete.UpdateApplication,
+                    Gtxn[0].on_completion() != OnComplete.DeleteApplication,
+                    Gtxn[Int(1)].on_completion() != OnComplete.UpdateApplication,
+                    Gtxn[Int(1)].on_completion() != OnComplete.DeleteApplication,
+                )
+            ),
+            Return(Int(1)),
+        ]
+    )
+
+
+def app1_case_8() -> Expr:
+    return Seq(
+        [
+            Assert(
+                And(
+                    Gtxn[0].rekey_to() == Global.zero_address(),
+                    Gtxn[0].fee() < Int(10000),
+                    Gtxn[0].close_remainder_to() == Global.zero_address(),
+                    Gtxn[0].asset_close_to() == Global.zero_address(),
+                )
+            ),
+            Assert(
+                And(
+                    Gtxn[0].on_completion() != OnComplete.UpdateApplication,
+                    Gtxn[0].on_completion() != OnComplete.DeleteApplication,
+                )
+            ),
+            Return(Int(1)),
+        ]
+    )
+
+
+def app1_case_9() -> Expr:
+    return Seq(
+        [
+            Assert(
+                And(
+                    Gtxn[1].rekey_to() == Global.zero_address(),
+                    Gtxn[1].fee() < Int(10000),
+                    Gtxn[1].close_remainder_to() == Global.zero_address(),
+                    Gtxn[1].asset_close_to() == Global.zero_address(),
+                )
+            ),
+            Assert(
+                And(
+                    Gtxn[1].on_completion() != OnComplete.UpdateApplication,
+                    Gtxn[1].on_completion() != OnComplete.DeleteApplication,
+                )
+            ),
+            Return(Int(1)),
+        ]
+    )
+
+
+def app1_case_10() -> Expr:
+    return Return(Int(1))
+
+
+def app1_case_11() -> Expr:
+    return Return(Int(1))
+
+
+def app1_case_12() -> Expr:
+    return Return(Int(1))
+
+
+def app1_case_13() -> Expr:
+    return Return(Int(1))
+
+
+def app1_case_14() -> Expr:
+    return Return(Int(1))
+
+
+def app1_case_15() -> Expr:
+    return Return(Int(1))
+
+
+def app1_case_16() -> Expr:
+    return Return(Int(1))
+
+
+def app1_case_17() -> Expr:
+    return Return(Int(1))
+
+
+def app1_case_18() -> Expr:
+    return Seq(
+        [
+            Assert(
+                And(
+                    Gtxn[0].rekey_to() == Global.zero_address(),
+                    Gtxn[Txn.group_index() + Int(4)].rekey_to() == Global.zero_address(),
+                    Gtxn[0].fee() < Int(10000),
+                    Gtxn[Txn.group_index() + Int(4)].fee() < Int(10000),
+                    Gtxn[0].close_remainder_to() == Global.zero_address(),
+                    Gtxn[Txn.group_index() + Int(4)].close_remainder_to() == Global.zero_address(),
+                    Gtxn[0].asset_close_to() == Global.zero_address(),
+                    Gtxn[Txn.group_index() + Int(4)].asset_close_to() == Global.zero_address(),
+                )
+            ),
+            Assert(
+                And(
+                    Gtxn[0].on_completion() != OnComplete.UpdateApplication,
+                    Gtxn[0].on_completion() != OnComplete.DeleteApplication,
+                    Gtxn[Txn.group_index() + Int(4)].on_completion()
+                    != OnComplete.UpdateApplication,
+                    Gtxn[Txn.group_index() + Int(4)].on_completion()
+                    != OnComplete.DeleteApplication,
+                )
+            ),
+            Return(Int(1)),
+        ]
+    )
+
+
+def app1_case_19() -> Expr:
+    return Seq(
+        [
+            Assert(Gtxn[Txn.group_index() + Int(4)].rekey_to() == Global.zero_address()),
+            Assert(Gtxn[Txn.group_index() + Int(4)].fee() < Int(10000)),
+            Assert(Gtxn[Txn.group_index() + Int(4)].asset_close_to() == Global.zero_address()),
+            Assert(Gtxn[Txn.group_index() + Int(4)].close_remainder_to() == Global.zero_address()),
+            Assert(
+                And(
+                    Gtxn[Txn.group_index() + Int(4)].on_completion()
+                    != OnComplete.UpdateApplication,
+                    Gtxn[Txn.group_index() + Int(4)].on_completion()
+                    != OnComplete.DeleteApplication,
+                )
+            ),
+            Return(Int(1)),
+        ]
+    )
+
+
+def app1_case_20() -> Expr:
+    return Seq(
+        [
+            Assert(Gtxn[1].rekey_to() == Global.zero_address()),
+            Assert(Gtxn[1].fee() < Int(10000)),
+            Assert(Gtxn[1].asset_close_to() == Global.zero_address()),
+            Assert(Gtxn[1].close_remainder_to() == Global.zero_address()),
+            Assert(
+                And(
+                    Gtxn[1].on_completion() != OnComplete.UpdateApplication,
+                    Gtxn[1].on_completion() != OnComplete.DeleteApplication,
+                )
+            ),
+            Return(Int(1)),
+        ]
+    )
+
+
+def app1_case_21() -> Expr:
+    return Seq(
+        [
+            Assert(Gtxn[Txn.group_index() + Int(2)].rekey_to() == Global.zero_address()),
+            Assert(Gtxn[Txn.group_index() + Int(2)].fee() < Int(10000)),
+            Assert(Gtxn[Txn.group_index() + Int(2)].asset_close_to() == Global.zero_address()),
+            Assert(Gtxn[Txn.group_index() + Int(2)].close_remainder_to() == Global.zero_address()),
+            Assert(
+                And(
+                    Gtxn[Txn.group_index() + Int(2)].on_completion()
+                    != OnComplete.UpdateApplication,
+                    Gtxn[Txn.group_index() + Int(2)].on_completion()
+                    != OnComplete.DeleteApplication,
+                )
+            ),
+            Return(Int(1)),
+        ]
+    )
+
+
+def app1_case_22() -> Expr:
+    return Return(Int(1))
+
+
+def app1_case_23() -> Expr:
+    return Return(Int(1))
+
+
+def app1_case_24() -> Expr:
+    return Return(Int(1))
+
+
+def app1_case_25() -> Expr:
+    return Return(Int(1))
+
+
+def app1_case_26() -> Expr:
+    return Return(Int(1))
+
+
+def app1_case_27() -> Expr:
+    return Return(Int(1))
+
+
+def app1_case_28() -> Expr:
+    return Seq(
+        [
+            Assert(Gtxn[Txn.group_index() + Int(5)].rekey_to() == Global.zero_address()),
+            Assert(Gtxn[Txn.group_index() + Int(5)].fee() < Int(10000)),
+            Assert(Gtxn[Txn.group_index() + Int(5)].asset_close_to() == Global.zero_address()),
+            Assert(Gtxn[Txn.group_index() + Int(5)].close_remainder_to() == Global.zero_address()),
+            Assert(
+                And(
+                    Gtxn[Txn.group_index() + Int(5)].on_completion()
+                    != OnComplete.UpdateApplication,
+                    Gtxn[Txn.group_index() + Int(5)].on_completion()
+                    != OnComplete.DeleteApplication,
+                )
+            ),
+            Return(Int(1)),
+        ]
+    )
+
+
+def app1_case_29() -> Expr:
+    return Return(Int(1))
+
+
+def app1_case_30() -> Expr:
+    return Seq(
+        [
+            Assert(
+                And(
+                    Gtxn[0].rekey_to() == Global.zero_address(),
+                    Gtxn[0].fee() < Int(10000),
+                    Gtxn[0].close_remainder_to() == Global.zero_address(),
+                    Gtxn[0].asset_close_to() == Global.zero_address(),
+                    Gtxn[0].on_completion() != OnComplete.UpdateApplication,
+                    Gtxn[0].on_completion() != OnComplete.DeleteApplication,
+                )
+            ),
+            Return(Int(1)),
+        ]
+    )
+
+
+def app1_case_31() -> Expr:
+    return Return(Int(1))
+
+
+def app1_case_32() -> Expr:
+    return Return(Int(1))
+
+
+def app1_case_33() -> Expr:
+    return Return(Int(1))
+
+
+def app1_case_34() -> Expr:
+    return Return(Int(1))
+
+
+def app1_case_35() -> Expr:
+    return Return(Int(1))
+
+
+def app1_case_36() -> Expr:
+    return Seq(
+        [
+            Assert(
+                And(
+                    Gtxn[4].rekey_to() == Global.zero_address(),
+                    Gtxn[4].fee() < Int(10000),
+                    Gtxn[4].close_remainder_to() == Global.zero_address(),
+                    Gtxn[4].asset_close_to() == Global.zero_address(),
+                    Gtxn[4].on_completion() != OnComplete.UpdateApplication,
+                    Gtxn[4].on_completion() != OnComplete.DeleteApplication,
+                )
+            ),
+            Return(Int(1)),
+        ]
+    )
+
+
+def app1_case_37() -> Expr:
+    return Return(Int(1))
+
+
+def approval_program() -> Expr:
+    test_case = Btoi(Txn.application_args[Int(0)])
+    return Seq(
+        [
+            Cond(
+                [test_case == Int(1), app1_case_1()],
+                [test_case == Int(2), app1_case_2()],
+                [test_case == Int(3), app1_case_3()],
+                [test_case == Int(4), app1_case_4()],
+                [test_case == Int(5), app1_case_5()],
+                [test_case == Int(6), app1_case_6()],
+                [test_case == Int(7), app1_case_7()],
+                [test_case == Int(8), app1_case_8()],
+                [test_case == Int(9), app1_case_9()],
+                [test_case == Int(10), app1_case_10()],
+                [test_case == Int(11), app1_case_11()],
+                [test_case == Int(12), app1_case_12()],
+                [test_case == Int(13), app1_case_13()],
+                [test_case == Int(14), app1_case_14()],
+                [test_case == Int(15), app1_case_15()],
+                [test_case == Int(16), app1_case_16()],
+                [test_case == Int(17), app1_case_17()],
+                [test_case == Int(18), app1_case_18()],
+                [test_case == Int(19), app1_case_19()],
+                [test_case == Int(20), app1_case_20()],
+                [test_case == Int(21), app1_case_21()],
+                [test_case == Int(22), app1_case_22()],
+                [test_case == Int(23), app1_case_23()],
+                [test_case == Int(24), app1_case_24()],
+                [test_case == Int(25), app1_case_25()],
+                [test_case == Int(26), app1_case_26()],
+                [test_case == Int(27), app1_case_27()],
+                [test_case == Int(28), app1_case_28()],
+                [test_case == Int(29), app1_case_29()],
+                [test_case == Int(30), app1_case_30()],
+                [test_case == Int(31), app1_case_31()],
+                [test_case == Int(32), app1_case_32()],
+                [test_case == Int(33), app1_case_33()],
+                [test_case == Int(34), app1_case_34()],
+                [test_case == Int(35), app1_case_35()],
+                [test_case == Int(36), app1_case_36()],
+                [test_case == Int(37), app1_case_37()],
+            ),
+        ]
+    )
+
+
+def compile_program(output_file: Path) -> None:
+    teal = compileTeal(approval_program(), mode=Mode.Application, version=7)
+    with open(output_file, "w", encoding="utf-8") as f:
+        f.write(teal)
+
+
+if __name__ == "__main__":
+    compile_program(Path("app_1.teal"))
diff --git a/tests/group_transactions/basic/app_1.teal b/tests/group_transactions/basic/app_1.teal
new file mode 100644
index 0000000..5bc1fa0
--- /dev/null
+++ b/tests/group_transactions/basic/app_1.teal
@@ -0,0 +1,754 @@
+#pragma version 7
+int 0
+txnas ApplicationArgs
+btoi
+int 1
+==
+bnz main_l74
+int 0
+txnas ApplicationArgs
+btoi
+int 2
+==
+bnz main_l73
+int 0
+txnas ApplicationArgs
+btoi
+int 3
+==
+bnz main_l72
+int 0
+txnas ApplicationArgs
+btoi
+int 4
+==
+bnz main_l71
+int 0
+txnas ApplicationArgs
+btoi
+int 5
+==
+bnz main_l70
+int 0
+txnas ApplicationArgs
+btoi
+int 6
+==
+bnz main_l69
+int 0
+txnas ApplicationArgs
+btoi
+int 7
+==
+bnz main_l68
+int 0
+txnas ApplicationArgs
+btoi
+int 8
+==
+bnz main_l67
+int 0
+txnas ApplicationArgs
+btoi
+int 9
+==
+bnz main_l66
+int 0
+txnas ApplicationArgs
+btoi
+int 10
+==
+bnz main_l65
+int 0
+txnas ApplicationArgs
+btoi
+int 11
+==
+bnz main_l64
+int 0
+txnas ApplicationArgs
+btoi
+int 12
+==
+bnz main_l63
+int 0
+txnas ApplicationArgs
+btoi
+int 13
+==
+bnz main_l62
+int 0
+txnas ApplicationArgs
+btoi
+int 14
+==
+bnz main_l61
+int 0
+txnas ApplicationArgs
+btoi
+int 15
+==
+bnz main_l60
+int 0
+txnas ApplicationArgs
+btoi
+int 16
+==
+bnz main_l59
+int 0
+txnas ApplicationArgs
+btoi
+int 17
+==
+bnz main_l58
+int 0
+txnas ApplicationArgs
+btoi
+int 18
+==
+bnz main_l57
+int 0
+txnas ApplicationArgs
+btoi
+int 19
+==
+bnz main_l56
+int 0
+txnas ApplicationArgs
+btoi
+int 20
+==
+bnz main_l55
+int 0
+txnas ApplicationArgs
+btoi
+int 21
+==
+bnz main_l54
+int 0
+txnas ApplicationArgs
+btoi
+int 22
+==
+bnz main_l53
+int 0
+txnas ApplicationArgs
+btoi
+int 23
+==
+bnz main_l52
+int 0
+txnas ApplicationArgs
+btoi
+int 24
+==
+bnz main_l51
+int 0
+txnas ApplicationArgs
+btoi
+int 25
+==
+bnz main_l50
+int 0
+txnas ApplicationArgs
+btoi
+int 26
+==
+bnz main_l49
+int 0
+txnas ApplicationArgs
+btoi
+int 27
+==
+bnz main_l48
+int 0
+txnas ApplicationArgs
+btoi
+int 28
+==
+bnz main_l47
+int 0
+txnas ApplicationArgs
+btoi
+int 29
+==
+bnz main_l46
+int 0
+txnas ApplicationArgs
+btoi
+int 30
+==
+bnz main_l45
+int 0
+txnas ApplicationArgs
+btoi
+int 31
+==
+bnz main_l44
+int 0
+txnas ApplicationArgs
+btoi
+int 32
+==
+bnz main_l43
+int 0
+txnas ApplicationArgs
+btoi
+int 33
+==
+bnz main_l42
+int 0
+txnas ApplicationArgs
+btoi
+int 34
+==
+bnz main_l41
+int 0
+txnas ApplicationArgs
+btoi
+int 35
+==
+bnz main_l40
+int 0
+txnas ApplicationArgs
+btoi
+int 36
+==
+bnz main_l39
+int 0
+txnas ApplicationArgs
+btoi
+int 37
+==
+bnz main_l38
+err
+main_l38:
+int 1
+return
+main_l39:
+gtxn 4 RekeyTo
+global ZeroAddress
+==
+gtxn 4 Fee
+int 10000
+<
+&&
+gtxn 4 CloseRemainderTo
+global ZeroAddress
+==
+&&
+gtxn 4 AssetCloseTo
+global ZeroAddress
+==
+&&
+gtxn 4 OnCompletion
+int UpdateApplication
+!=
+&&
+gtxn 4 OnCompletion
+int DeleteApplication
+!=
+&&
+assert
+int 1
+return
+main_l40:
+int 1
+return
+main_l41:
+int 1
+return
+main_l42:
+int 1
+return
+main_l43:
+int 1
+return
+main_l44:
+int 1
+return
+main_l45:
+gtxn 0 RekeyTo
+global ZeroAddress
+==
+gtxn 0 Fee
+int 10000
+<
+&&
+gtxn 0 CloseRemainderTo
+global ZeroAddress
+==
+&&
+gtxn 0 AssetCloseTo
+global ZeroAddress
+==
+&&
+gtxn 0 OnCompletion
+int UpdateApplication
+!=
+&&
+gtxn 0 OnCompletion
+int DeleteApplication
+!=
+&&
+assert
+int 1
+return
+main_l46:
+int 1
+return
+main_l47:
+txn GroupIndex
+int 5
++
+gtxns RekeyTo
+global ZeroAddress
+==
+assert
+txn GroupIndex
+int 5
++
+gtxns Fee
+int 10000
+<
+assert
+txn GroupIndex
+int 5
++
+gtxns AssetCloseTo
+global ZeroAddress
+==
+assert
+txn GroupIndex
+int 5
++
+gtxns CloseRemainderTo
+global ZeroAddress
+==
+assert
+txn GroupIndex
+int 5
++
+gtxns OnCompletion
+int UpdateApplication
+!=
+txn GroupIndex
+int 5
++
+gtxns OnCompletion
+int DeleteApplication
+!=
+&&
+assert
+int 1
+return
+main_l48:
+int 1
+return
+main_l49:
+int 1
+return
+main_l50:
+int 1
+return
+main_l51:
+int 1
+return
+main_l52:
+int 1
+return
+main_l53:
+int 1
+return
+main_l54:
+txn GroupIndex
+int 2
++
+gtxns RekeyTo
+global ZeroAddress
+==
+assert
+txn GroupIndex
+int 2
++
+gtxns Fee
+int 10000
+<
+assert
+txn GroupIndex
+int 2
++
+gtxns AssetCloseTo
+global ZeroAddress
+==
+assert
+txn GroupIndex
+int 2
++
+gtxns CloseRemainderTo
+global ZeroAddress
+==
+assert
+txn GroupIndex
+int 2
++
+gtxns OnCompletion
+int UpdateApplication
+!=
+txn GroupIndex
+int 2
++
+gtxns OnCompletion
+int DeleteApplication
+!=
+&&
+assert
+int 1
+return
+main_l55:
+gtxn 1 RekeyTo
+global ZeroAddress
+==
+assert
+gtxn 1 Fee
+int 10000
+<
+assert
+gtxn 1 AssetCloseTo
+global ZeroAddress
+==
+assert
+gtxn 1 CloseRemainderTo
+global ZeroAddress
+==
+assert
+gtxn 1 OnCompletion
+int UpdateApplication
+!=
+gtxn 1 OnCompletion
+int DeleteApplication
+!=
+&&
+assert
+int 1
+return
+main_l56:
+txn GroupIndex
+int 4
++
+gtxns RekeyTo
+global ZeroAddress
+==
+assert
+txn GroupIndex
+int 4
++
+gtxns Fee
+int 10000
+<
+assert
+txn GroupIndex
+int 4
++
+gtxns AssetCloseTo
+global ZeroAddress
+==
+assert
+txn GroupIndex
+int 4
++
+gtxns CloseRemainderTo
+global ZeroAddress
+==
+assert
+txn GroupIndex
+int 4
++
+gtxns OnCompletion
+int UpdateApplication
+!=
+txn GroupIndex
+int 4
++
+gtxns OnCompletion
+int DeleteApplication
+!=
+&&
+assert
+int 1
+return
+main_l57:
+gtxn 0 RekeyTo
+global ZeroAddress
+==
+txn GroupIndex
+int 4
++
+gtxns RekeyTo
+global ZeroAddress
+==
+&&
+gtxn 0 Fee
+int 10000
+<
+&&
+txn GroupIndex
+int 4
++
+gtxns Fee
+int 10000
+<
+&&
+gtxn 0 CloseRemainderTo
+global ZeroAddress
+==
+&&
+txn GroupIndex
+int 4
++
+gtxns CloseRemainderTo
+global ZeroAddress
+==
+&&
+gtxn 0 AssetCloseTo
+global ZeroAddress
+==
+&&
+txn GroupIndex
+int 4
++
+gtxns AssetCloseTo
+global ZeroAddress
+==
+&&
+assert
+gtxn 0 OnCompletion
+int UpdateApplication
+!=
+gtxn 0 OnCompletion
+int DeleteApplication
+!=
+&&
+txn GroupIndex
+int 4
++
+gtxns OnCompletion
+int UpdateApplication
+!=
+&&
+txn GroupIndex
+int 4
++
+gtxns OnCompletion
+int DeleteApplication
+!=
+&&
+assert
+int 1
+return
+main_l58:
+int 1
+return
+main_l59:
+int 1
+return
+main_l60:
+int 1
+return
+main_l61:
+int 1
+return
+main_l62:
+int 1
+return
+main_l63:
+int 1
+return
+main_l64:
+int 1
+return
+main_l65:
+int 1
+return
+main_l66:
+gtxn 1 RekeyTo
+global ZeroAddress
+==
+gtxn 1 Fee
+int 10000
+<
+&&
+gtxn 1 CloseRemainderTo
+global ZeroAddress
+==
+&&
+gtxn 1 AssetCloseTo
+global ZeroAddress
+==
+&&
+assert
+gtxn 1 OnCompletion
+int UpdateApplication
+!=
+gtxn 1 OnCompletion
+int DeleteApplication
+!=
+&&
+assert
+int 1
+return
+main_l67:
+gtxn 0 RekeyTo
+global ZeroAddress
+==
+gtxn 0 Fee
+int 10000
+<
+&&
+gtxn 0 CloseRemainderTo
+global ZeroAddress
+==
+&&
+gtxn 0 AssetCloseTo
+global ZeroAddress
+==
+&&
+assert
+gtxn 0 OnCompletion
+int UpdateApplication
+!=
+gtxn 0 OnCompletion
+int DeleteApplication
+!=
+&&
+assert
+int 1
+return
+main_l68:
+gtxn 0 RekeyTo
+global ZeroAddress
+==
+gtxn 1 RekeyTo
+global ZeroAddress
+==
+&&
+int 0
+gtxns Fee
+int 100000
+<
+&&
+gtxn 1 Fee
+int 1000
+<
+&&
+int 0
+gtxns CloseRemainderTo
+global ZeroAddress
+==
+&&
+gtxn 1 CloseRemainderTo
+global ZeroAddress
+==
+&&
+int 0
+gtxns AssetCloseTo
+global ZeroAddress
+==
+&&
+gtxn 1 AssetCloseTo
+global ZeroAddress
+==
+&&
+gtxn 0 OnCompletion
+int UpdateApplication
+!=
+&&
+gtxn 0 OnCompletion
+int DeleteApplication
+!=
+&&
+int 1
+gtxns OnCompletion
+int UpdateApplication
+!=
+&&
+int 1
+gtxns OnCompletion
+int DeleteApplication
+!=
+&&
+assert
+int 1
+return
+main_l69:
+txn GroupIndex
+gtxns RekeyTo
+global ZeroAddress
+==
+txn GroupIndex
+gtxns Fee
+int 10000
+<
+&&
+txn GroupIndex
+gtxns CloseRemainderTo
+global ZeroAddress
+==
+&&
+txn GroupIndex
+gtxns AssetCloseTo
+global ZeroAddress
+==
+&&
+txn GroupIndex
+gtxns OnCompletion
+int UpdateApplication
+!=
+&&
+txn GroupIndex
+gtxns OnCompletion
+int DeleteApplication
+!=
+&&
+assert
+int 1
+return
+main_l70:
+txn RekeyTo
+global ZeroAddress
+==
+txn Fee
+int 10000
+<
+&&
+txn CloseRemainderTo
+global ZeroAddress
+==
+&&
+txn AssetCloseTo
+global ZeroAddress
+==
+&&
+txn OnCompletion
+int UpdateApplication
+!=
+&&
+txn OnCompletion
+int DeleteApplication
+!=
+&&
+assert
+int 1
+return
+main_l71:
+int 1
+return
+main_l72:
+int 1
+return
+main_l73:
+int 1
+return
+main_l74:
+int 1
+return
\ No newline at end of file
diff --git a/tests/group_transactions/basic/config.yaml b/tests/group_transactions/basic/config.yaml
new file mode 100644
index 0000000..558e2f7
--- /dev/null
+++ b/tests/group_transactions/basic/config.yaml
@@ -0,0 +1,838 @@
+name: Basic
+contracts:
+  # app_1.py
+  - name: App_1
+    file_path: app_1.teal
+    type: ApprovalProgram
+    version: 7
+    subroutines: []
+    functions:
+      - name: app1_case_1
+        dispatch_path: ["B0", "B74"]
+      - name: app1_case_2
+        dispatch_path: ["B0", "B1", "B73"]
+      - name: app1_case_3
+        dispatch_path: ["B0", "B1", "B2", "B72"]
+      - name: app1_case_4
+        dispatch_path: ["B0", "B1", "B2", "B3", "B71"]
+      - name: app1_case_5
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B70"]
+      - name: app1_case_6
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B69"]
+      - name: app1_case_7
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B68"]
+      - name: app1_case_8
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B67"]
+      - name: app1_case_9
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B66"]
+      - name: app1_case_10
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "B65"]
+      - name: app1_case_11
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "B10", "B64"]
+      - name: app1_case_12
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "B10", "B11", "B63"]
+      - name: app1_case_13
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "B10", "B11", "B12", "B62"]
+      - name: app1_case_14
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "B10", "B11", "B12", "B13", "B61"]
+      - name: app1_case_15
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "B10", "B11", "B12", "B13", "B14", "B60"]
+      - name: app1_case_16
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "B10", "B11", "B12", "B13", "B14", "B15", "B59"]
+      - name: app1_case_17
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "B10", "B11", "B12", "B13", "B14", "B15", "B16", "B58"]
+      - name: app1_case_18
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "B10", "B11", "B12", "B13", "B14", "B15", "B16", "B17", "B57"]
+      - name: app1_case_19
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "B10", "B11", "B12", "B13", "B14", "B15", "B16", "B17", "B18", "B56"]
+      - name: app1_case_20
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "B10", "B11", "B12", "B13", "B14", "B15", "B16", "B17", "B18", "B19", "B55"]
+      - name: app1_case_21
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "B10", "B11", "B12", "B13", "B14", "B15", "B16", "B17", "B18", "B19", "B20", "B54"]
+      - name: app1_case_22
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "B10", "B11", "B12", "B13", "B14", "B15", "B16", "B17", "B18", "B19", "B20", "B21", "B53"]
+      - name: app1_case_23
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "B10", "B11", "B12", "B13", "B14", "B15", "B16", "B17", "B18", "B19", "B20", "B21", "B22", "B52"]
+      - name: app1_case_24
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "B10", "B11", "B12", "B13", "B14", "B15", "B16", "B17", "B18", "B19", "B20", "B21", "B22", "B23", "B51"]
+      - name: app1_case_25
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "B10", "B11", "B12", "B13", "B14", "B15", "B16", "B17", "B18", "B19", "B20", "B21", "B22", "B23", "B24", "B50"]
+      - name: app1_case_26
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "B10", "B11", "B12", "B13", "B14", "B15", "B16", "B17", "B18", "B19", "B20", "B21", "B22", "B23", "B24", "B25", "B49"]
+      - name: app1_case_27
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "B10", "B11", "B12", "B13", "B14", "B15", "B16", "B17", "B18", "B19", "B20", "B21", "B22", "B23", "B24", "B25", "B26", "B48"]
+      - name: app1_case_28
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "B10", "B11", "B12", "B13", "B14", "B15", "B16", "B17", "B18", "B19", "B20", "B21", "B22", "B23", "B24", "B25", "B26", "B27", "B47"]
+      - name: app1_case_29
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "B10", "B11", "B12", "B13", "B14", "B15", "B16", "B17", "B18", "B19", "B20", "B21", "B22", "B23", "B24", "B25", "B26", "B27", "B28", "B46"]
+      - name: app1_case_30
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "B10", "B11", "B12", "B13", "B14", "B15", "B16", "B17", "B18", "B19", "B20", "B21", "B22", "B23", "B24", "B25", "B26", "B27", "B28", "B29", "B45"]
+      - name: app1_case_31
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "B10", "B11", "B12", "B13", "B14", "B15", "B16", "B17", "B18", "B19", "B20", "B21", "B22", "B23", "B24", "B25", "B26", "B27", "B28", "B29", "B30", "B44"]
+      - name: app1_case_32
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "B10", "B11", "B12", "B13", "B14", "B15", "B16", "B17", "B18", "B19", "B20", "B21", "B22", "B23", "B24", "B25", "B26", "B27", "B28", "B29", "B30", "B31", "B43"]
+      - name: app1_case_33
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "B10", "B11", "B12", "B13", "B14", "B15", "B16", "B17", "B18", "B19", "B20", "B21", "B22", "B23", "B24", "B25", "B26", "B27", "B28", "B29", "B30", "B31", "B32", "B42"]
+      - name: app1_case_34
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "B10", "B11", "B12", "B13", "B14", "B15", "B16", "B17", "B18", "B19", "B20", "B21", "B22", "B23", "B24", "B25", "B26", "B27", "B28", "B29", "B30", "B31", "B32", "B33", "B41"]
+      - name: app1_case_35
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "B10", "B11", "B12", "B13", "B14", "B15", "B16", "B17", "B18", "B19", "B20", "B21", "B22", "B23", "B24", "B25", "B26", "B27", "B28", "B29", "B30", "B31", "B32", "B33", "B34", "B40"]
+      - name: app1_case_36
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "B10", "B11", "B12", "B13", "B14", "B15", "B16", "B17", "B18", "B19", "B20", "B21", "B22", "B23", "B24", "B25", "B26", "B27", "B28", "B29", "B30", "B31", "B32", "B33", "B34", "B35", "B39"]
+      - name: app1_case_37
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "B10", "B11", "B12", "B13", "B14", "B15", "B16", "B17", "B18", "B19", "B20", "B21", "B22", "B23", "B24", "B25", "B26", "B27", "B28", "B29", "B30", "B31", "B32", "B33", "B34", "B35", "B36", "B38"]
+  - name: LogicSig_1
+    file_path: logicsig_1.teal
+    type: LogicSig
+    version: 7
+    subroutines: []
+    functions:
+      - name: lsig1_case_1
+        dispatch_path: ["B0", "B58"]
+      - name: lsig1_case_2
+        dispatch_path: ["B0", "B1", "B57"]
+      - name: lsig1_case_5
+        dispatch_path: ["B0", "B1", "B2", "B56"]
+      - name: lsig1_case_6
+        dispatch_path: ["B0", "B1", "B2", "B3", "B55"]
+      - name: lsig1_case_7
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B54"]
+      - name: lsig1_case_8
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B53"]
+      - name: lsig1_case_9
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B52"]
+      - name: lsig1_case_10
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B51"]
+      - name: lsig1_case_14
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B50"]
+      - name: lsig1_case_15
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "B49"]
+      - name: lsig1_case_16
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "B10", "B48"]
+      - name: lsig1_case_17
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "B10", "B11", "B47"]
+      - name: lsig1_case_18
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "B10", "B11", "B12", "B46"]
+      - name: lsig1_case_19
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "B10", "B11", "B12", "B13", "B45"]
+      - name: lsig1_case_20
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "B10", "B11", "B12", "B13", "B14", "B44"]
+      - name: lsig1_case_21
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "B10", "B11", "B12", "B13", "B14", "B15", "B43"]
+      - name: lsig1_case_22
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "B10", "B11", "B12", "B13", "B14", "B15", "B16", "B42"]
+      - name: lsig1_case_26
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "B10", "B11", "B12", "B13", "B14", "B15", "B16", "B17", "B41"]
+      - name: lsig1_case_27
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "B10", "B11", "B12", "B13", "B14", "B15", "B16", "B17", "B18", "B40"]
+      - name: lsig1_case_28
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "B10", "B11", "B12", "B13", "B14", "B15", "B16", "B17", "B18", "B19", "B39"]
+      - name: lsig1_case_29
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "B10", "B11", "B12", "B13", "B14", "B15", "B16", "B17", "B18", "B19", "B20", "B38"]
+      - name: lsig1_case_30
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "B10", "B11", "B12", "B13", "B14", "B15", "B16", "B17", "B18", "B19", "B20", "B21", "B37"]
+      - name: lsig1_case_31
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "B10", "B11", "B12", "B13", "B14", "B15", "B16", "B17", "B18", "B19", "B20", "B21", "B22", "B36"]
+      - name: lsig1_case_32
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "B10", "B11", "B12", "B13", "B14", "B15", "B16", "B17", "B18", "B19", "B20", "B21", "B22", "B23", "B35"]
+      - name: lsig1_case_33
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "B10", "B11", "B12", "B13", "B14", "B15", "B16", "B17", "B18", "B19", "B20", "B21", "B22", "B23", "B24", "B34"]
+      - name: lsig1_case_34
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "B10", "B11", "B12", "B13", "B14", "B15", "B16", "B17", "B18", "B19", "B20", "B21", "B22", "B23", "B24", "B25", "B33"]
+      - name: lsig1_case_35
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "B10", "B11", "B12", "B13", "B14", "B15", "B16", "B17", "B18", "B19", "B20", "B21", "B22", "B23", "B24", "B25", "B26", "B32"]
+      - name: lsig1_case_36
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "B10", "B11", "B12", "B13", "B14", "B15", "B16", "B17", "B18", "B19", "B20", "B21", "B22", "B23", "B24", "B25", "B26", "B27", "B31"]
+      - name: lsig1_case_37
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "B10", "B11", "B12", "B13", "B14", "B15", "B16", "B17", "B18", "B19", "B20", "B21", "B22", "B23", "B24", "B25", "B26", "B27", "B28", "B30"]
+  - name: LogicSig_2
+    file_path: logicsig_2.teal
+    type: LogicSig
+    version: 7
+    subroutines: []
+    functions:
+      - name: lsig2_case_7
+        dispatch_path: ["B0", "B58"]
+      - name: lsig2_case_8
+        dispatch_path: ["B0", "B1", "B57"]
+      - name: lsig2_case_9
+        dispatch_path: ["B0", "B1", "B2", "B56"]
+      - name: lsig2_case_10
+        dispatch_path: ["B0", "B1", "B2", "B3", "B55"]
+      - name: lsig2_case_11
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B54"]
+      - name: lsig2_case_12
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B53"]
+      - name: lsig2_case_13
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B52"]
+      - name: lsig2_case_14
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B51"]
+      - name: lsig2_case_15
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B50"]
+      - name: lsig2_case_16
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "B49"]
+      - name: lsig2_case_17
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "B10", "B48"]
+      - name: lsig2_case_18
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "B10", "B11", "B47"]
+      - name: lsig2_case_19
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "B10", "B11", "B12", "B46"]
+      - name: lsig2_case_20
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "B10", "B11", "B12", "B13", "B45"]
+      - name: lsig2_case_21
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "B10", "B11", "B12", "B13", "B14", "B44"]
+      - name: lsig2_case_22
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "B10", "B11", "B12", "B13", "B14", "B15", "B43"]
+      - name: lsig2_case_23
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "B10", "B11", "B12", "B13", "B14", "B15", "B16", "B42"]
+      - name: lsig2_case_24
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "B10", "B11", "B12", "B13", "B14", "B15", "B16", "B17", "B41"]
+      - name: lsig2_case_25
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "B10", "B11", "B12", "B13", "B14", "B15", "B16", "B17", "B18", "B40"]
+      - name: lsig2_case_26
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "B10", "B11", "B12", "B13", "B14", "B15", "B16", "B17", "B18", "B19", "B39"]
+      - name: lsig2_case_27
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "B10", "B11", "B12", "B13", "B14", "B15", "B16", "B17", "B18", "B19", "B20", "B38"]
+      - name: lsig2_case_28
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "B10", "B11", "B12", "B13", "B14", "B15", "B16", "B17", "B18", "B19", "B20", "B21", "B37"]
+      - name: lsig2_case_29
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "B10", "B11", "B12", "B13", "B14", "B15", "B16", "B17", "B18", "B19", "B20", "B21", "B22", "B36"]
+      - name: lsig2_case_30
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "B10", "B11", "B12", "B13", "B14", "B15", "B16", "B17", "B18", "B19", "B20", "B21", "B22", "B23", "B35"]
+      - name: lsig2_case_31
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "B10", "B11", "B12", "B13", "B14", "B15", "B16", "B17", "B18", "B19", "B20", "B21", "B22", "B23", "B24", "B34"]
+      - name: lsig2_case_32
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "B10", "B11", "B12", "B13", "B14", "B15", "B16", "B17", "B18", "B19", "B20", "B21", "B22", "B23", "B24", "B25", "B33"]
+      - name: lsig2_case_33
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "B10", "B11", "B12", "B13", "B14", "B15", "B16", "B17", "B18", "B19", "B20", "B21", "B22", "B23", "B24", "B25", "B26", "B32"]
+      - name: lsig2_case_34
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "B10", "B11", "B12", "B13", "B14", "B15", "B16", "B17", "B18", "B19", "B20", "B21", "B22", "B23", "B24", "B25", "B26", "B27", "B31"]
+      - name: lsig2_case_35
+        dispatch_path: ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "B10", "B11", "B12", "B13", "B14", "B15", "B16", "B17", "B18", "B19", "B20", "B21", "B22", "B23", "B24", "B25", "B26", "B27", "B28", "B30"]
+# group configurations
+groups:
+  - operation: case_1
+    transactions:
+      - txn_id: T1
+        txn_type: appl
+        application:
+          contract: App_1
+          function: app1_case_1
+        logic_sig:
+          contract: LogicSig_1
+          function: lsig1_case_1
+  - operation: case_2
+    transactions:
+      - txn_id: T1
+        txn_type: appl
+        application:
+          contract: App_1
+          function: app1_case_2
+        logic_sig:
+          contract: LogicSig_1
+          function: lsig1_case_2
+  - operation: case_3
+    transactions:
+      - txn_id: T1
+        txn_type: appl
+        application:
+          contract: App_1
+          function: app1_case_3
+        has_logic_sig: true
+  - operation: case_4
+    transactions:
+      - txn_id: T1
+        txn_type: appl
+        application:
+          contract: App_1
+          function: app1_case_4
+        # has_logic_sig is false.
+  - operation: case_5
+    transactions:
+      - txn_id: T1
+        txn_type: appl
+        application:
+          contract: App_1
+          function: app1_case_5
+        logic_sig:
+          contract: LogicSig_1
+          function: lsig1_case_5
+  - operation: case_6
+    transactions:
+      - txn_id: T1
+        txn_type: appl
+        application:
+          contract: App_1
+          function: app1_case_6
+        logic_sig:
+          contract: LogicSig_1
+          function: lsig1_case_6
+  - operation: case_7
+    transactions:
+      - txn_id: T1
+        txn_type: appl
+        application:
+          contract: App_1
+          function: app1_case_7
+        logic_sig:
+          contract: LogicSig_1
+          function: lsig1_case_7
+        absolute_index: 0
+      - txn_id: T2
+        txn_type: pay
+        logic_sig:
+          contract: LogicSig_2
+          function: lsig2_case_7
+        absolute_index: 1
+  - operation: case_8
+    transactions:
+      - txn_id: T1
+        txn_type: appl
+        application:
+          contract: App_1
+          function: app1_case_8
+        logic_sig:
+          contract: LogicSig_1
+          function: lsig1_case_8
+        absolute_index: 0
+      - txn_id: T2
+        txn_type: axfer
+        logic_sig:
+          contract: LogicSig_2
+          function: lsig2_case_8
+        absolute_index: 1
+
+  - operation: case_9
+    transactions:
+      - txn_id: T1
+        txn_type: appl
+        application:
+          contract: App_1
+          function: app1_case_9
+        logic_sig:
+          contract: LogicSig_1
+          function: lsig1_case_9
+        absolute_index: 0
+      - txn_id: T2
+        txn_type: pay
+        logic_sig:
+          contract: LogicSig_2
+          function: lsig2_case_9
+        absolute_index: 1
+
+  - operation: case_10
+    transactions:
+      - txn_id: T1
+        txn_type: appl
+        application:
+          contract: App_1
+          function: app1_case_10
+        logic_sig:
+          contract: LogicSig_1
+          function: lsig1_case_10
+        absolute_index: 0
+      - txn_id: T2
+        txn_type: pay
+        logic_sig:
+          contract: LogicSig_2
+          function: lsig2_case_10
+        absolute_index: 1      
+
+  - operation: case_11
+    transactions:
+      - txn_id: T1
+        txn_type: appl
+        application:
+          contract: App_1
+          function: app1_case_11
+        has_logic_sig: true
+        absolute_index: 0
+      - txn_id: T2
+        txn_type: pay
+        logic_sig:
+          contract: LogicSig_2
+          function: lsig2_case_11
+        absolute_index: 1      
+
+  - operation: case_12
+    transactions:
+      - txn_id: T1
+        txn_type: appl
+        application:
+          contract: App_1
+          function: app1_case_12
+        has_logic_sig: true
+        absolute_index: 0
+      - txn_id: T2
+        txn_type: axfer
+        logic_sig:
+          contract: LogicSig_2
+          function: lsig2_case_12
+        absolute_index: 1      
+
+  - operation: case_13
+    transactions:
+      - txn_id: T1
+        txn_type: appl
+        application:
+          contract: App_1
+          function: app1_case_13
+        absolute_index: 0
+      - txn_id: T2
+        txn_type: pay
+        logic_sig:
+          contract: LogicSig_2
+          function: lsig2_case_13
+        absolute_index: 1      
+
+  - operation: case_14
+    transactions:
+      - txn_id: T1
+        txn_type: appl
+        application:
+          contract: App_1
+          function: app1_case_14
+        logic_sig:
+          contract: LogicSig_1
+          function: lsig1_case_14
+        absolute_index: 0
+      - txn_id: T2
+        txn_type: axfer
+        logic_sig:
+          contract: LogicSig_2
+          function: lsig2_case_14
+        absolute_index: 1
+
+  - operation: case_15
+    transactions:
+      - txn_id: T1
+        txn_type: appl
+        application:
+          contract: App_1
+          function: app1_case_15
+        logic_sig:
+          contract: LogicSig_1
+          function: lsig1_case_15
+        absolute_index: 0
+      - txn_id: T2
+        txn_type: axfer
+        logic_sig:
+          contract: LogicSig_2
+          function: lsig2_case_15
+        absolute_index: 1
+
+  - operation: case_16
+    transactions:
+      - txn_id: T1
+        txn_type: appl
+        application:
+          contract: App_1
+          function: app1_case_16
+        logic_sig:
+          contract: LogicSig_1
+          function: lsig1_case_16
+        absolute_index: 0
+      - txn_id: T2
+        txn_type: axfer
+        logic_sig:
+          contract: LogicSig_2
+          function: lsig2_case_16
+        absolute_index: 1
+
+  - operation: case_17
+    transactions:
+      - txn_id: T1
+        txn_type: appl
+        application:
+          contract: App_1
+          function: app1_case_17
+        logic_sig:
+          contract: LogicSig_1
+          function: lsig1_case_17
+        absolute_index: 0
+      - txn_id: T2
+        txn_type: pay
+        logic_sig:
+          contract: LogicSig_2
+          function: lsig2_case_17
+        absolute_index: 1
+
+  - operation: case_18
+    transactions:
+      - txn_id: T1
+        txn_type: appl
+        application:
+          contract: App_1
+          function: app1_case_18
+        logic_sig:
+          contract: LogicSig_1
+          function: lsig1_case_18
+        absolute_index: 0
+        relative_indexes:
+          - other_txn_id: T2
+            offset: 4
+      - txn_id: T2
+        txn_type: axfer
+        logic_sig:
+          contract: LogicSig_2
+          function: lsig2_case_18
+
+  - operation: case_19
+    transactions:
+      - txn_id: T1
+        txn_type: appl
+        application:
+          contract: App_1
+          function: app1_case_19
+        logic_sig:
+          contract: LogicSig_1
+          function: lsig1_case_19
+        relative_indexes:
+          - other_txn_id: T2
+            offset: 4
+      - txn_id: T2
+        txn_type: pay
+        logic_sig:
+          contract: LogicSig_2
+          function: lsig2_case_19
+        # The user should not be required to give this offset.
+        # TODO: Tealer should compute maximum information about the indexes
+        # from the limited information given by the user.
+        relative_indexes:
+          - other_txn_id: T1
+            offset: -4
+
+  - operation: case_20
+    transactions:
+      - txn_id: T1
+        txn_type: appl
+        application:
+          contract: App_1
+          function: app1_case_20
+        logic_sig:
+          contract: LogicSig_1
+          function: lsig1_case_20
+      - txn_id: T2
+        txn_type: pay
+        logic_sig:
+          contract: LogicSig_2
+          function: lsig2_case_20
+        absolute_index: 1
+        relative_indexes:
+          - other_txn_id: T1
+            offset: 5
+        
+  - operation: case_21
+    transactions:
+      - txn_id: T1
+        txn_type: appl
+        application:
+          contract: App_1
+          function: app1_case_21
+        logic_sig:
+          contract: LogicSig_1
+          function: lsig1_case_21
+        relative_indexes:
+          - other_txn_id: T2
+            offset: 2
+      - txn_id: T2
+        txn_type: pay
+        logic_sig:
+          contract: LogicSig_2
+          function: lsig2_case_21
+
+  - operation: case_22
+    transactions:
+      - txn_id: T1
+        txn_type: appl
+        application:
+          contract: App_1
+          function: app1_case_22
+        logic_sig:
+          contract: LogicSig_1
+          function: lsig1_case_22
+      - txn_id: T2
+        txn_type: axfer
+        logic_sig:
+          contract: LogicSig_2
+          function: lsig2_case_22
+        relative_indexes:
+          - other_txn_id: T1
+            offset: -1
+
+  - operation: case_23
+    transactions:
+      - txn_id: T1
+        txn_type: appl
+        application:
+          contract: App_1
+          function: app1_case_23
+        has_logic_sig: true
+      - txn_id: T2
+        txn_type: pay
+        logic_sig:
+          contract: LogicSig_2
+          function: lsig2_case_23
+        relative_indexes:
+          - other_txn_id: T1
+            offset: -1
+
+  - operation: case_24
+    transactions:
+      - txn_id: T1
+        txn_type: appl
+        application:
+          contract: App_1
+          function: app1_case_24
+        has_logic_sig: true
+      - txn_id: T2
+        txn_type: pay
+        logic_sig:
+          contract: LogicSig_2
+          function: lsig2_case_24
+        relative_indexes:
+          - other_txn_id: T1
+            offset: -1
+
+  - operation: case_25
+    transactions:
+      - txn_id: T1
+        txn_type: appl
+        application:
+          contract: App_1
+          function: app1_case_25
+      - txn_id: T2
+        txn_type: axfer
+        logic_sig:
+          contract: LogicSig_2
+          function: lsig2_case_25
+        relative_indexes:
+          - other_txn_id: T1
+            offset: -1
+
+  - operation: case_26
+    transactions:
+      - txn_id: T1
+        txn_type: appl
+        application:
+          contract: App_1
+          function: app1_case_26
+        logic_sig:
+          contract: LogicSig_1
+          function: lsig1_case_26
+      - txn_id: T2
+        txn_type: pay
+        logic_sig:
+          contract: LogicSig_2
+          function: lsig2_case_26
+        relative_indexes:
+          - other_txn_id: T1
+            offset: -1
+
+  - operation: case_27
+    transactions:
+      - txn_id: T1
+        txn_type: appl
+        application:
+          contract: App_1
+          function: app1_case_27
+        logic_sig:
+          contract: LogicSig_1
+          function: lsig1_case_27
+        relative_indexes:
+          - other_txn_id: T2
+            offset: 1
+      - txn_id: T2
+        txn_type: pay
+        logic_sig:
+          contract: LogicSig_2
+          function: lsig2_case_27
+        relative_indexes:
+          - other_txn_id: T1
+            offset: -1
+
+  - operation: case_28
+    transactions:
+      - txn_id: T1
+        txn_type: appl
+        application:
+          contract: App_1
+          function: app1_case_28
+        logic_sig:
+          contract: LogicSig_1
+          function: lsig1_case_28
+        relative_indexes:
+          - other_txn_id: T2
+            offset: 5
+      - txn_id: T2
+        txn_type: axfer
+        logic_sig:
+          contract: LogicSig_2
+          function: lsig2_case_28
+
+  - operation: case_29
+    transactions:
+      - txn_id: T1
+        txn_type: appl
+        application:
+          contract: App_1
+          function: app1_case_29
+        logic_sig:
+          contract: LogicSig_1
+          function: lsig1_case_29
+        absolute_index: 0
+        relative_indexes:
+          - other_txn_id: T2
+            offset: 3
+      - txn_id: T2
+        txn_type: pay
+        logic_sig:
+          contract: LogicSig_2
+          function: lsig2_case_29
+
+  - operation: case_30
+    transactions:
+      - txn_id: T1
+        txn_type: appl
+        application:
+          contract: App_1
+          function: app1_case_30
+        logic_sig:
+          contract: LogicSig_1
+          function: lsig1_case_30
+        absolute_index: 0
+        relative_indexes:
+          - other_txn_id: T2
+            offset: 4
+      - txn_id: T2
+        txn_type: pay
+        logic_sig:
+          contract: LogicSig_2
+          function: lsig2_case_30
+
+  - operation: case_31
+    transactions:
+      - txn_id: T1
+        txn_type: appl
+        application:
+          contract: App_1
+          function: app1_case_31
+        logic_sig:
+          contract: LogicSig_1
+          function: lsig1_case_31
+        relative_indexes:
+          - other_txn_id: T2
+            offset: 4
+      - txn_id: T2
+        txn_type: axfer
+        logic_sig:
+          contract: LogicSig_2
+          function: lsig2_case_31
+        # The user should not be required to give this offset.
+        # TODO: Tealer should compute maximum information about the indexes
+        # from the limited information given by the user.
+        relative_indexes:
+          - other_txn_id: T1
+            offset: -4
+
+  - operation: case_32
+    transactions:
+      - txn_id: T1
+        txn_type: appl
+        application:
+          contract: App_1
+          function: app1_case_32
+        logic_sig:
+          contract: LogicSig_1
+          function: lsig1_case_32
+        relative_indexes:
+          - other_txn_id: T2
+            offset: 5
+      - txn_id: T2
+        txn_type: axfer
+        logic_sig:
+          contract: LogicSig_2
+          function: lsig2_case_32
+
+  - operation: case_33
+    transactions:
+      - txn_id: T1
+        txn_type: appl
+        application:
+          contract: App_1
+          function: app1_case_33
+        logic_sig:
+          contract: LogicSig_1
+          function: lsig1_case_33
+      - txn_id: T2
+        txn_type: pay
+        logic_sig:
+          contract: LogicSig_2
+          function: lsig2_case_33
+        absolute_index: 1
+        relative_indexes:
+          - other_txn_id: T1
+            offset: 5
+
+  - operation: case_34
+    transactions:
+      - txn_id: T1
+        txn_type: appl
+        application:
+          contract: App_1
+          function: app1_case_34
+        logic_sig:
+          contract: LogicSig_1
+          function: lsig1_case_34
+        relative_indexes:
+          - other_txn_id: T2
+            offset: 2
+      - txn_id: T2
+        txn_type: pay
+        logic_sig:
+          contract: LogicSig_2
+          function: lsig2_case_34
+
+  - operation: case_35
+    transactions:
+      - txn_id: T1
+        txn_type: appl
+        application:
+          contract: App_1
+          function: app1_case_35
+        logic_sig:
+          contract: LogicSig_1
+          function: lsig1_case_35
+        absolute_index: 0
+        relative_indexes:
+          - other_txn_id: T2
+            offset: 3
+      - txn_id: T2
+        txn_type: axfer
+        logic_sig:
+          contract: LogicSig_2
+          function: lsig2_case_35
+
+  - operation: case_36
+    transactions:
+      - txn_id: T1
+        txn_type: appl
+        application:
+          contract: App_1
+          function: app1_case_36
+        logic_sig:
+          contract: LogicSig_1
+          function: lsig1_case_36
+        absolute_index: 4
+
+  - operation: case_37
+    transactions:
+      - txn_id: T1
+        txn_type: appl
+        application:
+          contract: App_1
+          function: app1_case_37
+        logic_sig:
+          contract: LogicSig_1
+          function: lsig1_case_37
+        absolute_index: 1
diff --git a/tests/group_transactions/basic/expected_output.yaml b/tests/group_transactions/basic/expected_output.yaml
new file mode 100644
index 0000000..d78ea9d
--- /dev/null
+++ b/tests/group_transactions/basic/expected_output.yaml
@@ -0,0 +1,97 @@
+name: BasicRekeyTo
+operations:
+  - operation: case_2
+    vulnerable_transactions:
+      - txn_id: T1
+        contracts:
+          - contract: LogicSig_1
+            function: lsig1_case_2
+  - operation: case_3
+    vulnerable_transactions:
+      - txn_id: T1
+  - operation: case_9
+    vulnerable_transactions:
+      - txn_id: T1
+        contracts:
+          - contract: LogicSig_1
+            function: lsig1_case_9
+  - operation: case_12
+    vulnerable_transactions:
+      - txn_id: T1
+  - operation: case_13
+    vulnerable_transactions:
+      - txn_id: T2
+        contracts:
+          - contract: LogicSig_2
+            function: lsig2_case_13
+  - operation: case_14
+    vulnerable_transactions:
+      - txn_id: T1
+        contracts:
+          - contract: LogicSig_1
+            function: lsig1_case_14  
+      - txn_id: T2
+        contracts:
+          - contract: LogicSig_2
+            function: lsig2_case_14
+  - operation: case_17
+    vulnerable_transactions:
+      - txn_id: T1
+        contracts:
+          - contract: LogicSig_1
+            function: lsig1_case_17
+  - operation: case_21
+    vulnerable_transactions:
+      - txn_id: T1
+        contracts:
+          - contract: LogicSig_1
+            function: lsig1_case_21
+  - operation: case_24
+    vulnerable_transactions:
+      - txn_id: T1
+  - operation: case_25
+    vulnerable_transactions:
+      - txn_id: T2
+        contracts:
+          - contract: LogicSig_2
+            function: lsig2_case_25
+  - operation: case_26
+    vulnerable_transactions:
+      - txn_id: T1
+        contracts:
+          - contract: LogicSig_1
+            function: lsig1_case_26
+      - txn_id: T2
+        contracts:
+          - contract: LogicSig_2
+            function: lsig2_case_26
+  - operation: case_28
+    vulnerable_transactions:
+      - txn_id: T1
+        contracts:
+          - contract: LogicSig_1
+            function: lsig1_case_28
+  - operation: case_29
+    vulnerable_transactions:
+      - txn_id: T1
+        contracts:
+          - contract: LogicSig_1
+            function: lsig1_case_29
+  - operation: case_32
+    vulnerable_transactions:
+      - txn_id: T1
+        contracts:
+          - contract: LogicSig_1
+            function: lsig1_case_32
+  - operation: case_34
+    vulnerable_transactions:
+      - txn_id: T1
+        contracts:
+          - contract: LogicSig_1
+            function: lsig1_case_34
+  - operation: case_35
+    vulnerable_transactions:
+      - txn_id: T1
+        contracts:
+          - contract: LogicSig_1
+            function: lsig1_case_35
diff --git a/tests/group_transactions/basic/expected_output_asset_close_to.yaml b/tests/group_transactions/basic/expected_output_asset_close_to.yaml
new file mode 100644
index 0000000..0d7731d
--- /dev/null
+++ b/tests/group_transactions/basic/expected_output_asset_close_to.yaml
@@ -0,0 +1,15 @@
+name: BasicRekeyTo
+operations:
+  - operation: case_14
+    vulnerable_transactions:
+      - txn_id: T2
+        contracts:
+          - contract: LogicSig_2
+            function: lsig2_case_14
+  - operation: case_25
+    vulnerable_transactions:
+      - txn_id: T2
+        contracts:
+          - contract: LogicSig_2
+            function: lsig2_case_25
+  
\ No newline at end of file
diff --git a/tests/group_transactions/basic/expected_output_close_to.yaml b/tests/group_transactions/basic/expected_output_close_to.yaml
new file mode 100644
index 0000000..c32d6aa
--- /dev/null
+++ b/tests/group_transactions/basic/expected_output_close_to.yaml
@@ -0,0 +1,15 @@
+name: BasicRekeyTo
+operations:
+  - operation: case_13
+    vulnerable_transactions:
+      - txn_id: T2
+        contracts:
+          - contract: LogicSig_2
+            function: lsig2_case_13
+  - operation: case_26
+    vulnerable_transactions:
+      - txn_id: T2
+        contracts:
+          - contract: LogicSig_2
+            function: lsig2_case_26
+  
\ No newline at end of file
diff --git a/tests/group_transactions/basic/expected_output_update_delete.yaml b/tests/group_transactions/basic/expected_output_update_delete.yaml
new file mode 100644
index 0000000..76dcd46
--- /dev/null
+++ b/tests/group_transactions/basic/expected_output_update_delete.yaml
@@ -0,0 +1,104 @@
+name: BasicRekeyTo
+operations:
+  - operation: case_2
+    vulnerable_transactions:
+      - txn_id: T1
+        contracts:
+          - contract: App_1
+            function: app1_case_2
+  - operation: case_3
+    vulnerable_transactions:
+      - txn_id: T1
+        contracts:
+          - contract: App_1
+            function: app1_case_3
+  - operation: case_4
+    vulnerable_transactions:
+      - txn_id: T1
+        contracts:
+          - contract: App_1
+            function: app1_case_4
+  - operation: case_9
+    vulnerable_transactions:
+      - txn_id: T1
+        contracts:
+          - contract: App_1
+            function: app1_case_9
+  - operation: case_12
+    vulnerable_transactions:
+      - txn_id: T1
+        contracts:
+          - contract: App_1
+            function: app1_case_12
+  - operation: case_13
+    vulnerable_transactions:
+      - txn_id: T1
+        contracts:
+          - contract: App_1
+            function: app1_case_13
+  - operation: case_14
+    vulnerable_transactions:
+      - txn_id: T1
+        contracts:
+          - contract: App_1
+            function: app1_case_14
+  - operation: case_17
+    vulnerable_transactions:
+      - txn_id: T1
+        contracts:
+          - contract: App_1
+            function: app1_case_17
+  - operation: case_21
+    vulnerable_transactions:
+      - txn_id: T1
+        contracts:
+          - contract: App_1
+            function: app1_case_21
+  - operation: case_24
+    vulnerable_transactions:
+      - txn_id: T1
+        contracts:
+          - contract: App_1
+            function: app1_case_24
+  - operation: case_25
+    vulnerable_transactions:
+      - txn_id: T1
+        contracts:
+          - contract: App_1
+            function: app1_case_25
+  - operation: case_26
+    vulnerable_transactions:
+      - txn_id: T1
+        contracts:
+          - contract: App_1
+            function: app1_case_26
+  - operation: case_28
+    vulnerable_transactions:
+      - txn_id: T1
+        contracts:
+          - contract: App_1
+            function: app1_case_28
+  - operation: case_29
+    vulnerable_transactions:
+      - txn_id: T1
+        contracts:
+          - contract: App_1
+            function: app1_case_29
+  - operation: case_32
+    vulnerable_transactions:
+      - txn_id: T1
+        contracts:
+          - contract: App_1
+            function: app1_case_32
+  - operation: case_34
+    vulnerable_transactions:
+      - txn_id: T1
+        contracts:
+          - contract: App_1
+            function: app1_case_34
+  - operation: case_35
+    vulnerable_transactions:
+      - txn_id: T1
+        contracts:
+          - contract: App_1
+            function: app1_case_35
diff --git a/tests/group_transactions/basic/logicsig_1.py b/tests/group_transactions/basic/logicsig_1.py
new file mode 100644
index 0000000..ed498b7
--- /dev/null
+++ b/tests/group_transactions/basic/logicsig_1.py
@@ -0,0 +1,979 @@
+# pylint: disable=undefined-variable
+# type: ignore[name-defined]
+"""
+case 1:
+    - Single transaction.
+    - app_1 and logicsig_1 is executed
+    - logicsig_1 checks the rekeyto field
+    - app_1 does not do anything.
+
+Expected:
+    - The detector should not report it as vulnerable.
+    Not Vulnerable - RekeyTo
+    Not Vulnerable - Fee
+    Not Vulnerable - AssetCloseTo
+
+
+case 2:
+    - Single transaction.
+    - Application and LogicSig does not check the RekeyTo field
+
+Expected:
+    - The detector should report it as vulnerable
+    Vulnerable - RekeyTo
+    Vulnerable - Fee
+    Not Vulnerable - CloseRemainderTo
+    Not Vulnerable - AssetCloseTo
+
+case 3:
+    - Single transaction.
+    - Only application is called
+    - has_logic_sig is set to True but the logic_sig contract is not given.
+
+Expected:
+    The detector should report the operation as vulnerable. Even if the detector does not the logic sig being executed, the operation should be considered
+    vulnerable because a logic sig will be vulnerable to Rekeying.
+    Vulnerable - RekeyTo
+    Vulnerable - Fee
+    Not Vulnerable - CloseRemainderTo
+    Not Vulnerable - AssetCloseTo
+
+case 4:
+    - Single transaction.
+    - Application is called
+    - has_logic_sig is set to False.
+
+Expected:
+    The detector should report the operation as NOT VULNERABLE. Applications are not vulnerable to rekeying.
+    Not Vulnerable - RekeyTo
+    Not Vulnerable - Fee
+    Not Vulnerable - CloseRemainderTo
+    Not Vulnerable - AssetCloseTo
+
+case 5:
+    - Single transaction
+    - Application checks the rekeyto field
+    - LogicSig checks that application is called
+
+Expected:
+    NOT VULNERABLE - RekeyTo
+    Not Vulnerable - Fee
+    Not Vulnerable - CloseRemainderTo
+    Not Vulnerable - AssetCloseTo
+
+
+
+case 6:
+    - Single transaction
+    - Application access the rekeyto field using `Gtxn[Txn.group_index()].rekey_to()`.
+    - LogicSig checks the application is called.
+
+Expected:
+    Not Vulnerable - RekeyTo
+    Not Vulnerable - Fee
+    Not Vulnerable - CloseRemainderTo
+    Not Vulnerable - AssetCloseTo
+
+case 7:
+    - Two transactions
+    - First transaction (appl)(index: 0):
+        - Application checks the rekeyto field of 0th transaction and 1st transaction using absolute indices.
+        - LogicSig checks the application id
+    - Second transaction (pay)(index: 1):
+        - LogicSig does not check anything.
+
+Expected:
+    Not Vulnerable - RekeyTo
+    Not Vulnerable - Fee
+    Not Vulnerable - CloseRemainderTo
+    Not Vulnerable - AssetCloseTo
+
+case 8:
+    - Two transactions
+    - First transaction (appl)(index: 0):
+        - Application checks the rekeyto field of 0th transaction using absolute index.
+        - LogicSig checks the application id
+    - Second transaction (axfer)(index: 1):
+        - LogicSig checks it's rekeyto field.
+
+Expected:
+    Not Vulnerable - RekeyTo
+    Not Vulnerable - Fee
+    Not Vulnerable - CloseRemainderTo
+    Not Vulnerable - AssetCloseTo
+
+case 9:
+    - Two transactions
+    - First transaction (appl)(index: 0):
+        - Application checks the rekeyto field of 1st transaction using absolute index.
+        - LogicSig checks the application id
+    - Second transaction (pay)(index: 1):
+        - LogicSig does not check anything.
+
+Expected:
+    Vulnerable. - RekeyTo
+    The first transaction is vulnerable. The second transaction should not be reported
+
+    Vulnerable - Fee
+    The first transaction is vulnerable. The second transaction should not be reported
+    Not Vulnerable - CloseRemainderTo
+    Not Vulnerable - AssetCloseTo
+
+case 10:
+    - Two transactions
+    - First transaction (appl)(index: 0):
+        - Application does not check anything.
+        - LogicSig checks the application id
+    - Second transaction (pay)(index: 1):
+        - LogicSig checks the fields of both transactions using absolute indices.
+
+Expected:
+    Not Vulnerable - RekeyTo
+    Not Vulnerable - Fee
+    Not Vulnerable - CloseRemainderTo
+    Not Vulnerable - AssetCloseTo
+
+case 11:
+    - Two transactions
+    - First transaction (appl)(index: 0):
+        - Application does not check anything.
+        - has_logic_sig is True.
+    - Second transaction (pay)(index: 1):
+        - LogicSig checks the fields of both transactions using absolute indices
+
+Expected:
+    Not Vulnerable - RekeyTo
+    Not Vulnerable - Fee
+    Not Vulnerable - CloseRemainderTo
+    Not Vulnerable - AssetCloseTo
+
+case 12:
+    - Two transactions
+    - First transaction (appl)(index: 0):
+        - Application does not check anything.
+        - has_logic_sig is True.
+    - Second transaction (axfer)(index: 1):
+        - LogicSig checks the fields of its own transaction field.
+
+Expected:
+    Vulnerable - RekeyTo
+    Only the first transaction is reported as vulnerable.
+
+    Vulnerable - Fee
+    Only the first transaction is reported as vulnerable.
+    Not Vulnerable - CloseRemainderTo
+    Not Vulnerable - AssetCloseTo
+
+case 13:
+    - Two transactions
+    - First transaction (appl)(index: 0):
+        - Application does not check anything.
+    - Second transaction (pay)(index: 1):
+        - LogicSig does not check anything.
+
+Expected:
+    Vulnerable - RekeyTo
+    Second transaction is reported as vulnerable
+
+    Vulnerable - Fee
+    Second transaction is reported as vulnerable
+
+    Vulnerable - CloseRemainderTo
+    Second transaction is reported as vulnerable
+
+    Not Vulnerable - AssetCloseTo
+
+case 14:
+    - Two transactions
+    - First transaction (appl)(index: 0):
+        - Application does not check anything.
+        - LogicSig does not check anything.
+    - Second transaction (axfer)(index: 1):
+        - LogicSig does not check anything.
+
+Expected:
+    Vulnerable - RekeyTo
+    Both First and Second transactions are reported as vulnerable.
+
+    Vulnerable - Fee
+    Both First and Second transactions are reported as vulnerable.
+
+    Not Vulnerable - CloseRemainderTo
+    Vulnerable - AssetCloseTo
+    Second transaction is reported as vulnerable
+
+case 15:
+    - Two transactions
+    - First transaction (appl)(index: 0):
+        - Application does not check anything.
+        - LogicSig checks the rekeyto field of 0th transaction and 1st transaction using absolute indices
+    - Second transaction (axfer)(index: 1):
+        - LogicSig does not check anything.
+
+Expected:
+    Not Vulnerable - RekeyTo
+    Not Vulnerable - Fee
+    Not Vulnerable - CloseRemainderTo
+    Not Vulnerable - AssetCloseTo
+
+case 16:
+    - Two transactions
+    - First transaction (appl)(index: 0):
+        - Application does not check anything.
+        - LogicSig checks the rekeyto field of 0th transaction using absolute index.
+    - Second transaction (axfer)(index: 1):
+        - LogicSig checks it's rekeyto field.
+
+Expected:
+    Not Vulnerable - RekeyTo
+    Not Vulnerable - Fee
+    Not Vulnerable - CloseRemainderTo
+    Not Vulnerable - AssetCloseTo
+
+case 17:
+    - Two transactions
+    - First transaction (appl)(index: 0):
+        - Application does not check anything.
+        - LogicSig checks the rekeyto field of 1st transaction using absolute index.
+    - Second transaction (pay)(index: 1):
+        - LogicSig does not check anything.
+
+Expected:
+    Vulnerable - RekeyTo
+    The first transaction is vulnerable. The second transaction should not be reported
+
+    Vulnerable - Fee
+    The first transaction is vulnerable. The second transaction should not be reported
+
+    Not Vulnerable - CloseRemainderTo
+    Not Vulnerable - AssetCloseTo
+
+case 18:
+    - Two transactions
+    - First transaction (appl)(index: 0):
+        - Application checks the rekeyto field of 0th transaction using absolute index and 1st transaction using relative index(+4).
+        - LogicSig checks the application id
+    - Second transaction (axfer):
+        - LogicSig does not check anything.
+
+Expected:
+    Not Vulnerable - RekeyTo
+    Not Vulnerable - Fee
+    Not Vulnerable - CloseRemainderTo
+    Not Vulnerable - AssetCloseTo
+
+case 19:
+    - Two transactions
+    - First transaction (appl):
+        - Application checks the rekeyto field of 1st transaction using relative index(+4).
+        - LogicSig checks the application id
+    - Second transaction (pay):
+        - LogicSig checks rekey field of the previous transaction using (-4) relative index.
+
+Expected:
+    Not Vulnerable - RekeyTo
+    Not Vulnerable - Fee
+    Not Vulnerable - CloseRemainderTo
+    Not Vulnerable - AssetCloseTo
+
+case 20:
+    - Two transactions
+    - First transaction (appl):
+        - Application checks the rekeyto field of 1st transaction using absolute index.
+        - LogicSig checks the application id
+    - Second transaction (pay)(index: 1):
+        - LogicSig checks rekey field of the first transaction using (+5) relative index.
+
+Expected:
+    Not Vulnerable - RekeyTo
+    Not Vulnerable - Fee
+    Not Vulnerable - CloseRemainderTo
+    Not Vulnerable - AssetCloseTo
+
+case 21:
+    - Two transactions
+    - First transaction (appl):
+        - Application checks the rekeyto field of 1st transaction using relative index(+2).
+        - LogicSig checks the application id
+    - Second transaction (pay):
+        - LogicSig does not check anything.
+
+Expected:
+    Vulnerable - RekeyTo
+    The first transaction is vulnerable. The second transaction should not be reported
+
+    Vulnerable - Fee
+    The first transaction is vulnerable. The second transaction should not be reported
+
+    Not Vulnerable - CloseRemainderTo
+    Not Vulnerable - AssetCloseTo
+
+case 22:
+    - Two transactions
+    - First transaction (appl):
+        - Application does not check anything.
+        - LogicSig checks the application id
+    - Second transaction (axfer):
+        - LogicSig checks the field of previous transaction using relative index(-1) and its own field directly.
+
+Expected:
+    Not Vulnerable - RekeyTo
+    Not Vulnerable - Fee
+    Not Vulnerable - CloseRemainderTo
+    Not Vulnerable - AssetCloseTo
+
+case 23:
+    - Two transactions
+    - First transaction (appl):
+        - Application does not check anything.
+        - has_logic_sig is True.
+    - Second transaction (pay):
+        - LogicSig checks the field of previous transaction using relative index(-1) and its own field directly.
+
+Expected:
+    Not Vulnerable - RekeyTo
+    Not Vulnerable - Fee
+    Not Vulnerable - CloseRemainderTo
+    Not Vulnerable - AssetCloseTo
+
+case 24:
+    - Two transactions
+    - First transaction (appl):
+        - Application does not check anything.
+        - has_logic_sig is True.
+    - Second transaction (pay):
+        - LogicSig checks the fields of its own transaction field.
+
+Expected:
+    Vulnerable - RekeyTo.
+    Only the first transaction is reported as vulnerable.
+
+    Vulnerable - Fee.
+    Only the first transaction is reported as vulnerable.
+
+    Not Vulnerable - CloseRemainderTo
+    Not Vulnerable - AssetCloseTo
+
+case 25:
+    - Two transactions
+    - First transaction (appl):
+        - Application does not check anything.
+    - Second transaction (axfer):
+        - LogicSig does not check anything.
+
+Expected:
+    Vulnerable - RekeyTo.
+    Second transaction is reported as vulnerable
+
+    Vulnerable - Fee.
+    Second transaction is reported as vulnerable
+
+    Not Vulnerable - CloseRemainderTo
+    Vulnerable - AssetCloseTo
+    Second transaction is reported
+
+
+case 26:
+    - Two transactions
+    - First transaction (appl):
+        - Application does not check anything.
+        - LogicSig does not check anything.
+    - Second transaction (pay):
+        - LogicSig does not check anything.
+
+Expected:
+    Vulnerable - RekeyTo.
+    Both First and Second transactions are reported as vulnerable.
+
+    Vulnerable - Fee.
+    Both First and Second transactions are reported as vulnerable.
+
+    Vulnerable - CloseRemainderTo
+    Second transaction is reported as vulnerable
+
+    Not Vulnerable - AssetCloseTo
+
+case 27:
+    - Two transactions
+    - First transaction (appl):
+        - Application does not check anything.
+        - LogicSig checks the rekeyto field of its own transaction and 1st transaction using relative index(+1)
+    - Second transaction (pay):
+        - LogicSig does not check anything.
+
+Expected:
+    Not Vulnerable - RekeyTo
+    Not Vulnerable - Fee
+    Not Vulnerable - CloseRemainderTo
+    Not Vulnerable - AssetCloseTo
+
+case 28:
+    - Two transactions
+    - First transaction (appl):
+        - Application checks the rekeyto field of 1st transaction using relative index(+5).
+        - LogicSig checks the application id
+    - Second transaction (axfer):
+        - LogicSig checks rekey field of the previous transaction using (-4) relative index.
+
+Expected:
+    Vulnerable - RekeyTo.
+    Second transaction uses the wrong index. First contract will be vulnerable.
+
+    Vulnerable - Fee
+    Second transaction uses the wrong index. First contract will be vulnerable.
+    Not Vulnerable - CloseRemainderTo
+    Not Vulnerable - AssetCloseTo
+
+case 29:
+    - Two transactions
+    - First transaction (appl)(index: 0):
+        - Application does not check anything.
+        - LogicSig checks the rekeyto field of 1st transaction using relative index(+3).
+    - Second transaction (pay):
+        - LogicSig does not check anything.
+
+Expected:
+    Vulnerable - RekeyTo.
+    The first transaction is vulnerable. The second transaction should not be reported
+
+    Vulnerable - Fee.
+    The first transaction is vulnerable. The second transaction should not be reported
+    Not Vulnerable - CloseRemainderTo
+    Not Vulnerable - AssetCloseTo
+
+case 30:
+    - Two transactions
+    - First transaction (appl)(index: 0):
+        - Application checks the rekeyto field of 0th transaction using absolute index
+        - LogicSig checks the application id and field of 1st transaction using relative index(+4).
+    - Second transaction (pay):
+        - LogicSig does not check anything.
+
+Expected:
+    Not Vulnerable - RekeyTo
+    Not Vulnerable - Fee
+    Not Vulnerable - CloseRemainderTo
+    Not Vulnerable - AssetCloseTo
+
+case 31:
+    - Two transactions
+    - First transaction (appl):
+        - Application does not check anything.
+        - LogicSig checks the rekeyto field of 1st transaction using relative index(+4).
+    - Second transaction (axfer):
+        - LogicSig checks rekey field of the previous transaction using (-4) relative index.
+
+Expected:
+    Not Vulnerable - RekeyTo
+    Not Vulnerable - Fee
+    Not Vulnerable - CloseRemainderTo
+    Not Vulnerable - AssetCloseTo
+
+
+case 32:
+    - Two transactions
+    - First transaction (appl):
+        - Application does not check anything.
+        - LogicSig checks the rekeyto field of 1st transaction using relative index(+5)
+    - Second transaction (axfer):
+        - LogicSig checks rekey field of the previous transaction using (-4) relative index.
+
+Expected:
+    Vulnerable - RekeyTo.
+    Second transaction uses the wrong index. First contract will be vulnerable.
+
+    Vulnerable - Fee.
+    Second transaction uses the wrong index. First contract will be vulnerable.
+    Not Vulnerable - CloseRemainderTo
+    Not Vulnerable - AssetCloseTo
+
+case 33:
+    - Two transactions
+    - First transaction (appl):
+        - Application does not check anything.
+        - LogicSig checks the rekeyto field of 1st transaction using absolute index.
+    - Second transaction (pay)(index: 1):
+        - LogicSig checks rekey field of the 0th transaction using (+5) relative index.
+
+Expected:
+    Not Vulnerable - RekeyTo
+    Not Vulnerable - Fee
+    Not Vulnerable - CloseRemainderTo
+    Not Vulnerable - AssetCloseTo
+
+case 34:
+    - Two transactions
+    - First transaction (appl):
+        - Application does not check anything
+        - LogicSig checks the rekeyto field of 1st transaction using relative index(+2).
+    - Second transaction (pay):
+        - LogicSig does not check anything.
+
+Expected:
+    Vulnerable - RekeyTo.
+    The first transaction is vulnerable. The second transaction should not be reported
+
+    Vulnerable - Fee.
+    The first transaction is vulnerable. The second transaction should not be reported
+    Not Vulnerable - CloseRemainderTo
+    Not Vulnerable - AssetCloseTo
+
+case 35:
+    - Two transactions
+    - First transaction (appl)(index: 0):
+        - Application does not check anything.
+        - LogicSig checks the rekeyto field of 1st transaction using relative index(+3).
+    - Second transaction (axfer):
+        - LogicSig does not check anything.
+
+Expected:
+    Vulnerable - RekeyTo.
+    The first transaction is vulnerable. The second transaction should not be reported
+
+    Vulnerable - Fee
+    The first transaction is vulnerable. The second transaction should not be reported
+    Not Vulnerable - CloseRemainderTo
+    Not Vulnerable - AssetCloseTo
+
+case 36:
+    - Single transaction (index: 4).
+    - Application checks the field using absolute index.
+    - LogicSig checks the application id
+    - Absolute index is given in the config
+
+Expected:
+    Not Vulnerable - RekeyTo
+    Not Vulnerable - Fee
+    Not Vulnerable - CloseRemainderTo
+    Not Vulnerable - AssetCloseTo
+
+case 37:
+    - Single transaction (index: 1)
+    - Application does not check anything.
+    - Logic checks the field using absolute index.
+    - Absolute index is given in the config.
+
+Expected:
+    Not Vulnerable - RekeyTo
+    Not Vulnerable - Fee
+    Not Vulnerable - CloseRemainderTo
+    Not Vulnerable - AssetCloseTo
+"""
+from pathlib import Path
+from pyteal import *  # pylint: disable=wildcard-import, unused-wildcard-import
+
+
+def lsig1_case_1() -> Expr:
+    return Seq(
+        [
+            Assert(
+                And(
+                    Txn.rekey_to() == Global.zero_address(),
+                    Txn.fee() < Int(10000),
+                    Txn.close_remainder_to() == Global.zero_address(),
+                    Txn.asset_close_to() == Global.zero_address(),
+                    Txn.on_completion() != OnComplete.UpdateApplication,
+                    Txn.on_completion() != OnComplete.DeleteApplication,
+                )
+            ),
+            Return(Int(1)),
+        ]
+    )
+
+
+def lsig1_case_2() -> Expr:
+    return Return(Int(1))
+
+
+def lsig1_case_5() -> Expr:
+    return Seq(
+        [
+            Assert(
+                And(
+                    Txn.application_id() == Int(1337),
+                    # Txn.on_completion() == OnComplete.NoOp,
+                )
+            ),
+            Return(Int(1)),
+        ]
+    )
+
+
+def lsig1_case_6() -> Expr:
+    return lsig1_case_5()
+
+
+def lsig1_case_7() -> Expr:
+    return lsig1_case_5()
+
+
+def lsig1_case_8() -> Expr:
+    return lsig1_case_5()
+
+
+def lsig1_case_9() -> Expr:
+    return lsig1_case_5()
+
+
+def lsig1_case_10() -> Expr:
+    return lsig1_case_5()
+
+
+def lsig1_case_14() -> Expr:
+    return Return(Int(1))
+
+
+def lsig1_case_15() -> Expr:
+    return Seq(
+        [
+            Assert(
+                And(
+                    # int 0; gtxns RekeyTo
+                    Gtxn[Int(0)].rekey_to() == Global.zero_address(),
+                    Gtxn[1].rekey_to() == Global.zero_address(),
+                    Gtxn[Int(0)].fee() < Int(100000),
+                    Gtxn[1].fee() < Int(1000),
+                    Gtxn[Int(0)].close_remainder_to() == Global.zero_address(),
+                    Gtxn[1].close_remainder_to() == Global.zero_address(),
+                    Gtxn[Int(0)].asset_close_to() == Global.zero_address(),
+                    Gtxn[1].asset_close_to() == Global.zero_address(),
+                    Gtxn[0].on_completion() != OnComplete.UpdateApplication,
+                    Gtxn[0].on_completion() != OnComplete.DeleteApplication,
+                    Gtxn[Int(1)].on_completion() != OnComplete.UpdateApplication,
+                    Gtxn[Int(1)].on_completion() != OnComplete.DeleteApplication,
+                )
+            ),
+            Return(Int(1)),
+        ]
+    )
+
+
+def lsig1_case_16() -> Expr:
+    return Seq(
+        [
+            Assert(Gtxn[0].rekey_to() == Global.zero_address()),
+            Assert(Gtxn[0].fee() < Int(10000)),
+            Assert(Gtxn[0].close_remainder_to() == Global.zero_address()),
+            Assert(Gtxn[0].asset_close_to() == Global.zero_address()),
+            Assert(
+                And(
+                    Gtxn[0].on_completion() != OnComplete.UpdateApplication,
+                    Gtxn[0].on_completion() != OnComplete.DeleteApplication,
+                )
+            ),
+            Return(Int(1)),
+        ]
+    )
+
+
+def lsig1_case_17() -> Expr:
+    return Seq(
+        [
+            Assert(Gtxn[1].rekey_to() == Global.zero_address()),
+            Assert(Gtxn[1].fee() < Int(10000)),
+            Assert(Gtxn[1].close_remainder_to() == Global.zero_address()),
+            Assert(Gtxn[1].asset_close_to() == Global.zero_address()),
+            Assert(
+                And(
+                    Gtxn[1].on_completion() != OnComplete.UpdateApplication,
+                    Gtxn[1].on_completion() != OnComplete.DeleteApplication,
+                )
+            ),
+            Return(Int(1)),
+        ]
+    )
+
+
+def lsig1_case_18() -> Expr:
+    return Seq(
+        [
+            Assert(
+                And(
+                    Txn.application_id() == Int(1337),
+                    # Txn.on_completion() == OnComplete.NoOp,
+                )
+            ),
+            Return(Int(1)),
+        ]
+    )
+
+
+def lsig1_case_19() -> Expr:
+    return lsig1_case_18()
+
+
+def lsig1_case_20() -> Expr:
+    return lsig1_case_18()
+
+
+def lsig1_case_21() -> Expr:
+    return lsig1_case_18()
+
+
+def lsig1_case_22() -> Expr:
+    return lsig1_case_18()
+
+
+def lsig1_case_26() -> Expr:
+    return Return(Int(1))
+
+
+def lsig1_case_27() -> Expr:
+    return Seq(
+        [
+            Assert(
+                And(
+                    Txn.rekey_to() == Global.zero_address(),
+                    Gtxn[Txn.group_index() + Int(1)].rekey_to() == Global.zero_address(),
+                    Txn.fee() < Int(10000),
+                    Gtxn[Txn.group_index() + Int(1)].fee() < Int(10000),
+                    Txn.close_remainder_to() == Global.zero_address(),
+                    Gtxn[Txn.group_index() + Int(1)].close_remainder_to() == Global.zero_address(),
+                    Txn.asset_close_to() == Global.zero_address(),
+                    Gtxn[Txn.group_index() + Int(1)].asset_close_to() == Global.zero_address(),
+                    Txn.on_completion() != OnComplete.UpdateApplication,
+                    Txn.on_completion() != OnComplete.DeleteApplication,
+                    Gtxn[Txn.group_index() + Int(1)].on_completion()
+                    != OnComplete.UpdateApplication,
+                    Gtxn[Txn.group_index() + Int(1)].on_completion()
+                    != OnComplete.DeleteApplication,
+                )
+            ),
+            Return(Int(1)),
+        ]
+    )
+
+
+def lsig1_case_28() -> Expr:
+    return Seq(
+        [
+            Assert(
+                And(
+                    Txn.application_id() == Int(1337),
+                    # Txn.on_completion() == OnComplete.NoOp,
+                )
+            ),
+            Return(Int(1)),
+        ]
+    )
+
+
+def lsig1_case_29() -> Expr:
+    return Seq(
+        [
+            Assert(
+                And(
+                    Gtxn[Txn.group_index() + Int(3)].rekey_to() == Global.zero_address(),
+                    Gtxn[Txn.group_index() + Int(3)].fee() < Int(10000),
+                    Gtxn[Txn.group_index() + Int(3)].close_remainder_to() == Global.zero_address(),
+                    Gtxn[Txn.group_index() + Int(3)].asset_close_to() == Global.zero_address(),
+                    Gtxn[Txn.group_index() + Int(3)].on_completion()
+                    != OnComplete.UpdateApplication,
+                    Gtxn[Txn.group_index() + Int(3)].on_completion()
+                    != OnComplete.UpdateApplication,
+                )
+            ),
+            Return(Int(1)),
+        ]
+    )
+
+
+def lsig1_case_30() -> Expr:
+    return Seq(
+        [
+            Assert(
+                And(
+                    Txn.application_id() == Int(1337),
+                    # Txn.on_completion() == OnComplete.NoOp,
+                    Gtxn[Txn.group_index() + Int(4)].rekey_to() == Global.zero_address(),
+                    Gtxn[Txn.group_index() + Int(4)].fee() < Int(10000),
+                    Gtxn[Txn.group_index() + Int(4)].asset_close_to() == Global.zero_address(),
+                    Gtxn[Txn.group_index() + Int(4)].close_remainder_to() == Global.zero_address(),
+                    Gtxn[Txn.group_index() + Int(4)].on_completion()
+                    != OnComplete.UpdateApplication,
+                    Gtxn[Txn.group_index() + Int(4)].on_completion()
+                    != OnComplete.DeleteApplication,
+                )
+            ),
+            Return(Int(1)),
+        ]
+    )
+
+
+def lsig1_case_31() -> Expr:
+    return lsig1_case_30()
+
+
+def lsig1_case_32() -> Expr:
+    return Seq(
+        [
+            Assert(
+                And(
+                    Txn.application_id() == Int(1337),
+                    # Txn.on_completion() == OnComplete.NoOp,
+                    Gtxn[Txn.group_index() + Int(5)].rekey_to() == Global.zero_address(),
+                    Gtxn[Txn.group_index() + Int(5)].fee() < Int(10000),
+                    Gtxn[Txn.group_index() + Int(5)].close_remainder_to() == Global.zero_address(),
+                    Gtxn[Txn.group_index() + Int(5)].asset_close_to() == Global.zero_address(),
+                    Gtxn[Txn.group_index() + Int(5)].on_completion()
+                    != OnComplete.UpdateApplication,
+                    Gtxn[Txn.group_index() + Int(5)].on_completion()
+                    != OnComplete.DeleteApplication,
+                )
+            ),
+            Return(Int(1)),
+        ]
+    )
+
+
+def lsig1_case_33() -> Expr:
+    return Seq(
+        [
+            Assert(
+                And(
+                    Gtxn[1].rekey_to() == Global.zero_address(),
+                    Gtxn[1].fee() < Int(10000),
+                    Gtxn[1].close_remainder_to() == Global.zero_address(),
+                    Gtxn[1].asset_close_to() == Global.zero_address(),
+                    Txn.application_id() == Int(1337),
+                    # Txn.on_completion() == OnComplete.NoOp,
+                    Gtxn[1].on_completion() != OnComplete.UpdateApplication,
+                    Gtxn[1].on_completion() != OnComplete.DeleteApplication,
+                )
+            ),
+            Return(Int(1)),
+        ]
+    )
+
+
+def lsig1_case_34() -> Expr:
+    return Seq(
+        [
+            Assert(
+                And(
+                    Gtxn[Txn.group_index() + Int(2)].rekey_to() == Global.zero_address(),
+                    Gtxn[Txn.group_index() + Int(2)].fee() < Int(10000),
+                    Gtxn[Txn.group_index() + Int(2)].close_remainder_to() == Global.zero_address(),
+                    Gtxn[Txn.group_index() + Int(2)].asset_close_to() == Global.zero_address(),
+                    Txn.application_id() == Int(1337),
+                    # Txn.on_completion() == OnComplete.NoOp,
+                    Gtxn[Txn.group_index() + Int(2)].on_completion()
+                    != OnComplete.UpdateApplication,
+                    Gtxn[Txn.group_index() + Int(2)].on_completion()
+                    != OnComplete.DeleteApplication,
+                )
+            ),
+            Return(Int(1)),
+        ]
+    )
+
+
+def lsig1_case_35() -> Expr:
+    return Seq(
+        [
+            Assert(
+                And(
+                    Gtxn[Txn.group_index() + Int(3)].rekey_to() == Global.zero_address(),
+                    Gtxn[Txn.group_index() + Int(3)].fee() < Int(10000),
+                    Gtxn[Txn.group_index() + Int(3)].close_remainder_to() == Global.zero_address(),
+                    Gtxn[Txn.group_index() + Int(3)].asset_close_to() == Global.zero_address(),
+                    Txn.application_id() == Int(1337),
+                    # Txn.on_completion() == OnComplete.NoOp,
+                    Gtxn[Txn.group_index() + Int(3)].on_completion()
+                    != OnComplete.UpdateApplication,
+                    Gtxn[Txn.group_index() + Int(3)].on_completion()
+                    != OnComplete.DeleteApplication,
+                )
+            ),
+            Return(Int(1)),
+        ]
+    )
+
+
+def lsig1_case_36() -> Expr:
+    return Seq(
+        [
+            Assert(
+                And(
+                    Txn.application_id() == Int(1337),
+                    # Txn.on_completion() == OnComplete.NoOp,
+                )
+            ),
+            Return(Int(1)),
+        ]
+    )
+
+
+def lsig1_case_37() -> Expr:
+    return Seq(
+        [
+            Assert(
+                And(
+                    Gtxn[1].fee() < Int(10000),
+                    Gtxn[1].rekey_to() == Global.zero_address(),
+                    Gtxn[1].close_remainder_to() == Global.zero_address(),
+                    Gtxn[1].asset_close_to() == Global.zero_address(),
+                    Gtxn[1].on_completion() != OnComplete.UpdateApplication,
+                    Gtxn[1].on_completion() != OnComplete.DeleteApplication,
+                )
+            ),
+            Return(Int(1)),
+        ]
+    )
+
+
+def logic_sig() -> Expr:
+    test_case = Btoi(Arg(0))
+    return Seq(
+        [
+            Cond(
+                [test_case == Int(1), lsig1_case_1()],
+                [test_case == Int(2), lsig1_case_2()],
+                # [test_case == Int(3), lsig1_case_3()],
+                # [test_case == Int(4), lsig1_case_4()],
+                [test_case == Int(5), lsig1_case_5()],
+                [test_case == Int(6), lsig1_case_6()],
+                [test_case == Int(7), lsig1_case_7()],
+                [test_case == Int(8), lsig1_case_8()],
+                [test_case == Int(9), lsig1_case_9()],
+                [test_case == Int(10), lsig1_case_10()],
+                # [test_case == Int(11), lsig1_case_11()],
+                # [test_case == Int(12), lsig1_case_12()],
+                # [test_case == Int(13), lsig1_case_13()],
+                [test_case == Int(14), lsig1_case_14()],
+                [test_case == Int(15), lsig1_case_15()],
+                [test_case == Int(16), lsig1_case_16()],
+                [test_case == Int(17), lsig1_case_17()],
+                [test_case == Int(18), lsig1_case_18()],
+                [test_case == Int(19), lsig1_case_19()],
+                [test_case == Int(20), lsig1_case_20()],
+                [test_case == Int(21), lsig1_case_21()],
+                [test_case == Int(22), lsig1_case_22()],
+                # [test_case == Int(23), lsig1_case_23()],
+                # [test_case == Int(24), lsig1_case_24()],
+                # [test_case == Int(25), lsig1_case_25()],
+                [test_case == Int(26), lsig1_case_26()],
+                [test_case == Int(27), lsig1_case_27()],
+                [test_case == Int(28), lsig1_case_28()],
+                [test_case == Int(29), lsig1_case_29()],
+                [test_case == Int(30), lsig1_case_30()],
+                [test_case == Int(31), lsig1_case_31()],
+                [test_case == Int(32), lsig1_case_32()],
+                [test_case == Int(33), lsig1_case_33()],
+                [test_case == Int(34), lsig1_case_34()],
+                [test_case == Int(35), lsig1_case_35()],
+                [test_case == Int(36), lsig1_case_36()],
+                [test_case == Int(37), lsig1_case_37()],
+            )
+        ]
+    )
+
+
+def compile_program(output_file: Path) -> None:
+    teal = compileTeal(logic_sig(), mode=Mode.Signature, version=7)
+    with open(output_file, "w", encoding="utf-8") as f:
+        f.write(teal)
+
+
+if __name__ == "__main__":
+    compile_program(Path("logicsig_1.teal"))
diff --git a/tests/group_transactions/basic/logicsig_1.teal b/tests/group_transactions/basic/logicsig_1.teal
new file mode 100644
index 0000000..bbb2085
--- /dev/null
+++ b/tests/group_transactions/basic/logicsig_1.teal
@@ -0,0 +1,802 @@
+#pragma version 7
+arg 0
+btoi
+int 1
+==
+bnz main_l58
+arg 0
+btoi
+int 2
+==
+bnz main_l57
+arg 0
+btoi
+int 5
+==
+bnz main_l56
+arg 0
+btoi
+int 6
+==
+bnz main_l55
+arg 0
+btoi
+int 7
+==
+bnz main_l54
+arg 0
+btoi
+int 8
+==
+bnz main_l53
+arg 0
+btoi
+int 9
+==
+bnz main_l52
+arg 0
+btoi
+int 10
+==
+bnz main_l51
+arg 0
+btoi
+int 14
+==
+bnz main_l50
+arg 0
+btoi
+int 15
+==
+bnz main_l49
+arg 0
+btoi
+int 16
+==
+bnz main_l48
+arg 0
+btoi
+int 17
+==
+bnz main_l47
+arg 0
+btoi
+int 18
+==
+bnz main_l46
+arg 0
+btoi
+int 19
+==
+bnz main_l45
+arg 0
+btoi
+int 20
+==
+bnz main_l44
+arg 0
+btoi
+int 21
+==
+bnz main_l43
+arg 0
+btoi
+int 22
+==
+bnz main_l42
+arg 0
+btoi
+int 26
+==
+bnz main_l41
+arg 0
+btoi
+int 27
+==
+bnz main_l40
+arg 0
+btoi
+int 28
+==
+bnz main_l39
+arg 0
+btoi
+int 29
+==
+bnz main_l38
+arg 0
+btoi
+int 30
+==
+bnz main_l37
+arg 0
+btoi
+int 31
+==
+bnz main_l36
+arg 0
+btoi
+int 32
+==
+bnz main_l35
+arg 0
+btoi
+int 33
+==
+bnz main_l34
+arg 0
+btoi
+int 34
+==
+bnz main_l33
+arg 0
+btoi
+int 35
+==
+bnz main_l32
+arg 0
+btoi
+int 36
+==
+bnz main_l31
+arg 0
+btoi
+int 37
+==
+bnz main_l30
+err
+main_l30:
+gtxn 1 Fee
+int 10000
+<
+gtxn 1 RekeyTo
+global ZeroAddress
+==
+&&
+gtxn 1 CloseRemainderTo
+global ZeroAddress
+==
+&&
+gtxn 1 AssetCloseTo
+global ZeroAddress
+==
+&&
+gtxn 1 OnCompletion
+int UpdateApplication
+!=
+&&
+gtxn 1 OnCompletion
+int DeleteApplication
+!=
+&&
+assert
+int 1
+return
+main_l31:
+txn ApplicationID
+int 1337
+==
+assert
+int 1
+return
+main_l32:
+txn GroupIndex
+int 3
++
+gtxns RekeyTo
+global ZeroAddress
+==
+txn GroupIndex
+int 3
++
+gtxns Fee
+int 10000
+<
+&&
+txn GroupIndex
+int 3
++
+gtxns CloseRemainderTo
+global ZeroAddress
+==
+&&
+txn GroupIndex
+int 3
++
+gtxns AssetCloseTo
+global ZeroAddress
+==
+&&
+txn ApplicationID
+int 1337
+==
+&&
+txn GroupIndex
+int 3
++
+gtxns OnCompletion
+int UpdateApplication
+!=
+&&
+txn GroupIndex
+int 3
++
+gtxns OnCompletion
+int DeleteApplication
+!=
+&&
+assert
+int 1
+return
+main_l33:
+txn GroupIndex
+int 2
++
+gtxns RekeyTo
+global ZeroAddress
+==
+txn GroupIndex
+int 2
++
+gtxns Fee
+int 10000
+<
+&&
+txn GroupIndex
+int 2
++
+gtxns CloseRemainderTo
+global ZeroAddress
+==
+&&
+txn GroupIndex
+int 2
++
+gtxns AssetCloseTo
+global ZeroAddress
+==
+&&
+txn ApplicationID
+int 1337
+==
+&&
+txn GroupIndex
+int 2
++
+gtxns OnCompletion
+int UpdateApplication
+!=
+&&
+txn GroupIndex
+int 2
++
+gtxns OnCompletion
+int DeleteApplication
+!=
+&&
+assert
+int 1
+return
+main_l34:
+gtxn 1 RekeyTo
+global ZeroAddress
+==
+gtxn 1 Fee
+int 10000
+<
+&&
+gtxn 1 CloseRemainderTo
+global ZeroAddress
+==
+&&
+gtxn 1 AssetCloseTo
+global ZeroAddress
+==
+&&
+txn ApplicationID
+int 1337
+==
+&&
+gtxn 1 OnCompletion
+int UpdateApplication
+!=
+&&
+gtxn 1 OnCompletion
+int DeleteApplication
+!=
+&&
+assert
+int 1
+return
+main_l35:
+txn ApplicationID
+int 1337
+==
+txn GroupIndex
+int 5
++
+gtxns RekeyTo
+global ZeroAddress
+==
+&&
+txn GroupIndex
+int 5
++
+gtxns Fee
+int 10000
+<
+&&
+txn GroupIndex
+int 5
++
+gtxns CloseRemainderTo
+global ZeroAddress
+==
+&&
+txn GroupIndex
+int 5
++
+gtxns AssetCloseTo
+global ZeroAddress
+==
+&&
+txn GroupIndex
+int 5
++
+gtxns OnCompletion
+int UpdateApplication
+!=
+&&
+txn GroupIndex
+int 5
++
+gtxns OnCompletion
+int DeleteApplication
+!=
+&&
+assert
+int 1
+return
+main_l36:
+txn ApplicationID
+int 1337
+==
+txn GroupIndex
+int 4
++
+gtxns RekeyTo
+global ZeroAddress
+==
+&&
+txn GroupIndex
+int 4
++
+gtxns Fee
+int 10000
+<
+&&
+txn GroupIndex
+int 4
++
+gtxns AssetCloseTo
+global ZeroAddress
+==
+&&
+txn GroupIndex
+int 4
++
+gtxns CloseRemainderTo
+global ZeroAddress
+==
+&&
+txn GroupIndex
+int 4
++
+gtxns OnCompletion
+int UpdateApplication
+!=
+&&
+txn GroupIndex
+int 4
++
+gtxns OnCompletion
+int DeleteApplication
+!=
+&&
+assert
+int 1
+return
+main_l37:
+txn ApplicationID
+int 1337
+==
+txn GroupIndex
+int 4
++
+gtxns RekeyTo
+global ZeroAddress
+==
+&&
+txn GroupIndex
+int 4
++
+gtxns Fee
+int 10000
+<
+&&
+txn GroupIndex
+int 4
++
+gtxns AssetCloseTo
+global ZeroAddress
+==
+&&
+txn GroupIndex
+int 4
++
+gtxns CloseRemainderTo
+global ZeroAddress
+==
+&&
+txn GroupIndex
+int 4
++
+gtxns OnCompletion
+int UpdateApplication
+!=
+&&
+txn GroupIndex
+int 4
++
+gtxns OnCompletion
+int DeleteApplication
+!=
+&&
+assert
+int 1
+return
+main_l38:
+txn GroupIndex
+int 3
++
+gtxns RekeyTo
+global ZeroAddress
+==
+txn GroupIndex
+int 3
++
+gtxns Fee
+int 10000
+<
+&&
+txn GroupIndex
+int 3
++
+gtxns CloseRemainderTo
+global ZeroAddress
+==
+&&
+txn GroupIndex
+int 3
++
+gtxns AssetCloseTo
+global ZeroAddress
+==
+&&
+txn GroupIndex
+int 3
++
+gtxns OnCompletion
+int UpdateApplication
+!=
+&&
+txn GroupIndex
+int 3
++
+gtxns OnCompletion
+int UpdateApplication
+!=
+&&
+assert
+int 1
+return
+main_l39:
+txn ApplicationID
+int 1337
+==
+assert
+int 1
+return
+main_l40:
+txn RekeyTo
+global ZeroAddress
+==
+txn GroupIndex
+int 1
++
+gtxns RekeyTo
+global ZeroAddress
+==
+&&
+txn Fee
+int 10000
+<
+&&
+txn GroupIndex
+int 1
++
+gtxns Fee
+int 10000
+<
+&&
+txn CloseRemainderTo
+global ZeroAddress
+==
+&&
+txn GroupIndex
+int 1
++
+gtxns CloseRemainderTo
+global ZeroAddress
+==
+&&
+txn AssetCloseTo
+global ZeroAddress
+==
+&&
+txn GroupIndex
+int 1
++
+gtxns AssetCloseTo
+global ZeroAddress
+==
+&&
+txn OnCompletion
+int UpdateApplication
+!=
+&&
+txn OnCompletion
+int DeleteApplication
+!=
+&&
+txn GroupIndex
+int 1
++
+gtxns OnCompletion
+int UpdateApplication
+!=
+&&
+txn GroupIndex
+int 1
++
+gtxns OnCompletion
+int DeleteApplication
+!=
+&&
+assert
+int 1
+return
+main_l41:
+int 1
+return
+main_l42:
+txn ApplicationID
+int 1337
+==
+assert
+int 1
+return
+main_l43:
+txn ApplicationID
+int 1337
+==
+assert
+int 1
+return
+main_l44:
+txn ApplicationID
+int 1337
+==
+assert
+int 1
+return
+main_l45:
+txn ApplicationID
+int 1337
+==
+assert
+int 1
+return
+main_l46:
+txn ApplicationID
+int 1337
+==
+assert
+int 1
+return
+main_l47:
+gtxn 1 RekeyTo
+global ZeroAddress
+==
+assert
+gtxn 1 Fee
+int 10000
+<
+assert
+gtxn 1 CloseRemainderTo
+global ZeroAddress
+==
+assert
+gtxn 1 AssetCloseTo
+global ZeroAddress
+==
+assert
+gtxn 1 OnCompletion
+int UpdateApplication
+!=
+gtxn 1 OnCompletion
+int DeleteApplication
+!=
+&&
+assert
+int 1
+return
+main_l48:
+gtxn 0 RekeyTo
+global ZeroAddress
+==
+assert
+gtxn 0 Fee
+int 10000
+<
+assert
+gtxn 0 CloseRemainderTo
+global ZeroAddress
+==
+assert
+gtxn 0 AssetCloseTo
+global ZeroAddress
+==
+assert
+gtxn 0 OnCompletion
+int UpdateApplication
+!=
+gtxn 0 OnCompletion
+int DeleteApplication
+!=
+&&
+assert
+int 1
+return
+main_l49:
+int 0
+gtxns RekeyTo
+global ZeroAddress
+==
+gtxn 1 RekeyTo
+global ZeroAddress
+==
+&&
+int 0
+gtxns Fee
+int 100000
+<
+&&
+gtxn 1 Fee
+int 1000
+<
+&&
+int 0
+gtxns CloseRemainderTo
+global ZeroAddress
+==
+&&
+gtxn 1 CloseRemainderTo
+global ZeroAddress
+==
+&&
+int 0
+gtxns AssetCloseTo
+global ZeroAddress
+==
+&&
+gtxn 1 AssetCloseTo
+global ZeroAddress
+==
+&&
+gtxn 0 OnCompletion
+int UpdateApplication
+!=
+&&
+gtxn 0 OnCompletion
+int DeleteApplication
+!=
+&&
+int 1
+gtxns OnCompletion
+int UpdateApplication
+!=
+&&
+int 1
+gtxns OnCompletion
+int DeleteApplication
+!=
+&&
+assert
+int 1
+return
+main_l50:
+int 1
+return
+main_l51:
+txn ApplicationID
+int 1337
+==
+assert
+int 1
+return
+main_l52:
+txn ApplicationID
+int 1337
+==
+assert
+int 1
+return
+main_l53:
+txn ApplicationID
+int 1337
+==
+assert
+int 1
+return
+main_l54:
+txn ApplicationID
+int 1337
+==
+assert
+int 1
+return
+main_l55:
+txn ApplicationID
+int 1337
+==
+assert
+int 1
+return
+main_l56:
+txn ApplicationID
+int 1337
+==
+assert
+int 1
+return
+main_l57:
+int 1
+return
+main_l58:
+txn RekeyTo
+global ZeroAddress
+==
+txn Fee
+int 10000
+<
+&&
+txn CloseRemainderTo
+global ZeroAddress
+==
+&&
+txn AssetCloseTo
+global ZeroAddress
+==
+&&
+txn OnCompletion
+int UpdateApplication
+!=
+&&
+txn OnCompletion
+int DeleteApplication
+!=
+&&
+assert
+int 1
+return
\ No newline at end of file
diff --git a/tests/group_transactions/basic/logicsig_2.py b/tests/group_transactions/basic/logicsig_2.py
new file mode 100644
index 0000000..1c5f50d
--- /dev/null
+++ b/tests/group_transactions/basic/logicsig_2.py
@@ -0,0 +1,373 @@
+# pylint: disable=undefined-variable
+# type: ignore[name-defined]
+from pathlib import Path
+from pyteal import *  # pylint: disable=wildcard-import, unused-wildcard-import
+
+
+def lsig2_case_7() -> Expr:
+    return Return(Int(1))
+
+
+def lsig2_case_8() -> Expr:
+    return Seq(
+        [
+            Assert(
+                And(
+                    Txn.rekey_to() == Global.zero_address(),
+                    Txn.fee() < Int(10000),
+                    Txn.close_remainder_to() == Global.zero_address(),
+                    Txn.asset_close_to() == Global.zero_address(),
+                    Txn.on_completion() != OnComplete.UpdateApplication,
+                    Txn.on_completion() != OnComplete.DeleteApplication,
+                )
+            ),
+            Return(Int(1)),
+        ]
+    )
+
+
+def lsig2_case_9() -> Expr:
+    return Return(Int(1))
+
+
+def lsig2_case_10() -> Expr:
+    return Seq(
+        [
+            Assert(
+                And(
+                    Gtxn[0].rekey_to() == Global.zero_address(),
+                    Gtxn[1].rekey_to() == Global.zero_address(),
+                    Gtxn[Int(0)].fee() < Int(100000),
+                    Gtxn[1].fee() < Int(1000),
+                    Gtxn[Int(0)].close_remainder_to() == Global.zero_address(),
+                    Gtxn[1].close_remainder_to() == Global.zero_address(),
+                    Gtxn[Int(0)].asset_close_to() == Global.zero_address(),
+                    Gtxn[1].asset_close_to() == Global.zero_address(),
+                    Gtxn[0].on_completion() != OnComplete.UpdateApplication,
+                    Gtxn[0].on_completion() != OnComplete.DeleteApplication,
+                    Gtxn[Int(1)].on_completion() != OnComplete.UpdateApplication,
+                    Gtxn[Int(1)].on_completion() != OnComplete.DeleteApplication,
+                )
+            ),
+            Return(Int(1)),
+        ]
+    )
+
+
+def lsig2_case_11() -> Expr:
+    return lsig2_case_10()
+
+
+def lsig2_case_12() -> Expr:
+    return Seq(
+        [
+            Assert(
+                And(
+                    Gtxn[1].rekey_to() == Global.zero_address(),
+                    Gtxn[1].fee() < Int(10000),
+                    Gtxn[1].close_remainder_to() == Global.zero_address(),
+                    Gtxn[1].asset_close_to() == Global.zero_address(),
+                )
+            ),
+            Assert(
+                And(
+                    Gtxn[1].on_completion() != OnComplete.UpdateApplication,
+                    Gtxn[1].on_completion() != OnComplete.DeleteApplication,
+                )
+            ),
+            Return(Int(1)),
+        ]
+    )
+
+
+def lsig2_case_13() -> Expr:
+    return Return(Int(1))
+
+
+def lsig2_case_14() -> Expr:
+    return Return(Int(1))
+
+
+def lsig2_case_15() -> Expr:
+    return Return(Int(1))
+
+
+def lsig2_case_16() -> Expr:
+    return Seq(
+        [
+            Assert(
+                And(
+                    Txn.rekey_to() == Global.zero_address(),
+                    Txn.fee() < Int(10000),
+                    Txn.close_remainder_to() == Global.zero_address(),
+                    Txn.asset_close_to() == Global.zero_address(),
+                    Txn.on_completion() != OnComplete.UpdateApplication,
+                    Txn.on_completion() != OnComplete.DeleteApplication,
+                )
+            ),
+            Return(Int(1)),
+        ]
+    )
+
+
+def lsig2_case_17() -> Expr:
+    return Return(Int(1))
+
+
+def lsig2_case_18() -> Expr:
+    return Return(Int(1))
+
+
+def lsig2_case_19() -> Expr:
+    return Seq(
+        [
+            Assert(Gtxn[Txn.group_index() - Int(4)].rekey_to() == Global.zero_address()),
+            Assert(Gtxn[Txn.group_index() - Int(4)].fee() < Int(10000)),
+            Assert(Gtxn[Txn.group_index() - Int(4)].close_remainder_to() == Global.zero_address()),
+            Assert(Gtxn[Txn.group_index() - Int(4)].asset_close_to() == Global.zero_address()),
+            Assert(
+                And(
+                    Gtxn[Txn.group_index() - Int(4)].on_completion()
+                    != OnComplete.UpdateApplication,
+                    Gtxn[Txn.group_index() - Int(4)].on_completion()
+                    != OnComplete.DeleteApplication,
+                )
+            ),
+            Return(Int(1)),
+        ]
+    )
+
+
+def lsig2_case_20() -> Expr:
+    return Seq(
+        [
+            Assert(Gtxn[Txn.group_index() + Int(5)].rekey_to() == Global.zero_address()),
+            Assert(Gtxn[Txn.group_index() + Int(5)].fee() < Int(10000)),
+            Assert(Gtxn[Txn.group_index() + Int(5)].close_remainder_to() == Global.zero_address()),
+            Assert(Gtxn[Txn.group_index() + Int(5)].asset_close_to() == Global.zero_address()),
+            Assert(
+                And(
+                    Gtxn[Txn.group_index() + Int(5)].on_completion()
+                    != OnComplete.UpdateApplication,
+                    Gtxn[Txn.group_index() + Int(5)].on_completion()
+                    != OnComplete.DeleteApplication,
+                )
+            ),
+            Return(Int(1)),
+        ]
+    )
+
+
+def lsig2_case_21() -> Expr:
+    return Return(Int(1))
+
+
+def lsig2_case_22() -> Expr:
+    return Seq(
+        [
+            Assert(
+                And(
+                    Gtxn[Txn.group_index() - Int(1)].rekey_to() == Global.zero_address(),
+                    Gtxn[Txn.group_index() - Int(1)].fee() < Int(10000),
+                    Gtxn[Txn.group_index() - Int(1)].close_remainder_to() == Global.zero_address(),
+                    Gtxn[Txn.group_index() - Int(1)].asset_close_to() == Global.zero_address(),
+                    Gtxn[Txn.group_index() - Int(1)].on_completion()
+                    != OnComplete.UpdateApplication,
+                    Gtxn[Txn.group_index() - Int(1)].on_completion()
+                    != OnComplete.DeleteApplication,
+                    Txn.rekey_to() == Global.zero_address(),
+                    Txn.fee() < Int(10000),
+                    Txn.close_remainder_to() == Global.zero_address(),
+                    Txn.asset_close_to() == Global.zero_address(),
+                    Txn.on_completion() != OnComplete.UpdateApplication,
+                    Txn.on_completion() != OnComplete.DeleteApplication,
+                )
+            ),
+            Return(Int(1)),
+        ]
+    )
+
+
+def lsig2_case_23() -> Expr:
+    return lsig2_case_22()
+
+
+def lsig2_case_24() -> Expr:
+    return Seq(
+        [
+            Assert(
+                And(
+                    Txn.rekey_to() == Global.zero_address(),
+                    Txn.fee() < Int(10000),
+                    Txn.close_remainder_to() == Global.zero_address(),
+                    Txn.asset_close_to() == Global.zero_address(),
+                    Txn.on_completion() != OnComplete.UpdateApplication,
+                    Txn.on_completion() != OnComplete.DeleteApplication,
+                )
+            ),
+            Return(Int(1)),
+        ]
+    )
+
+
+def lsig2_case_25() -> Expr:
+    return Return(Int(1))
+
+
+def lsig2_case_26() -> Expr:
+    return Return(Int(1))
+
+
+def lsig2_case_27() -> Expr:
+    return Return(Int(1))
+
+
+def lsig2_case_28() -> Expr:
+    return Seq(
+        [
+            Assert(Gtxn[Txn.group_index() - Int(4)].rekey_to() == Global.zero_address()),
+            Assert(Gtxn[Txn.group_index() - Int(4)].fee() < Int(10000)),
+            Assert(Gtxn[Txn.group_index() - Int(4)].close_remainder_to() == Global.zero_address()),
+            Assert(Gtxn[Txn.group_index() - Int(4)].asset_close_to() == Global.zero_address()),
+            Assert(
+                And(
+                    Gtxn[Txn.group_index() - Int(4)].on_completion()
+                    != OnComplete.UpdateApplication,
+                    Gtxn[Txn.group_index() - Int(4)].on_completion()
+                    != OnComplete.DeleteApplication,
+                )
+            ),
+            Return(Int(1)),
+        ]
+    )
+
+
+def lsig2_case_29() -> Expr:
+    return Return(Int(1))
+
+
+def lsig2_case_30() -> Expr:
+    return Return(Int(1))
+
+
+def lsig2_case_31() -> Expr:
+    return Seq(
+        [
+            Assert(Gtxn[Txn.group_index() - Int(4)].rekey_to() == Global.zero_address()),
+            Assert(Gtxn[Txn.group_index() - Int(4)].fee() < Int(10000)),
+            Assert(Gtxn[Txn.group_index() - Int(4)].close_remainder_to() == Global.zero_address()),
+            Assert(Gtxn[Txn.group_index() - Int(4)].asset_close_to() == Global.zero_address()),
+            Assert(
+                And(
+                    Gtxn[Txn.group_index() - Int(4)].on_completion()
+                    != OnComplete.UpdateApplication,
+                    Gtxn[Txn.group_index() - Int(4)].on_completion()
+                    != OnComplete.DeleteApplication,
+                )
+            ),
+            Return(Int(1)),
+        ]
+    )
+
+
+def lsig2_case_32() -> Expr:
+    return Seq(
+        [
+            Assert(Gtxn[Txn.group_index() - Int(4)].rekey_to() == Global.zero_address()),
+            Assert(Gtxn[Txn.group_index() - Int(4)].fee() < Int(10000)),
+            Assert(Gtxn[Txn.group_index() - Int(4)].close_remainder_to() == Global.zero_address()),
+            Assert(Gtxn[Txn.group_index() - Int(4)].asset_close_to() == Global.zero_address()),
+            Assert(
+                And(
+                    Gtxn[Txn.group_index() - Int(4)].on_completion()
+                    != OnComplete.UpdateApplication,
+                    Gtxn[Txn.group_index() - Int(4)].on_completion()
+                    != OnComplete.DeleteApplication,
+                )
+            ),
+            Return(Int(1)),
+        ]
+    )
+
+
+def lsig2_case_33() -> Expr:
+    return Seq(
+        [
+            Assert(Gtxn[Txn.group_index() + Int(5)].rekey_to() == Global.zero_address()),
+            Assert(Gtxn[Txn.group_index() + Int(5)].fee() < Int(10000)),
+            Assert(Gtxn[Txn.group_index() + Int(5)].close_remainder_to() == Global.zero_address()),
+            Assert(Gtxn[Txn.group_index() + Int(5)].asset_close_to() == Global.zero_address()),
+            Assert(
+                And(
+                    Gtxn[Txn.group_index() + Int(5)].on_completion()
+                    != OnComplete.UpdateApplication,
+                    Gtxn[Txn.group_index() + Int(5)].on_completion()
+                    != OnComplete.DeleteApplication,
+                )
+            ),
+            Return(Int(1)),
+        ]
+    )
+
+
+def lsig2_case_34() -> Expr:
+    return Return(Int(1))
+
+
+def lsig2_case_35() -> Expr:
+    return Return(Int(1))
+
+
+def logic_sig() -> Expr:
+    test_case = Btoi(Arg(0))
+    return Seq(
+        [
+            Cond(
+                # [test_case == Int(1), lsig2_case_1()],
+                # [test_case == Int(2), lsig2_case_2()],
+                # [test_case == Int(3), lsig2_case_3()],
+                # [test_case == Int(4), lsig2_case_4()],
+                # [test_case == Int(5), lsig2_case_5()],
+                # [test_case == Int(6), lsig2_case_6()],
+                [test_case == Int(7), lsig2_case_7()],
+                [test_case == Int(8), lsig2_case_8()],
+                [test_case == Int(9), lsig2_case_9()],
+                [test_case == Int(10), lsig2_case_10()],
+                [test_case == Int(11), lsig2_case_11()],
+                [test_case == Int(12), lsig2_case_12()],
+                [test_case == Int(13), lsig2_case_13()],
+                [test_case == Int(14), lsig2_case_14()],
+                [test_case == Int(15), lsig2_case_15()],
+                [test_case == Int(16), lsig2_case_16()],
+                [test_case == Int(17), lsig2_case_17()],
+                [test_case == Int(18), lsig2_case_18()],
+                [test_case == Int(19), lsig2_case_19()],
+                [test_case == Int(20), lsig2_case_20()],
+                [test_case == Int(21), lsig2_case_21()],
+                [test_case == Int(22), lsig2_case_22()],
+                [test_case == Int(23), lsig2_case_23()],
+                [test_case == Int(24), lsig2_case_24()],
+                [test_case == Int(25), lsig2_case_25()],
+                [test_case == Int(26), lsig2_case_26()],
+                [test_case == Int(27), lsig2_case_27()],
+                [test_case == Int(28), lsig2_case_28()],
+                [test_case == Int(29), lsig2_case_29()],
+                [test_case == Int(30), lsig2_case_30()],
+                [test_case == Int(31), lsig2_case_31()],
+                [test_case == Int(32), lsig2_case_32()],
+                [test_case == Int(33), lsig2_case_33()],
+                [test_case == Int(34), lsig2_case_34()],
+                [test_case == Int(35), lsig2_case_35()],
+            )
+        ]
+    )
+
+
+def compile_program(output_file: Path) -> None:
+    teal = compileTeal(logic_sig(), mode=Mode.Signature, version=7)
+    with open(output_file, "w", encoding="utf-8") as f:
+        f.write(teal)
+
+
+if __name__ == "__main__":
+    compile_program(Path("logicsig_2.teal"))
diff --git a/tests/group_transactions/basic/logicsig_2.teal b/tests/group_transactions/basic/logicsig_2.teal
new file mode 100644
index 0000000..e73b4b2
--- /dev/null
+++ b/tests/group_transactions/basic/logicsig_2.teal
@@ -0,0 +1,820 @@
+#pragma version 7
+arg 0
+btoi
+int 7
+==
+bnz main_l58
+arg 0
+btoi
+int 8
+==
+bnz main_l57
+arg 0
+btoi
+int 9
+==
+bnz main_l56
+arg 0
+btoi
+int 10
+==
+bnz main_l55
+arg 0
+btoi
+int 11
+==
+bnz main_l54
+arg 0
+btoi
+int 12
+==
+bnz main_l53
+arg 0
+btoi
+int 13
+==
+bnz main_l52
+arg 0
+btoi
+int 14
+==
+bnz main_l51
+arg 0
+btoi
+int 15
+==
+bnz main_l50
+arg 0
+btoi
+int 16
+==
+bnz main_l49
+arg 0
+btoi
+int 17
+==
+bnz main_l48
+arg 0
+btoi
+int 18
+==
+bnz main_l47
+arg 0
+btoi
+int 19
+==
+bnz main_l46
+arg 0
+btoi
+int 20
+==
+bnz main_l45
+arg 0
+btoi
+int 21
+==
+bnz main_l44
+arg 0
+btoi
+int 22
+==
+bnz main_l43
+arg 0
+btoi
+int 23
+==
+bnz main_l42
+arg 0
+btoi
+int 24
+==
+bnz main_l41
+arg 0
+btoi
+int 25
+==
+bnz main_l40
+arg 0
+btoi
+int 26
+==
+bnz main_l39
+arg 0
+btoi
+int 27
+==
+bnz main_l38
+arg 0
+btoi
+int 28
+==
+bnz main_l37
+arg 0
+btoi
+int 29
+==
+bnz main_l36
+arg 0
+btoi
+int 30
+==
+bnz main_l35
+arg 0
+btoi
+int 31
+==
+bnz main_l34
+arg 0
+btoi
+int 32
+==
+bnz main_l33
+arg 0
+btoi
+int 33
+==
+bnz main_l32
+arg 0
+btoi
+int 34
+==
+bnz main_l31
+arg 0
+btoi
+int 35
+==
+bnz main_l30
+err
+main_l30:
+int 1
+return
+main_l31:
+int 1
+return
+main_l32:
+txn GroupIndex
+int 5
++
+gtxns RekeyTo
+global ZeroAddress
+==
+assert
+txn GroupIndex
+int 5
++
+gtxns Fee
+int 10000
+<
+assert
+txn GroupIndex
+int 5
++
+gtxns CloseRemainderTo
+global ZeroAddress
+==
+assert
+txn GroupIndex
+int 5
++
+gtxns AssetCloseTo
+global ZeroAddress
+==
+assert
+txn GroupIndex
+int 5
++
+gtxns OnCompletion
+int UpdateApplication
+!=
+txn GroupIndex
+int 5
++
+gtxns OnCompletion
+int DeleteApplication
+!=
+&&
+assert
+int 1
+return
+main_l33:
+txn GroupIndex
+int 4
+-
+gtxns RekeyTo
+global ZeroAddress
+==
+assert
+txn GroupIndex
+int 4
+-
+gtxns Fee
+int 10000
+<
+assert
+txn GroupIndex
+int 4
+-
+gtxns CloseRemainderTo
+global ZeroAddress
+==
+assert
+txn GroupIndex
+int 4
+-
+gtxns AssetCloseTo
+global ZeroAddress
+==
+assert
+txn GroupIndex
+int 4
+-
+gtxns OnCompletion
+int UpdateApplication
+!=
+txn GroupIndex
+int 4
+-
+gtxns OnCompletion
+int DeleteApplication
+!=
+&&
+assert
+int 1
+return
+main_l34:
+txn GroupIndex
+int 4
+-
+gtxns RekeyTo
+global ZeroAddress
+==
+assert
+txn GroupIndex
+int 4
+-
+gtxns Fee
+int 10000
+<
+assert
+txn GroupIndex
+int 4
+-
+gtxns CloseRemainderTo
+global ZeroAddress
+==
+assert
+txn GroupIndex
+int 4
+-
+gtxns AssetCloseTo
+global ZeroAddress
+==
+assert
+txn GroupIndex
+int 4
+-
+gtxns OnCompletion
+int UpdateApplication
+!=
+txn GroupIndex
+int 4
+-
+gtxns OnCompletion
+int DeleteApplication
+!=
+&&
+assert
+int 1
+return
+main_l35:
+int 1
+return
+main_l36:
+int 1
+return
+main_l37:
+txn GroupIndex
+int 4
+-
+gtxns RekeyTo
+global ZeroAddress
+==
+assert
+txn GroupIndex
+int 4
+-
+gtxns Fee
+int 10000
+<
+assert
+txn GroupIndex
+int 4
+-
+gtxns CloseRemainderTo
+global ZeroAddress
+==
+assert
+txn GroupIndex
+int 4
+-
+gtxns AssetCloseTo
+global ZeroAddress
+==
+assert
+txn GroupIndex
+int 4
+-
+gtxns OnCompletion
+int UpdateApplication
+!=
+txn GroupIndex
+int 4
+-
+gtxns OnCompletion
+int DeleteApplication
+!=
+&&
+assert
+int 1
+return
+main_l38:
+int 1
+return
+main_l39:
+int 1
+return
+main_l40:
+int 1
+return
+main_l41:
+txn RekeyTo
+global ZeroAddress
+==
+txn Fee
+int 10000
+<
+&&
+txn CloseRemainderTo
+global ZeroAddress
+==
+&&
+txn AssetCloseTo
+global ZeroAddress
+==
+&&
+txn OnCompletion
+int UpdateApplication
+!=
+&&
+txn OnCompletion
+int DeleteApplication
+!=
+&&
+assert
+int 1
+return
+main_l42:
+txn GroupIndex
+int 1
+-
+gtxns RekeyTo
+global ZeroAddress
+==
+txn GroupIndex
+int 1
+-
+gtxns Fee
+int 10000
+<
+&&
+txn GroupIndex
+int 1
+-
+gtxns CloseRemainderTo
+global ZeroAddress
+==
+&&
+txn GroupIndex
+int 1
+-
+gtxns AssetCloseTo
+global ZeroAddress
+==
+&&
+txn GroupIndex
+int 1
+-
+gtxns OnCompletion
+int UpdateApplication
+!=
+&&
+txn GroupIndex
+int 1
+-
+gtxns OnCompletion
+int DeleteApplication
+!=
+&&
+txn RekeyTo
+global ZeroAddress
+==
+&&
+txn Fee
+int 10000
+<
+&&
+txn CloseRemainderTo
+global ZeroAddress
+==
+&&
+txn AssetCloseTo
+global ZeroAddress
+==
+&&
+txn OnCompletion
+int UpdateApplication
+!=
+&&
+txn OnCompletion
+int DeleteApplication
+!=
+&&
+assert
+int 1
+return
+main_l43:
+txn GroupIndex
+int 1
+-
+gtxns RekeyTo
+global ZeroAddress
+==
+txn GroupIndex
+int 1
+-
+gtxns Fee
+int 10000
+<
+&&
+txn GroupIndex
+int 1
+-
+gtxns CloseRemainderTo
+global ZeroAddress
+==
+&&
+txn GroupIndex
+int 1
+-
+gtxns AssetCloseTo
+global ZeroAddress
+==
+&&
+txn GroupIndex
+int 1
+-
+gtxns OnCompletion
+int UpdateApplication
+!=
+&&
+txn GroupIndex
+int 1
+-
+gtxns OnCompletion
+int DeleteApplication
+!=
+&&
+txn RekeyTo
+global ZeroAddress
+==
+&&
+txn Fee
+int 10000
+<
+&&
+txn CloseRemainderTo
+global ZeroAddress
+==
+&&
+txn AssetCloseTo
+global ZeroAddress
+==
+&&
+txn OnCompletion
+int UpdateApplication
+!=
+&&
+txn OnCompletion
+int DeleteApplication
+!=
+&&
+assert
+int 1
+return
+main_l44:
+int 1
+return
+main_l45:
+txn GroupIndex
+int 5
++
+gtxns RekeyTo
+global ZeroAddress
+==
+assert
+txn GroupIndex
+int 5
++
+gtxns Fee
+int 10000
+<
+assert
+txn GroupIndex
+int 5
++
+gtxns CloseRemainderTo
+global ZeroAddress
+==
+assert
+txn GroupIndex
+int 5
++
+gtxns AssetCloseTo
+global ZeroAddress
+==
+assert
+txn GroupIndex
+int 5
++
+gtxns OnCompletion
+int UpdateApplication
+!=
+txn GroupIndex
+int 5
++
+gtxns OnCompletion
+int DeleteApplication
+!=
+&&
+assert
+int 1
+return
+main_l46:
+txn GroupIndex
+int 4
+-
+gtxns RekeyTo
+global ZeroAddress
+==
+assert
+txn GroupIndex
+int 4
+-
+gtxns Fee
+int 10000
+<
+assert
+txn GroupIndex
+int 4
+-
+gtxns CloseRemainderTo
+global ZeroAddress
+==
+assert
+txn GroupIndex
+int 4
+-
+gtxns AssetCloseTo
+global ZeroAddress
+==
+assert
+txn GroupIndex
+int 4
+-
+gtxns OnCompletion
+int UpdateApplication
+!=
+txn GroupIndex
+int 4
+-
+gtxns OnCompletion
+int DeleteApplication
+!=
+&&
+assert
+int 1
+return
+main_l47:
+int 1
+return
+main_l48:
+int 1
+return
+main_l49:
+txn RekeyTo
+global ZeroAddress
+==
+txn Fee
+int 10000
+<
+&&
+txn CloseRemainderTo
+global ZeroAddress
+==
+&&
+txn AssetCloseTo
+global ZeroAddress
+==
+&&
+txn OnCompletion
+int UpdateApplication
+!=
+&&
+txn OnCompletion
+int DeleteApplication
+!=
+&&
+assert
+int 1
+return
+main_l50:
+int 1
+return
+main_l51:
+int 1
+return
+main_l52:
+int 1
+return
+main_l53:
+gtxn 1 RekeyTo
+global ZeroAddress
+==
+gtxn 1 Fee
+int 10000
+<
+&&
+gtxn 1 CloseRemainderTo
+global ZeroAddress
+==
+&&
+gtxn 1 AssetCloseTo
+global ZeroAddress
+==
+&&
+assert
+gtxn 1 OnCompletion
+int UpdateApplication
+!=
+gtxn 1 OnCompletion
+int DeleteApplication
+!=
+&&
+assert
+int 1
+return
+main_l54:
+gtxn 0 RekeyTo
+global ZeroAddress
+==
+gtxn 1 RekeyTo
+global ZeroAddress
+==
+&&
+int 0
+gtxns Fee
+int 100000
+<
+&&
+gtxn 1 Fee
+int 1000
+<
+&&
+int 0
+gtxns CloseRemainderTo
+global ZeroAddress
+==
+&&
+gtxn 1 CloseRemainderTo
+global ZeroAddress
+==
+&&
+int 0
+gtxns AssetCloseTo
+global ZeroAddress
+==
+&&
+gtxn 1 AssetCloseTo
+global ZeroAddress
+==
+&&
+gtxn 0 OnCompletion
+int UpdateApplication
+!=
+&&
+gtxn 0 OnCompletion
+int DeleteApplication
+!=
+&&
+int 1
+gtxns OnCompletion
+int UpdateApplication
+!=
+&&
+int 1
+gtxns OnCompletion
+int DeleteApplication
+!=
+&&
+assert
+int 1
+return
+main_l55:
+gtxn 0 RekeyTo
+global ZeroAddress
+==
+gtxn 1 RekeyTo
+global ZeroAddress
+==
+&&
+int 0
+gtxns Fee
+int 100000
+<
+&&
+gtxn 1 Fee
+int 1000
+<
+&&
+int 0
+gtxns CloseRemainderTo
+global ZeroAddress
+==
+&&
+gtxn 1 CloseRemainderTo
+global ZeroAddress
+==
+&&
+int 0
+gtxns AssetCloseTo
+global ZeroAddress
+==
+&&
+gtxn 1 AssetCloseTo
+global ZeroAddress
+==
+&&
+gtxn 0 OnCompletion
+int UpdateApplication
+!=
+&&
+gtxn 0 OnCompletion
+int DeleteApplication
+!=
+&&
+int 1
+gtxns OnCompletion
+int UpdateApplication
+!=
+&&
+int 1
+gtxns OnCompletion
+int DeleteApplication
+!=
+&&
+assert
+int 1
+return
+main_l56:
+int 1
+return
+main_l57:
+txn RekeyTo
+global ZeroAddress
+==
+txn Fee
+int 10000
+<
+&&
+txn CloseRemainderTo
+global ZeroAddress
+==
+&&
+txn AssetCloseTo
+global ZeroAddress
+==
+&&
+txn OnCompletion
+int UpdateApplication
+!=
+&&
+txn OnCompletion
+int DeleteApplication
+!=
+&&
+assert
+int 1
+return
+main_l58:
+int 1
+return
\ No newline at end of file
diff --git a/tests/group_transactions/test_group_support.py b/tests/group_transactions/test_group_support.py
new file mode 100644
index 0000000..c81f5e7
--- /dev/null
+++ b/tests/group_transactions/test_group_support.py
@@ -0,0 +1,128 @@
+from typing import Tuple, List, Type
+from pathlib import Path
+import pytest
+
+
+from tealer.detectors.abstract_detector import AbstractDetector
+from tealer.detectors.all_detectors import (
+    MissingRekeyTo,
+    MissingFeeCheck,
+    CanCloseAccount,
+    CanCloseAsset,
+    IsUpdatable,
+    IsDeletable,
+    AnyoneCanDelete,
+    AnyoneCanUpdate,
+)
+from tealer.utils.command_line.group_config import read_config_from_file
+from tealer.utils.command_line.common import init_tealer_from_config
+from tealer.utils.output import GroupTransactionOutput
+
+from tests.group_transactions.utils import expected_output_from_file
+
+
+# config, detector, expected output
+ALL_TESTS: List[Tuple[Path, Type[AbstractDetector], Path]] = [
+    (
+        Path("tests/group_transactions/basic/config.yaml"),
+        MissingRekeyTo,
+        Path("tests/group_transactions/basic/expected_output.yaml"),
+    ),
+    (
+        Path("tests/group_transactions/basic/config.yaml"),
+        MissingFeeCheck,
+        Path("tests/group_transactions/basic/expected_output.yaml"),
+    ),
+    (
+        Path("tests/group_transactions/basic/config.yaml"),
+        CanCloseAccount,
+        Path("tests/group_transactions/basic/expected_output_close_to.yaml"),
+    ),
+    (
+        Path("tests/group_transactions/basic/config.yaml"),
+        CanCloseAsset,
+        Path("tests/group_transactions/basic/expected_output_asset_close_to.yaml"),
+    ),
+    (
+        Path("tests/group_transactions/basic/config.yaml"),
+        IsUpdatable,
+        Path("tests/group_transactions/basic/expected_output_update_delete.yaml"),
+    ),
+    (
+        Path("tests/group_transactions/basic/config.yaml"),
+        IsDeletable,
+        Path("tests/group_transactions/basic/expected_output_update_delete.yaml"),
+    ),
+    (
+        Path("tests/group_transactions/basic/config.yaml"),
+        AnyoneCanUpdate,
+        Path("tests/group_transactions/basic/expected_output_update_delete.yaml"),
+    ),
+    (
+        Path("tests/group_transactions/basic/config.yaml"),
+        AnyoneCanDelete,
+        Path("tests/group_transactions/basic/expected_output_update_delete.yaml"),
+    ),
+]
+
+
+# pylint: disable=too-many-locals
+@pytest.mark.parametrize("test", ALL_TESTS)  # type: ignore
+def test_instructions_output_detectors(test: Tuple[Path, Type[AbstractDetector], Path]) -> None:
+    config_path, detector, expected_output_file_path = test
+
+    expected_output = expected_output_from_file(expected_output_file_path)
+    tealer = init_tealer_from_config(read_config_from_file(config_path))
+    tealer.register_detector(detector)
+    output = tealer.run_detectors()[0]
+
+    reported_output: List["GroupTransactionOutput"] = []
+    for i in output:
+        assert isinstance(i, GroupTransactionOutput)
+        reported_output.append(i)
+        i.generate_output(Path("."))
+
+    # expected output is also sorted by operation name
+    reported_output = sorted(reported_output, key=lambda x: x.group_transaction.operation_name)
+    expected_output_operations = sorted(expected_output.operations, key=lambda x: x.operation)
+    # same number of groups/operations are reported as vulnerable
+    assert len(reported_output) == len(expected_output_operations)
+
+    for expected_operation, reported_operation in zip(expected_output_operations, reported_output):
+        # reported same operation
+        assert expected_operation.operation == reported_operation.group_transaction.operation_name
+        # reported same transactions
+        assert len(expected_operation.vulnerable_transactions) == len(
+            reported_operation.transactions.keys()
+        )
+        expected_transactions = sorted(
+            expected_operation.vulnerable_transactions, key=lambda x: x.transaction_id
+        )
+        reported_transactions = sorted(
+            list(reported_operation.transactions.keys()), key=lambda x: x.transacton_id
+        )
+
+        for expected_txn, reported_txn in zip(expected_transactions, reported_transactions):
+            # same transaction id
+            assert expected_txn.transaction_id == reported_txn.transacton_id
+            # same contracts
+            expected_contracts = sorted(
+                expected_txn.contracts, key=lambda x: (x.contract_name, x.function_name)
+            )
+            reported_contracts = sorted(
+                reported_operation.transactions[reported_txn],
+                key=lambda x: (x.contract.contract_name, x.function_name),
+            )
+
+            assert len(expected_contracts) == len(reported_contracts)
+            for expected_contract, reported_contract in zip(expected_contracts, reported_contracts):
+                # same contract name and function name
+                assert expected_contract.contract_name == reported_contract.contract.contract_name
+                assert expected_contract.function_name == reported_contract.function_name
+
+    # for i in output:
+    #     # assert isinstance(output, Gr)
+    #     out = i.to_json()
+    #     print(out["operation"])
+    #     print(out["transactions"])
+    # assert(False)
diff --git a/tests/group_transactions/utils.py b/tests/group_transactions/utils.py
new file mode 100644
index 0000000..ed2e406
--- /dev/null
+++ b/tests/group_transactions/utils.py
@@ -0,0 +1,84 @@
+from typing import Dict, Any, List
+from dataclasses import dataclass
+from pathlib import Path
+import yaml
+
+
+class InvalidTestCase(Exception):
+    pass
+
+
+@dataclass
+class OutputContract:
+    contract_name: str
+    function_name: str
+
+    @staticmethod
+    def from_yaml(contract: Dict[str, Any]) -> "OutputContract":
+        if "contract" not in contract or "function" not in contract:
+            raise InvalidTestCase(
+                f"Missing required fields in the contract description: {contract}"
+            )
+        return OutputContract(contract["contract"], contract["function"])
+
+
+@dataclass
+class OutputTransaction:
+    transaction_id: str
+    contracts: List[OutputContract]
+
+    @staticmethod
+    def from_yaml(transaction: Dict[str, Any]) -> "OutputTransaction":
+        if "txn_id" not in transaction:
+            raise InvalidTestCase(f'Missing "txn_id" field for the transaction {transaction}')
+
+        contracts = []
+        if "contracts" in transaction:
+            for contract in transaction["contracts"]:
+                contracts.append(OutputContract.from_yaml(contract))
+        return OutputTransaction(
+            transaction["txn_id"],
+            sorted(contracts, key=lambda x: (x.contract_name, x.function_name)),
+        )
+
+
+@dataclass
+class OutputOperation:
+    operation: str
+    vulnerable_transactions: List[OutputTransaction]
+
+    @staticmethod
+    def from_yaml(operation: Dict[str, Any]) -> "OutputOperation":
+        if "operation" not in operation or "vulnerable_transactions" not in operation:
+            raise InvalidTestCase(f"Missing required fields in the operation: {operation}")
+
+        operation_name = operation["operation"]
+
+        vulnerable_transactions = []
+        for transaction in operation["vulnerable_transactions"]:
+            vulnerable_transactions.append(OutputTransaction.from_yaml(transaction))
+
+        return OutputOperation(operation_name, vulnerable_transactions)
+
+
+@dataclass
+class ExpectedOutput:
+    name: str
+    operations: List[OutputOperation]
+
+    @staticmethod
+    def from_yaml(output: Dict[str, Any]) -> "ExpectedOutput":
+        if "name" not in output or "operations" not in output:
+            raise InvalidTestCase(f"Missing required fields in the expected output: {output}")
+
+        name = output["name"]
+        operations = []
+        for operation in output["operations"]:
+            operations.append(OutputOperation.from_yaml(operation))
+        return ExpectedOutput(name, operations)
+
+
+def expected_output_from_file(file_path: Path) -> "ExpectedOutput":
+    with open(file_path, encoding="utf-8") as f:
+        # print(yaml.safe_load(f.read()))
+        return ExpectedOutput.from_yaml(yaml.safe_load(f.read()))
diff --git a/tests/parsing/teal8-instructions.teal b/tests/parsing/teal8-instructions.teal
index 98f85fc..557e563 100644
--- a/tests/parsing/teal8-instructions.teal
+++ b/tests/parsing/teal8-instructions.teal
@@ -1,6 +1,4 @@
-#pragma version 8
-
-
+#pragma version 8 // B0
 int 1
 int 2
 int 3
@@ -10,22 +8,30 @@ dupn 1
 pushbytess b32(AA) base64 AA 0x00 "00"
 pushints 1 2 3 4 0x5
 callsub label1
-proto 1 2
+
+proto 1 2   // B1
 frame_dig 1
 frame_bury 1
 switch label2 label3
-match label2 label3
-box_create
+
+match label2 label3     // B2
+
+box_create              // B3
 box_extract
 box_replace
 box_del
 box_len
 box_get
 box_put
-label1:
+int 1
+return
+
+label1:                 // B4
 int 1
 retsub
-label2:
+
+label2:                 // B5
 int 2
-label3:
+
+label3:                 // B6
 int 3
\ No newline at end of file
diff --git a/tests/pyteal_parsing/control_flow_constructs.py b/tests/pyteal_parsing/control_flow_constructs.py
index 9460500..60249a8 100644
--- a/tests/pyteal_parsing/control_flow_constructs.py
+++ b/tests/pyteal_parsing/control_flow_constructs.py
@@ -1,3 +1,4 @@
+# pylint: skip-file
 # pylint: disable=undefined-variable
 # type: ignore[name-defined]
 
diff --git a/tests/pyteal_parsing/normal_application.py b/tests/pyteal_parsing/normal_application.py
index 2800f63..5baee8f 100644
--- a/tests/pyteal_parsing/normal_application.py
+++ b/tests/pyteal_parsing/normal_application.py
@@ -1,3 +1,4 @@
+# pylint: skip-file
 # pylint: disable=undefined-variable
 # type: ignore[name-defined]
 from pyteal import *  # pylint: disable=wildcard-import, unused-wildcard-import
diff --git a/tests/regex/atomic_swap.py b/tests/regex/atomic_swap.py
new file mode 100644
index 0000000..3eee43c
--- /dev/null
+++ b/tests/regex/atomic_swap.py
@@ -0,0 +1,108 @@
+# pylint: skip-file
+# type: ignore
+# Example from https://pyteal.readthedocs.io/en/stable/examples.html
+
+from pyteal import *
+
+
+def approval_program():
+    on_creation = Seq(
+        [
+            App.globalPut(Bytes("Creator"), Txn.sender()),
+            Assert(Txn.application_args.length() == Int(4)),
+            App.globalPut(Bytes("RegBegin"), Btoi(Txn.application_args[0])),
+            App.globalPut(Bytes("RegEnd"), Btoi(Txn.application_args[1])),
+            App.globalPut(Bytes("VoteBegin"), Btoi(Txn.application_args[2])),
+            App.globalPut(Bytes("VoteEnd"), Btoi(Txn.application_args[3])),
+            Return(Int(1)),
+        ]
+    )
+
+    is_creator = Txn.sender() == App.globalGet(Bytes("Creator"))
+
+    get_vote_of_sender = App.localGetEx(Int(0), App.id(), Bytes("voted"))
+
+    on_closeout = Seq(
+        [
+            get_vote_of_sender,
+            If(
+                And(
+                    Global.round() <= App.globalGet(Bytes("VoteEnd")),
+                    get_vote_of_sender.hasValue(),
+                ),
+                App.globalPut(
+                    get_vote_of_sender.value(),
+                    App.globalGet(get_vote_of_sender.value()) - Int(1),
+                ),
+            ),
+            Return(Int(1)),
+        ]
+    )
+
+    on_register = Return(
+        And(
+            Global.round() >= App.globalGet(Bytes("RegBegin")),
+            Global.round() <= App.globalGet(Bytes("RegEnd")),
+        )
+    )
+
+    choice = Txn.application_args[1]
+    choice_tally = App.globalGet(choice)
+    on_vote = Seq(
+        [
+            Assert(
+                And(
+                    Global.round() >= App.globalGet(Bytes("VoteBegin")),
+                    Global.round() <= App.globalGet(Bytes("VoteEnd")),
+                )
+            ),
+            get_vote_of_sender,
+            If(get_vote_of_sender.hasValue(), Return(Int(0))),
+            App.globalPut(choice, choice_tally + Int(1)),
+            App.localPut(Int(0), Bytes("voted"), choice),
+            Return(Int(1)),
+        ]
+    )
+
+    program = Cond(
+        [Txn.application_id() == Int(0), on_creation],
+        [Txn.on_completion() == OnComplete.DeleteApplication, Return(is_creator)],
+        [Txn.on_completion() == OnComplete.UpdateApplication, Return(is_creator)],
+        [Txn.on_completion() == OnComplete.CloseOut, on_closeout],
+        [Txn.on_completion() == OnComplete.OptIn, on_register],
+        [Txn.application_args[0] == Bytes("vote"), on_vote],
+    )
+
+    return program
+
+
+def clear_state_program():
+    get_vote_of_sender = App.localGetEx(Int(0), App.id(), Bytes("voted"))
+    program = Seq(
+        [
+            get_vote_of_sender,
+            If(
+                And(
+                    Global.round() <= App.globalGet(Bytes("VoteEnd")),
+                    get_vote_of_sender.hasValue(),
+                ),
+                App.globalPut(
+                    get_vote_of_sender.value(),
+                    App.globalGet(get_vote_of_sender.value()) - Int(1),
+                ),
+            ),
+            Return(Int(1)),
+        ]
+    )
+
+    return program
+
+
+if __name__ == "__main__":
+    with open("vote_approval.teal", "w") as f:
+        compiled = compileTeal(approval_program(), mode=Mode.Application, version=2)
+        f.write(compiled)
+
+    with open("vote_clear_state.teal", "w") as f:
+        compiled = compileTeal(clear_state_program(), mode=Mode.Application, version=2)
+        f.write(compiled)
diff --git a/tests/regex/regex.txt b/tests/regex/regex.txt
new file mode 100644
index 0000000..df20c4b
--- /dev/null
+++ b/tests/regex/regex.txt
@@ -0,0 +1,3 @@
+main_l7 =>
+ int 0
+ return
\ No newline at end of file
diff --git a/tests/regex/vote_approval.teal b/tests/regex/vote_approval.teal
new file mode 100644
index 0000000..f1d93be
--- /dev/null
+++ b/tests/regex/vote_approval.teal
@@ -0,0 +1,138 @@
+#pragma version 2
+txn ApplicationID
+int 0
+==
+bnz main_l18
+txn OnCompletion
+int DeleteApplication
+==
+bnz main_l17
+txn OnCompletion
+int UpdateApplication
+==
+bnz main_l16
+txn OnCompletion
+int CloseOut
+==
+bnz main_l13
+txn OnCompletion
+int OptIn
+==
+bnz main_l12
+txna ApplicationArgs 0
+byte "vote"
+==
+bnz main_l7
+err
+main_l7:
+global Round
+byte "VoteBegin"
+app_global_get
+>=
+global Round
+byte "VoteEnd"
+app_global_get
+<=
+&&
+bnz main_l9
+err
+main_l9:
+int 0
+global CurrentApplicationID
+byte "voted"
+app_local_get_ex
+store 1
+store 0
+load 1
+bnz main_l11
+txna ApplicationArgs 1
+txna ApplicationArgs 1
+app_global_get
+int 1
++
+app_global_put
+int 0
+byte "voted"
+txna ApplicationArgs 1
+app_local_put
+int 1
+return
+main_l11:
+int 0
+return
+main_l12:
+global Round
+byte "RegBegin"
+app_global_get
+>=
+global Round
+byte "RegEnd"
+app_global_get
+<=
+&&
+return
+main_l13:
+int 0
+global CurrentApplicationID
+byte "voted"
+app_local_get_ex
+store 1
+store 0
+global Round
+byte "VoteEnd"
+app_global_get
+<=
+load 1
+&&
+bnz main_l15
+main_l14:
+int 1
+return
+main_l15:
+load 0
+load 0
+app_global_get
+int 1
+-
+app_global_put
+b main_l14
+main_l16:
+txn Sender
+byte "Creator"
+app_global_get
+==
+return
+main_l17:
+txn Sender
+byte "Creator"
+app_global_get
+==
+return
+main_l18:
+byte "Creator"
+txn Sender
+app_global_put
+txn NumAppArgs
+int 4
+==
+bnz main_l20
+err
+main_l20:
+byte "RegBegin"
+txna ApplicationArgs 0
+btoi
+app_global_put
+byte "RegEnd"
+txna ApplicationArgs 1
+btoi
+app_global_put
+byte "VoteBegin"
+txna ApplicationArgs 2
+btoi
+app_global_put
+byte "VoteEnd"
+txna ApplicationArgs 3
+btoi
+app_global_put
+int 1
+return
\ No newline at end of file
diff --git a/tests/test_cfg.py b/tests/test_cfg.py
index 2090da5..3772ed4 100644
--- a/tests/test_cfg.py
+++ b/tests/test_cfg.py
@@ -1,8 +1,9 @@
-from typing import List, Tuple
+from typing import List, Tuple, Dict
 import pytest
 
 
 from tealer.teal.basic_blocks import BasicBlock
+from tealer.teal.subroutine import Subroutine
 from tealer.teal.instructions import instructions
 from tealer.teal.instructions import transaction_field
 from tealer.teal.parse_teal import parse_teal
@@ -47,11 +48,22 @@
 ]
 
 ins_partitions = [(0, 2), (2, 6), (6, 8), (8, 11), (11, 14), (14, 15)]
-bbs_links = [(0, 4), (4, 1), (1, 2), (1, 3), (2, 5), (3, 5)]
+bbs_links = [(0, 4), (4, 5), (1, 2), (1, 3)]
+
+bbs_edges_new = [(0, 4), (4, 5), (1, 2), (1, 3)]
 
 
 MULTIPLE_RETSUB_CFG = construct_cfg(ins_list, ins_partitions, bbs_links)
 
+MULTIPLE_RETSUB_CFG_NEW = construct_cfg(ins_list, ins_partitions, bbs_edges_new)
+
+bbs = MULTIPLE_RETSUB_CFG_NEW
+MULTIPLE_RETSUB_MAIN_NEW = Subroutine("__main__", bbs[0], [bbs[0], bbs[4], bbs[5]])
+MULTIPLE_RETSUB_SUBROUTINES_NEW = {
+    "is_even": Subroutine("is_even", bbs[1], [bbs[1], bbs[2], bbs[3]])
+}
+
+MULTIPLE_RETSUB_SUBROUTINES_NEW["is_even"].caller_blocks = [bbs[4]]
 
 SUBROUTINE_BACK_JUMP = """
 #pragma version 5
@@ -84,9 +96,18 @@
 ]
 
 ins_partitions = [(0, 2), (2, 5), (5, 8), (8, 11), (11, 12)]
-bbs_links = [(0, 3), (3, 2), (2, 1), (1, 4)]
+bbs_links = [(0, 3), (3, 4), (2, 1)]
+
+bbs_edges_new = [(0, 3), (3, 4), (2, 1)]
 
 SUBROUTINE_BACK_JUMP_CFG = construct_cfg(ins_list, ins_partitions, bbs_links)
+SUBROUTINE_BACK_JUMP_CFG_NEW = construct_cfg(ins_list, ins_partitions, bbs_edges_new)
+
+bbs = SUBROUTINE_BACK_JUMP_CFG_NEW
+SUBROUTINE_BACK_JUMP_MAIN_NEW = Subroutine("__main__", bbs[0], [bbs[0], bbs[3], bbs[4]])
+SUBROUTINE_BACK_JUMP_SUBROUTINES_NEW = {"is_odd": Subroutine("is_odd", bbs[2], [bbs[1], bbs[2]])}
+SUBROUTINE_BACK_JUMP_SUBROUTINES_NEW["is_odd"].caller_blocks = [bbs[3]]
+
 
 BRANCHING = """
 #pragma version 2
@@ -142,6 +163,14 @@
 bbs_links = [(0, 1), (0, 5), (1, 2), (1, 3), (3, 4), (3, 5)]
 
 BRANCHING_CFG = construct_cfg(ins_list, ins_partitions, bbs_links)
+BRANCHING_CFG_NEW = construct_cfg(ins_list, ins_partitions, bbs_links)
+
+bbs = BRANCHING_CFG_NEW
+BRANCHING_MAIN_NEW = Subroutine(
+    "__main__", bbs[0], [bbs[0], bbs[1], bbs[2], bbs[3], bbs[4], bbs[5]]
+)
+BRANCHING_SUBROUTINES_NEW: Dict[str, Subroutine] = {}
+
 
 LOOPS = """
 int 0
@@ -177,6 +206,12 @@
 bbs_links = [(0, 1), (1, 2), (1, 3), (2, 1)]
 
 LOOPS_CFG = construct_cfg(ins_list, ins_partitions, bbs_links)
+LOOPS_CFG_NEW = construct_cfg(ins_list, ins_partitions, bbs_links)
+
+bbs = LOOPS_CFG_NEW
+LOOPS_MAIN_NEW = Subroutine("__main__", bbs[0], [bbs[0], bbs[1], bbs[2], bbs[3]])
+LOOPS_SUBROUTINES_NEW: Dict[str, Subroutine] = {}
+
 
 SWITCH_N_MATCH = """
 #pragma version 8
@@ -218,6 +253,12 @@
 bbs_links = [(0, 1), (0, 3), (0, 4), (1, 2), (1, 3), (1, 4)]
 
 SWITCH_N_MATCH_CFG = construct_cfg(ins_list, ins_partitions, bbs_links)
+SWITCH_N_MATCH_CFG_NEW = construct_cfg(ins_list, ins_partitions, bbs_links)
+
+bbs = SWITCH_N_MATCH_CFG_NEW
+SWITCH_N_MATCH_MAIN_NEW = Subroutine("__main__", bbs[0], [bbs[0], bbs[1], bbs[2], bbs[3], bbs[4]])
+SWITCH_N_MATCH_SUBROUTINES_NEW: Dict[str, Subroutine] = {}
+
 
 ALL_TESTS = [
     (MULTIPLE_RETSUB, MULTIPLE_RETSUB_CFG),
@@ -227,6 +268,29 @@
     (SWITCH_N_MATCH, SWITCH_N_MATCH_CFG),
 ]
 
+ALL_TESTS_NEW = [
+    (
+        MULTIPLE_RETSUB,
+        MULTIPLE_RETSUB_CFG_NEW,
+        MULTIPLE_RETSUB_MAIN_NEW,
+        MULTIPLE_RETSUB_SUBROUTINES_NEW,
+    ),
+    (
+        SUBROUTINE_BACK_JUMP,
+        SUBROUTINE_BACK_JUMP_CFG_NEW,
+        SUBROUTINE_BACK_JUMP_MAIN_NEW,
+        SUBROUTINE_BACK_JUMP_SUBROUTINES_NEW,
+    ),
+    (BRANCHING, BRANCHING_CFG_NEW, BRANCHING_MAIN_NEW, BRANCHING_SUBROUTINES_NEW),
+    (LOOPS, LOOPS_CFG_NEW, LOOPS_MAIN_NEW, LOOPS_SUBROUTINES_NEW),
+    (
+        SWITCH_N_MATCH,
+        SWITCH_N_MATCH_CFG_NEW,
+        SWITCH_N_MATCH_MAIN_NEW,
+        SWITCH_N_MATCH_SUBROUTINES_NEW,
+    ),
+]
+
 
 @pytest.mark.parametrize("test", ALL_TESTS)  # type: ignore
 def test_cfg_construction(test: Tuple[str, List[BasicBlock]]) -> None:
@@ -238,3 +302,33 @@ def test_cfg_construction(test: Tuple[str, List[BasicBlock]]) -> None:
         print(bb)
     print("*" * 20)
     assert cmp_cfg(teal.bbs, cfg)
+
+
+@pytest.mark.parametrize("test", ALL_TESTS_NEW)  # type: ignore
+def test_cfg_construction_new(
+    test: Tuple[str, List[BasicBlock], Subroutine, Dict[str, Subroutine]]
+) -> None:
+    code, expected_cfg, expected_main, expected_subroutines = test
+    teal = parse_teal(code.strip())
+    for bb in expected_cfg:
+        print(bb)
+    for bb in teal.bbs:
+        print(bb)
+    print("*" * 20)
+    assert cmp_cfg(teal.bbs, expected_cfg)
+    test_main = teal.main
+    test_subroutines = teal.subroutines
+
+    assert cmp_cfg([test_main.entry], [expected_main.entry])
+    assert cmp_cfg(test_main.blocks, expected_main.blocks)
+
+    assert len(test_subroutines.keys()) == len(expected_subroutines.keys())
+    assert sorted(test_subroutines.keys()) == sorted(expected_subroutines.keys())
+    for ex_name in expected_subroutines:
+        test_subroutine = test_subroutines[ex_name]
+        expected_subroutine = expected_subroutines[ex_name]
+        assert test_subroutine.name == expected_subroutine.name
+        assert cmp_cfg([test_subroutine.entry], [expected_subroutine.entry])
+        assert cmp_cfg(test_subroutine.blocks, expected_subroutine.blocks)
+        assert cmp_cfg(test_subroutine.caller_blocks, expected_subroutine.caller_blocks)
+    assert len(test_subroutines) == len(expected_subroutines)
diff --git a/tests/test_detectors.py b/tests/test_detectors.py
index 15ca6d0..82d954a 100644
--- a/tests/test_detectors.py
+++ b/tests/test_detectors.py
@@ -3,7 +3,8 @@
 
 from tealer.teal.basic_blocks import BasicBlock
 from tealer.detectors.abstract_detector import AbstractDetector
-from tealer.teal.parse_teal import parse_teal
+from tealer.utils.command_line.common import init_tealer_from_single_contract
+from tealer.utils.output import ExecutionPaths, InstructionsOutput
 
 from tests.detectors.groupsize import missing_group_size_tests
 from tests.detectors.fee_check import missing_fee_check_tests, new_missing_fee_tests
@@ -11,6 +12,9 @@
 from tests.detectors.can_close_asset import can_close_asset_tests, new_can_close_asset_tests
 from tests.detectors.can_delete import can_delete_tests, new_can_delete_tests
 from tests.detectors.can_update import can_update_tests, new_can_update_tests
+from tests.detectors.optimizations.constant_gtxn import constant_gtxn
+from tests.detectors.optimizations.sender_access import sender_access
+from tests.detectors.optimizations.self_access import self_access
 from tests.detectors.rekeyto import missing_rekeyto_tests, new_missing_rekeyto_tests
 from tests.detectors.subroutine_patterns import subroutine_patterns_tests
 
@@ -31,12 +35,18 @@
 @pytest.mark.parametrize("test", ALL_TESTS)  # type: ignore
 def test_detectors(test: Tuple[str, Type[AbstractDetector], List[List[BasicBlock]]]) -> None:
     code, detector, expected_paths = test
-    teal = parse_teal(code.strip())
-    teal.register_detector(detector)
-    result = teal.run_detectors()[0]
-    assert len(result.paths) == len(expected_paths)
-    for path, ex_path in zip(result.paths, expected_paths):
-        assert cmp_cfg(path, ex_path)
+    tealer = init_tealer_from_single_contract(code.strip(), "test")
+    tealer.register_detector(detector)
+    result = tealer.run_detectors()[0][0]
+
+    if isinstance(result, ExecutionPaths):
+        assert len(result.paths) == len(expected_paths)
+        for path, ex_path in zip(result.paths, expected_paths):
+            assert cmp_cfg(path, ex_path)
+
+    else:
+        # Not implemented yet
+        assert False
 
 
 ALL_NEW_TESTS: List[Tuple[str, Type[AbstractDetector], List[List[int]]]] = [
@@ -53,29 +63,56 @@ def test_detectors(test: Tuple[str, Type[AbstractDetector], List[List[BasicBlock
 @pytest.mark.parametrize("test", ALL_NEW_TESTS)  # type: ignore
 def test_just_detectors(test: Tuple[str, Type[AbstractDetector], List[List[int]]]) -> None:
     code, detector, expected_paths = test
-    teal = parse_teal(code.strip())
-    teal.register_detector(detector)
-    result = teal.run_detectors()[0]
-    for bi in teal.bbs:
-        print(
-            bi,
-            bi.idx,
-            bi.transaction_context.transaction_types,
-            bi.transaction_context.group_indices,
-        )
-        print(
-            bi.transaction_context.gtxn_context(0).transaction_types,
-            bi.transaction_context.group_indices,
-        )
+    tealer = init_tealer_from_single_contract(code.strip(), "test")
+    tealer.register_detector(detector)
+    result = tealer.run_detectors()[0][0]
+
+    if isinstance(result, ExecutionPaths):
+        print(f"count: result = {len(result.paths)}, expected = {len(expected_paths)}")
+        assert len(result.paths) == len(expected_paths)
+        for path, expected_path in zip(result.paths, expected_paths):
+            print(f"path length: result = {len(path)}, expected = {len(expected_path)}")
+            print(f"path ids: result = {path}, expected = {expected_path}")
+            assert len(path) == len(expected_path)
+            for bi, expected_idx in zip(path, expected_path):
+                assert bi.idx == expected_idx
+
+    else:
+        # Not implemented yet
+        assert False
+
+
+# This test is for the detectors using InstructionsOutput
+# This type return only a list of instructions
+# So we compare the expected lines of the source code
+# source code -> detector -> lust(expected lines)
+TESTS_INSTRUCTIONS_OUTPUT: List[Tuple[str, Type[AbstractDetector], List[List[int]]]] = [
+    *constant_gtxn,
+    *sender_access,
+    *self_access,
+]
+
+
+@pytest.mark.parametrize("test", TESTS_INSTRUCTIONS_OUTPUT)  # type: ignore
+def test_instructions_output_detectors(
+    test: Tuple[str, Type[AbstractDetector], List[List[int]]]
+) -> None:
+    code, detector, expected_instruction_lines = test
+    tealer = init_tealer_from_single_contract(code.strip(), "test")
+    tealer.register_detector(detector)
+    result = tealer.run_detectors()[0][0]
+
+    if isinstance(result, InstructionsOutput):
         print(
-            bi.transaction_context.gtxn_context(1).transaction_types,
-            bi.transaction_context.group_indices,
+            f"count: result = {len(result.instructions)}, expected = {len(expected_instruction_lines)}"
         )
-    print(f"count: result = {len(result.paths)}, expected = {len(expected_paths)}")
-    assert len(result.paths) == len(expected_paths)
-    for path, expected_path in zip(result.paths, expected_paths):
-        print(f"path length: result = {len(path)}, expected = {len(expected_path)}")
-        print(f"path ids: result = {path}, expected = {expected_path}")
-        assert len(path) == len(expected_path)
-        for bi, expected_idx in zip(path, expected_path):
-            assert bi.idx == expected_idx
+
+        actual_instruction_lines = [
+            [ins.line for ins in instructions] for instructions in result.instructions
+        ]
+
+        assert actual_instruction_lines == expected_instruction_lines
+
+    else:
+        # Not implemented yet
+        assert False
diff --git a/tests/test_detectors_using_pyteal.py b/tests/test_detectors_using_pyteal.py
index 734f5a1..e53b16b 100644
--- a/tests/test_detectors_using_pyteal.py
+++ b/tests/test_detectors_using_pyteal.py
@@ -5,7 +5,8 @@
 import pytest
 
 from tealer.detectors.abstract_detector import AbstractDetector
-from tealer.teal.parse_teal import parse_teal
+from tealer.utils.command_line.common import init_tealer_from_single_contract
+from tealer.utils.output import ExecutionPaths
 
 if not sys.version_info >= (3, 10):
     pytest.skip(reason="PyTeal based tests require python >= 3.10", allow_module_level=True)
@@ -41,9 +42,12 @@
 @pytest.mark.parametrize("test", TESTS)  # type: ignore
 def test_detectors_using_pyteal(test: Tuple[str, Type[AbstractDetector], List[List[int]]]) -> None:
     code, detector, expected_paths = test
-    teal = parse_teal(code.strip())
-    teal.register_detector(detector)
-    result = teal.run_detectors()[0]
+    tealer = init_tealer_from_single_contract(code.strip(), "test")
+    tealer.register_detector(detector)
+    result = tealer.run_detectors()[0]
+    if not isinstance(result, ExecutionPaths):
+        result = result[0]
+
     # for bi in teal.bbs:
     #     print(
     #         bi,
diff --git a/tests/test_mode_detector.py b/tests/test_mode_detector.py
index eef6605..f6cba41 100644
--- a/tests/test_mode_detector.py
+++ b/tests/test_mode_detector.py
@@ -1,7 +1,7 @@
 import pytest  # pylint: disable=unused-import
 
 from tealer.teal.parse_teal import parse_teal
-from tealer.teal.instructions.instructions import ContractType
+from tealer.utils.teal_enums import ExecutionMode
 
 STATEFULL = """
 #pragma version 5
@@ -27,12 +27,12 @@
 def test_mode() -> None:
     teal = parse_teal(STATEFULL)
     assert teal.version == 5
-    assert teal.mode == ContractType.STATEFULL
+    assert teal.mode == ExecutionMode.STATEFUL
 
     teal = parse_teal(STATELESS)
     assert teal.version == 1
-    assert teal.mode == ContractType.STATELESS
+    assert teal.mode == ExecutionMode.STATELESS
 
     teal = parse_teal(ANY)
     assert teal.version == 1
-    assert teal.mode == ContractType.ANY
+    assert teal.mode == ExecutionMode.ANY
diff --git a/tests/test_parsing.py b/tests/test_parsing.py
index 6409835..1eb12de 100644
--- a/tests/test_parsing.py
+++ b/tests/test_parsing.py
@@ -9,10 +9,19 @@
     BytecInstruction,
 )
 from tealer.teal.instructions import transaction_field
+from tealer.teal.global_field import GlobalField
+from tealer.teal.instructions.transaction_field import TransactionField
+from tealer.teal.instructions.acct_params_field import AcctParamsField
+from tealer.teal.instructions.app_params_field import AppParamsField
+from tealer.teal.instructions.asset_holding_field import AssetHoldingField
+from tealer.teal.instructions.asset_params_field import AssetParamsField
 from tealer.teal.instructions.parse_instruction import parse_line, ParseError
 from tealer.teal.parse_teal import parse_teal
+from tealer.teal.parse_functions import copy_main_cfg
 from tealer.utils.analyses import is_int_push_ins, is_byte_push_ins
+from tealer.exceptions import TealerException
 
+from tests.utils import cmp_cfg
 
 TARGETS = [
     "tests/parsing/teal1-instructions.teal",
@@ -109,9 +118,58 @@ def test_parsing(target: str) -> None:
     with open(target, encoding="utf-8") as f:
         teal = parse_teal(f.read())
     # print instruction to trigger __str__ on each ins
+    # print stack pop/push to trigger the function on each ins
     for i in teal.instructions:
         assert not isinstance(i, instructions.UnsupportedInstruction), f'ins "{i}" is not supported'
         print(i, i.cost)
+        print(i, i.stack_pop_size)
+        print(i, i.stack_push_size)
+
+
+# pylint: disable=too-many-locals
+@pytest.mark.parametrize("target", TARGETS)  # type: ignore
+def test_copy_main_cfg(target: str) -> None:
+    with open(target, encoding="utf-8") as f:
+        teal = parse_teal(f.read())
+    copied_main = copy_main_cfg(teal)
+
+    assert cmp_cfg(copied_main, teal.main.blocks)
+    copied_blocks = sorted(copied_main, key=lambda bi: bi.idx)
+    original_blocks = sorted(teal.main.blocks, key=lambda bi: bi.idx)
+    copied_instructions = [ins for bi in copied_blocks for ins in bi.instructions]
+    # original_instructions = [ins for bi in original_blocks for ins in bi.instructions]
+
+    print(vars(copied_blocks[0]).keys())
+    print(vars(copied_instructions[0]).keys())
+
+    ignore_block_fields = ["_instructions", "_prev", "_next", "_transaction_context", "_subroutine"]
+    ignore_instruction_fields = ["_prev", "_next", "_bb"]
+    for bb_copy, bb_orig in zip(copied_blocks, original_blocks):
+        for key in vars(bb_copy):
+            if key in ignore_block_fields:
+                continue
+            print(key)
+            assert vars(bb_copy)[key] == vars(bb_orig)[key]
+        for ins_copy, ins_orig in zip(bb_copy.instructions, bb_orig.instructions):
+            ins_copy_vars = vars(ins_copy)
+            ins_orig_vars = vars(ins_orig)
+            for key in ins_copy_vars:
+                if key in ignore_instruction_fields:
+                    continue
+                if isinstance(
+                    ins_copy_vars[key],
+                    (
+                        TransactionField,
+                        GlobalField,
+                        AppParamsField,
+                        AcctParamsField,
+                        AssetParamsField,
+                        AssetHoldingField,
+                    ),
+                ):
+                    continue
+                print(key)
+                assert ins_copy_vars[key] == ins_orig_vars[key]
 
 
 def _cmp_instructions(
@@ -257,14 +315,6 @@ def test_instruction_properties() -> None:
     assert ins2.next == []
     assert ins1.comment == "// comment"
 
-    # cannot set return point of callsub instruction multiple times
-    ins = parse_line("callsub main")
-    assert isinstance(ins, instructions.Callsub) and ins.return_point is None
-
-    ins.return_point = parse_line("int 1")
-    with pytest.raises(Exception):
-        ins.return_point = parse_line("int 1")  # cannot set multiple times
-
     # accessing replace instruction start_position fails if it is None i.e if there's no immediate argument.
     # it should be checked that whether given replace instruction is semantically equivalent to replace2 or replace3.
     ins = parse_line("replace 1")
@@ -305,7 +355,7 @@ def test_cost_values() -> None:
     for line in CURRENT_TEST_CODE.strip().splitlines():
         # when cost parameter accessed, it checks that instruction object's BasicBlock is not none. if it is none, then
         # cost property raises exception. These tests are included to cover those brances.
-        with pytest.raises(ValueError):
+        with pytest.raises(TealerException):
             print(line)
             # pylint: disable=expression-not-assigned
             parse_line(line).cost  # type: ignore
diff --git a/tests/test_parsing_using_pyteal.py b/tests/test_parsing_using_pyteal.py
index 6439775..e621eec 100644
--- a/tests/test_parsing_using_pyteal.py
+++ b/tests/test_parsing_using_pyteal.py
@@ -26,6 +26,9 @@
 def test_parsing_using_pyteal(target: str) -> None:
     teal = parse_teal(target)
     # print instruction to trigger __str__ on each ins
+    # print stack pop/push to trigger the function on each ins
     for i in teal.instructions:
         assert not isinstance(i, instructions.UnsupportedInstruction), f'ins "{i}" is not supported'
         print(i, i.cost)
+        print(i, i.stack_pop_size)
+        print(i, i.stack_push_size)
diff --git a/tests/test_regex.py b/tests/test_regex.py
new file mode 100644
index 0000000..d11a280
--- /dev/null
+++ b/tests/test_regex.py
@@ -0,0 +1,42 @@
+import tempfile
+from pathlib import Path
+
+from tealer.teal.instructions.instructions import Eq
+from tealer.teal.parse_teal import parse_teal
+from tealer.utils.regex.regex import parse_regex, match_regex, update_config
+from tealer.utils.output import CFGDotConfig, full_cfg_to_dot
+
+
+def test_parse_regex() -> None:
+    txt = """
+    some_label => 
+   gtxn 0 Amount
+   int 13337
+   =="""
+
+    regex = parse_regex(txt)
+    assert regex.label == "some_label"
+    assert len(regex.instructions) == 3
+    assert isinstance(regex.instructions[-1], Eq)
+
+
+def test_match() -> None:
+
+    with open("./tests/regex/vote_approval.teal", "r", encoding="utf8") as f_codebase:
+        teal = parse_teal(f_codebase.read())
+
+    with open("./tests/regex/regex.txt", "r", encoding="utf8") as f_regex:
+        regex = parse_regex(f_regex.read())
+
+    matches, covered = match_regex(teal, regex)
+
+    assert len(matches) == 1
+
+    assert len(covered) == 21
+
+    # Ensure the CFG generation does not crash
+    config = CFGDotConfig()
+    update_config(config, matches, covered)
+
+    with tempfile.NamedTemporaryFile() as fp:
+        full_cfg_to_dot(teal, config, Path(fp.name))
diff --git a/tests/test_subroutine_identification.py b/tests/test_subroutine_identification.py
index 97b1a83..05c9415 100644
--- a/tests/test_subroutine_identification.py
+++ b/tests/test_subroutine_identification.py
@@ -51,7 +51,7 @@
 ]
 
 ins_partitions = [(0, 2), (2, 5), (5, 9), (9, 10), (10, 11), (11, 14), (14, 17), (17, 18)]
-bbs_links = [(0, 6), (6, 2), (2, 3), (2, 5), (3, 1), (1, 4), (4, 7), (5, 7)]
+bbs_links = [(0, 6), (2, 3), (2, 5), (3, 4), (6, 7)]
 
 bbs = construct_cfg(ins_list, ins_partitions, bbs_links)
 MULTIPLE_RETSUB_MAIN = Subroutine("", bbs[0], [bbs[0], bbs[6], bbs[7]])
@@ -91,7 +91,7 @@
 ]
 
 ins_partitions = [(0, 2), (2, 5), (5, 8), (8, 11), (11, 12)]
-bbs_links = [(0, 3), (3, 2), (2, 1), (1, 4)]
+bbs_links = [(0, 3), (3, 4), (2, 1)]
 
 bbs = construct_cfg(ins_list, ins_partitions, bbs_links)
 SUBROUTINE_BACK_JUMP_MAIN = Subroutine("", bbs[0], [bbs[0], bbs[3], bbs[4]])
diff --git a/tests/test_versions.py b/tests/test_versions.py
index b2c48a0..17e6849 100644
--- a/tests/test_versions.py
+++ b/tests/test_versions.py
@@ -3,7 +3,7 @@
 
 from tealer.teal.parse_teal import parse_teal
 from tealer.teal.instructions.transaction_field import TransactionField
-from tealer.teal.instructions.instructions import ContractType
+from tealer.utils.teal_enums import ExecutionMode
 
 
 INSTRUCTIONS_TARGETS = [
@@ -31,9 +31,9 @@ def test_instruction_version(target: str) -> None:
             continue
         version, mode_str, pop_size, push_size = match_obj.groups()
         mode = {
-            "Any": ContractType.ANY,
-            "Stateful": ContractType.STATEFULL,
-            "Stateless": ContractType.STATELESS,
+            "Any": ExecutionMode.ANY,
+            "Stateful": ExecutionMode.STATEFUL,
+            "Stateless": ExecutionMode.STATELESS,
         }[mode_str]
         assert ins.mode == mode
         assert ins.version == int(version)
diff --git a/tests/transaction_context/test_addr_fields.py b/tests/transaction_context/test_addr_fields.py
index 6a7270b..80a6814 100644
--- a/tests/transaction_context/test_addr_fields.py
+++ b/tests/transaction_context/test_addr_fields.py
@@ -1,7 +1,7 @@
 from typing import Tuple, List
 import pytest
 
-from tealer.teal.parse_teal import parse_teal
+from tealer.utils.command_line.common import init_tealer_from_single_contract
 from tests.utils import order_basic_blocks
 
 
@@ -101,18 +101,18 @@ def test_addr_fields(  # pylint: disable=too-many-locals
     test: Tuple[str, List[Tuple[List[bool], List[bool], List[List[str]]]], int]
 ) -> None:
     code, values, idx = test
-    teal = parse_teal(code.strip())
-
+    tealer = init_tealer_from_single_contract(code.strip(), "test")
+    function = tealer.contracts["test"].functions["test"]
     ex_rekeyto_any, ex_rekeyto_none, ex_rekeyto = values[0]
     ex_closeto_any, ex_closeto_none, ex_closeto = values[1]
     ex_assetcloseto_any, ex_assetcloseto_none, ex_assetcloseto = values[2]
 
-    bbs = order_basic_blocks(teal.bbs)
+    bbs = order_basic_blocks(function.blocks)
     for b in bbs:
         if idx == -1:
-            ctx = b.transaction_context
+            ctx = function.transaction_context(b)
         else:
-            ctx = b.transaction_context.gtxn_context(idx)
+            ctx = function.transaction_context(b).gtxn_context(idx)
 
         assert ctx.rekeyto.any_addr == ex_rekeyto_any[b.idx]
         assert ctx.rekeyto.no_addr == ex_rekeyto_none[b.idx]
diff --git a/tests/transaction_context/test_fee.py b/tests/transaction_context/test_fee.py
index 95e8100..e3af27f 100644
--- a/tests/transaction_context/test_fee.py
+++ b/tests/transaction_context/test_fee.py
@@ -1,7 +1,7 @@
 from typing import List, Tuple, Union
 import pytest
 
-from tealer.teal.parse_teal import parse_teal
+from tealer.utils.command_line.common import init_tealer_from_single_contract
 from tealer.utils.algorand_constants import MAX_UINT64, MAX_GROUP_SIZE
 
 from tests.utils import order_basic_blocks
@@ -205,6 +205,52 @@
     retsub
 """
 
+"""
+#pragma version 6
+txn Fee
+int 10000
+>=
+bnz fail_txn
+callsub validate_fee
+callsub perform_validations
+int 1 return
+
+fail_txn:
+    int 0
+    return
+
+perform_validations:
+    b fail_txn
+
+validate_fee:
+    global GroupSize
+    int 2
+    ==
+    bnz group_size_2
+
+    global GroupSize
+    int 1
+    ==
+    assert
+    gtxn 0 Fee
+    int 10000
+    <=
+    assert
+    retsub
+
+    group_size_2:
+        gtxn 0 Fee
+        int 1000
+        <
+        assert
+        gtxn 1 Fee
+        int 1000
+        <
+        bz fail_txn
+
+    retsub
+"""
+
 txn_fees = [9999] * 10
 txn_fees[4] = 0  # fail_txn block
 gtxn_fees = [
@@ -391,7 +437,7 @@
     (NO_CHECKS, *NO_CHECKS_LISTS),
     (GTXN_0, *GTXN_0_LISTS),
     (CHECK_IN_SUBROUTINE, *CHECK_IN_SUBROUTINE_LISTS),
-    (MULTIPLE_GROUP_SIZES_IN_SUBROUTINES, *MULTIPLE_GROUP_SIZES_IN_SUBROUTINES_LISTS),
+    # (MULTIPLE_GROUP_SIZES_IN_SUBROUTINES, *MULTIPLE_GROUP_SIZES_IN_SUBROUTINES_LISTS),
     (CHECKS_IN_MULTIPLE_SUBROUTINES, *CHECKS_IN_MULTIPLE_SUBROUTINES_LISTS),
     (CHECK_IN_ONE_PATH, *CHECK_IN_ONE_PATH_LISTS),
     (BASIC_2, *BASIC_2_LISTS),
@@ -405,17 +451,18 @@ def test_tx_types_gtxn(
 ) -> None:
     code, max_fees_list, gtxn_fees_list = test
 
-    teal = parse_teal(code.strip())
+    tealer = init_tealer_from_single_contract(code.strip(), "test")
+    function = tealer.contracts["test"].functions["test"]
 
-    bbs = order_basic_blocks(teal.bbs)
+    bbs = order_basic_blocks(function.blocks)
     for i, b in enumerate(bbs):
-        print("X:", i, repr(b), b.transaction_context.max_fee)
+        print("X:", i, repr(b), function.transaction_context(b).max_fee)
         if isinstance(max_fees_list[i], bool):
-            assert b.transaction_context.max_fee_unknown is True
+            assert function.transaction_context(b).max_fee_unknown is True
         else:
-            assert b.transaction_context.max_fee == max_fees_list[i]
+            assert function.transaction_context(b).max_fee == max_fees_list[i]
         for ind in range(MAX_GROUP_SIZE):
-            ctx = b.transaction_context.gtxn_context(ind)
+            ctx = function.transaction_context(b).gtxn_context(ind)
             print(f"Y: {ind}, {i}", ctx.max_fee, gtxn_fees_list[ind][i])
             if isinstance(gtxn_fees_list[ind][i], bool):
                 assert ctx.max_fee_unknown is True
diff --git a/tests/transaction_context/test_group_indices.py b/tests/transaction_context/test_group_indices.py
index 69ef25d..50cc01b 100644
--- a/tests/transaction_context/test_group_indices.py
+++ b/tests/transaction_context/test_group_indices.py
@@ -1,7 +1,7 @@
 from typing import List, Tuple
 import pytest
 
-from tealer.teal.parse_teal import parse_teal
+from tealer.utils.command_line.common import init_tealer_from_single_contract
 from tests.utils import order_basic_blocks
 
 
@@ -181,8 +181,9 @@
 @pytest.mark.parametrize("test", ALL_TESTS)  # type: ignore
 def test_group_indices(test: Tuple[str, List[List[int]]]) -> None:
     code, group_indices = test
-    teal = parse_teal(code.strip())
+    tealer = init_tealer_from_single_contract(code.strip(), "test")
+    function = tealer.contracts["test"].functions["test"]
 
-    bbs = order_basic_blocks(teal.bbs)
+    bbs = order_basic_blocks(function.blocks)
     for b, indices in zip(bbs, group_indices):
-        assert b.transaction_context.group_indices == indices
+        assert function.transaction_context(b).group_indices == indices
diff --git a/tests/transaction_context/test_group_sizes.py b/tests/transaction_context/test_group_sizes.py
index b1d3c37..25c605a 100644
--- a/tests/transaction_context/test_group_sizes.py
+++ b/tests/transaction_context/test_group_sizes.py
@@ -1,4 +1,4 @@
-from typing import List, Tuple
+from typing import List, Tuple, Dict
 import pytest
 
 
@@ -6,7 +6,7 @@
 from tealer.teal.instructions import instructions
 from tealer.teal.instructions import transaction_field
 from tealer.teal import global_field
-from tealer.teal.parse_teal import parse_teal
+from tealer.utils.command_line.common import init_tealer_from_single_contract
 
 from tests.utils import cmp_cfg, construct_cfg, order_basic_blocks
 
@@ -72,7 +72,7 @@
 ]
 
 ins_partitions = [(0, 2), (2, 6), (6, 12), (12, 19), (19, 26), (26, 27)]
-bbs_links = [(0, 4), (4, 1), (1, 2), (1, 3), (2, 5), (3, 5)]
+bbs_links = [(0, 4), (4, 5), (1, 2), (1, 3)]
 
 MULTIPLE_RETSUB_CFG_GROUP_SIZES = [[2], [2], [2], [2], [2], [2]]
 
@@ -126,7 +126,7 @@
 ]
 
 ins_partitions = [(0, 2), (2, 5), (5, 16), (16, 19), (19, 20)]
-bbs_links = [(0, 3), (3, 2), (2, 1), (1, 4)]
+bbs_links = [(0, 3), (3, 4), (2, 1)]
 
 SUBROUTINE_BACK_JUMP_CFG_GROUP_SIZES = [[1, 3], [1, 3], [1, 3], [1, 3], [1, 3], [1, 3]]
 SUBROUTINE_BACK_JUMP_CFG = construct_cfg(ins_list, ins_partitions, bbs_links)
@@ -266,34 +266,38 @@
     (LOOPS_CFG, LOOPS_CFG_GROUP_SIZES),
 ]
 
+transaction_contexts = []
 for cfg, sizes in cfg_group_sizes:
     bb = order_basic_blocks(cfg)
+    transaction_context = {}
     for b, group_sizes in zip(bb, sizes):  # type: ignore
-        b.transaction_context.group_sizes = group_sizes
-
+        transaction_context[b] = group_sizes
+    transaction_contexts.append(transaction_context)
 
 ALL_TESTS = [
-    (MULTIPLE_RETSUB, MULTIPLE_RETSUB_CFG),
-    (SUBROUTINE_BACK_JUMP, SUBROUTINE_BACK_JUMP_CFG),
-    (BRANCHING, BRANCHING_CFG),
-    (LOOPS, LOOPS_CFG),
+    (MULTIPLE_RETSUB, MULTIPLE_RETSUB_CFG, transaction_contexts[0]),
+    (SUBROUTINE_BACK_JUMP, SUBROUTINE_BACK_JUMP_CFG, transaction_contexts[1]),
+    (BRANCHING, BRANCHING_CFG, transaction_contexts[2]),
+    (LOOPS, LOOPS_CFG, transaction_contexts[3]),
 ]
 
 
 @pytest.mark.parametrize("test", ALL_TESTS)  # type: ignore
-def test_group_sizes(test: Tuple[str, List[BasicBlock]]) -> None:
-    code, cfg_tested = test
-    teal = parse_teal(code.strip())
+def test_group_sizes(test: Tuple[str, List[BasicBlock], Dict]) -> None:
+    code, cfg_tested, cfg_tested_txn_context = test
+
+    tealer = init_tealer_from_single_contract(code.strip(), "test")
+    function = tealer.contracts["test"].functions["test"]
     for bb_tested in cfg_tested:
         print(bb_tested)
     print("*" * 20)
-    assert cmp_cfg(teal.bbs, cfg_tested)
+    assert cmp_cfg(function.blocks, cfg_tested)
 
-    bbs = order_basic_blocks(teal.bbs)
+    bbs = order_basic_blocks(function.blocks)
     cfg_tested = order_basic_blocks(cfg_tested)
     for b1, b2 in zip(bbs, cfg_tested):
-        print(b1.transaction_context.group_sizes, b2.transaction_context.group_sizes)
-        assert b1.transaction_context.group_sizes == b2.transaction_context.group_sizes
+        print(function.transaction_context(b1).group_sizes, cfg_tested_txn_context[b2])
+        assert function.transaction_context(b1).group_sizes == cfg_tested_txn_context[b2]
 
 
 MULTIPLE_RETSUB_CFG_GROUP_INDICES = [[0, 1], [0, 1], [0, 1], [0, 1], [0, 1], [0, 1]]
@@ -319,9 +323,10 @@ def test_group_sizes(test: Tuple[str, List[BasicBlock]]) -> None:
 @pytest.mark.parametrize("test", GROUP_INDICES_TESTS)  # type: ignore
 def test_group_indices(test: Tuple[str, List[List[int]]]) -> None:
     code, group_indices_list = test
-    teal = parse_teal(code.strip())
+    tealer = init_tealer_from_single_contract(code.strip(), "test")
+    function = tealer.contracts["test"].functions["test"]
 
-    bbs = order_basic_blocks(teal.bbs)
+    bbs = order_basic_blocks(function.blocks)
     for bb_tested, group_indices in zip(bbs, group_indices_list):
-        print(bb_tested.transaction_context.group_indices, group_indices)
-        assert bb_tested.transaction_context.group_indices == group_indices
+        print(function.transaction_context(bb_tested).group_indices, group_indices)
+        assert function.transaction_context(bb_tested).group_indices == group_indices
diff --git a/tests/transaction_context/test_transaction_types.py b/tests/transaction_context/test_transaction_types.py
index 3a5eff6..7f7a185 100644
--- a/tests/transaction_context/test_transaction_types.py
+++ b/tests/transaction_context/test_transaction_types.py
@@ -1,9 +1,9 @@
 from typing import List, Tuple
 import pytest
 
-from tealer.teal.parse_teal import parse_teal
 from tealer.utils.teal_enums import TealerTransactionType
 from tealer.utils.algorand_constants import MAX_GROUP_SIZE
+from tealer.utils.command_line.common import init_tealer_from_single_contract
 
 from tests.utils import order_basic_blocks
 
@@ -320,14 +320,16 @@
 @pytest.mark.parametrize("test", ALL_TESTS_TXN)  # type: ignore
 def test_tx_types(test: Tuple[str, List[List[int]], int]) -> None:
     code, tx_types_list, idx = test
-    teal = parse_teal(code.strip())
 
-    bbs = order_basic_blocks(teal.bbs)
+    tealer = init_tealer_from_single_contract(code.strip(), "test")
+    function = tealer.contracts["test"].functions["test"]
+
+    bbs = order_basic_blocks(function.blocks)
     for b, tx_types in zip(bbs, tx_types_list):
         if idx == -1:
-            ctx = b.transaction_context
+            ctx = function.transaction_context(b)
         else:
-            ctx = b.transaction_context.gtxn_context(idx)
+            ctx = function.transaction_context(b).gtxn_context(idx)
         assert set(ctx.transaction_types) == set(tx_types)
 
 
@@ -503,14 +505,18 @@ def test_tx_types_gtxn(
 ) -> None:
     code, ex_txn_types_list, ex_gtxn_types_list = test
 
-    teal = parse_teal(code.strip())
-    bbs = order_basic_blocks(teal.bbs)
+    tealer = init_tealer_from_single_contract(code.strip(), "test")
+    function = tealer.contracts["test"].functions["test"]
+
+    bbs = order_basic_blocks(function.blocks)
     print("number of blocks:", len(bbs))
     for block_num, b in enumerate(bbs):
         print(block_num, b)
-        assert set(b.transaction_context.transaction_types) == set(ex_txn_types_list[block_num])
+        assert set(function.transaction_context(b).transaction_types) == set(
+            ex_txn_types_list[block_num]
+        )
         print(block_num, b)
         for txn_ind in range(MAX_GROUP_SIZE):
-            ctx = b.transaction_context.gtxn_context(txn_ind)
+            ctx = function.transaction_context(b).gtxn_context(txn_ind)
             print("txn_ind =", txn_ind)
             assert set(ctx.transaction_types) == set(ex_gtxn_types_list[txn_ind][block_num])
diff --git a/tests/utils.py b/tests/utils.py
index 15584c2..2097751 100644
--- a/tests/utils.py
+++ b/tests/utils.py
@@ -15,10 +15,14 @@ def order_basic_blocks(bbs: List[BasicBlock]) -> List[BasicBlock]:
 
 def cmp_basic_blocks(b1: BasicBlock, b2: BasicBlock) -> bool:
     if len(b1.instructions) != len(b2.instructions):
+        print(
+            f"number of instructions are different: {len(b1.instructions)}, {len(b2.instructions)}"
+        )
         return False
 
     for ins1, ins2 in zip(b1.instructions, b2.instructions):
         if not cmp_instructions(ins1, ins2):
+            print(f"instructions comparison faile: {ins1}, {ins2}")
             return False
 
     return True
@@ -26,29 +30,39 @@ def cmp_basic_blocks(b1: BasicBlock, b2: BasicBlock) -> bool:
 
 def cmp_cfg(bbs1: List[BasicBlock], bbs2: List[BasicBlock]) -> bool:
     if len(bbs1) != len(bbs2):
+        print(f"number of blocks are different: {len(bbs1)}, {len(bbs2)}")
         return False
     bbs1, bbs2 = map(order_basic_blocks, (bbs1, bbs2))
 
     for bb1, bb2 in zip(bbs1, bbs2):
-
+        print(f"comparing bb1: {bb1.idx}, bb2: {bb2.idx}\nbb1:\n{bb1}\nbb2:\n{bb2}\n")
         if not cmp_basic_blocks(bb1, bb2):
             return False
 
         if len(bb1.prev) != len(bb2.prev):
+            print(f"number of prev blocks are different: {len(bb1.prev)}, {len(bb2.prev)}")
+            print(bb1.prev)
+            print(bb2.prev)
             return False
 
         if len(bb1.next) != len(bb2.next):
+            print(f"number of next blocks are different: {len(bb1.next)}, {len(bb2.next)}")
+            print(bb1.next)
+            print(bb2.next)
             return False
 
         bbs_prev_1, bbs_prev_2 = map(order_basic_blocks, (bb1.prev, bb2.prev))
-
         for bbp1, bbp2 in zip(bbs_prev_1, bbs_prev_2):
             if bbp1.entry_instr.line != bbp2.entry_instr.line:
+                print("entry lines of previous blocks is different")
+                print(bbp1.entry_instr.line, bbp2.entry_instr.line)
                 return False
 
         bbs_next_1, bbs_next_2 = map(order_basic_blocks, (bb1.next, bb2.next))
         for bbn1, bbn2 in zip(bbs_next_1, bbs_next_2):
             if bbn1.entry_instr.line != bbn2.entry_instr.line:
+                print("entry lines of next blocks is different")
+                print(bbn1.entry_instr.line, bbn2.entry_instr.line)
                 return False
 
     return True
@@ -65,16 +79,16 @@ def construct_cfg(
     and their relation.
 
     Args:
-            ins_list (List[Instruction]): list of instructions of the program.
-            ins_partitions (List[Tuple[int]]): list of tuple indicating starting and
-                    ending index into ins_list where ins_list[starting: ending] belong to a
-                    single basic_block.
-            bbs_links (List[Tuple[int]]): links two basic blocks (b1, b2). indexes of basic block
-                    corresponds to index in ins_partition. order of indexes is important as this adds
-                    a directed edge from b1 to b2.
+        ins_list: list of instructions of the program.
+        ins_partitions (List[Tuple[int]]): list of tuple indicating starting and
+                ending index into ins_list where ins_list[starting: ending] belong to a
+                single basic_block.
+        bbs_links (List[Tuple[int]]): links two basic blocks (b1, b2). indexes of basic block
+                corresponds to index in ins_partition. order of indexes is important as this adds
+                a directed edge from b1 to b2.
 
     Returns:
-            List[BasicBlock]: returns a list of basic blocks representing the CFG.
+        List[BasicBlock]: returns a list of basic blocks representing the CFG.
     """
     for idx, ins in enumerate(ins_list, start=1):
         ins.line = idx