Repository: algorand/pyteal (language: python)

Issue:
Get current contract address
Is there a way to get the current address of the contract from pyteal? If there's not I think it would be a pretty useful feature to have


Discussion / hints:
For a LogicSig contract, `Txn.sender()` will be the address of the contract, with one exception.

The one situation in which `Txn.sender()` will not be the contract's address is if another account, let's say account `B`, rekeys to the contract address. Then a transaction could originate from account `B`, but the LogicSig is responsible for verifying it, so `Txn.sender()` will be the address of `B`.

In most cases you can probably ignore this though, since if your program approves the transaction, it affects the account `B`, not your program's account.
That's fair! Thanks for the clarification... but in case I want to send some fund or asa to the fund the lsig, isn't it a transaction to the lsig from my address?
When a transaction sends funds or assets to a LogicSig account, the LogicSig program does not execute at all. The program only executes when a transaction originates from its address. I hope that helps!
Oh sorry! I'm still fairly new at this concepts, this really helps!

Fix patch:
diff --git a/requirements.txt b/requirements.txt
index 5ff207492..609ac6e51 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,7 +1,7 @@
 black==22.3.0
 flake8==5.0.4
 flake8-tidy-imports==4.6.0
-graviton@git+https://github.com/algorand/graviton@v0.5.0
+graviton@git+https://github.com/algorand/graviton@v0.7.0
 mypy==0.991
 pytest==7.2.0
 pytest-cov==3.0.0


Test patch:
diff --git a/tests/blackbox.py b/tests/blackbox.py
index db5eb97b3..50e169342 100644
--- a/tests/blackbox.py
+++ b/tests/blackbox.py
@@ -1,4 +1,4 @@
-from typing import Callable, Generic, Sequence, TypeVar, cast
+from typing import Any, Callable, Generic, Optional, Sequence, TypeVar, cast
 from dataclasses import dataclass
 
 import algosdk.abi
@@ -144,8 +144,14 @@ def decorator_blackbox(func: SubroutineFnWrapper | ABIReturnSubroutine):
 
 @dataclass(frozen=True)
 class _MatchMode(Generic[Output]):
+    runner: Optional["PyTealDryRunExecutor"]
     app_case: Lazy
     signature_case: Lazy
+    trace: Any = None
+
+    def __post_init__(self):
+        if self.runner and self.trace:
+            self.runner.add_trace(self.trace)
 
     def __call__(self, mode: Mode, *args, **kwargs) -> Output:
         match mode:
@@ -159,6 +165,7 @@ def __call__(self, mode: Mode, *args, **kwargs) -> Output:
 
 def mode_to_execution_mode(mode: Mode) -> blackbox.ExecutionMode:
     return _MatchMode(
+        runner=None,
         app_case=lambda: blackbox.ExecutionMode.Application,
         signature_case=lambda: blackbox.ExecutionMode.Signature,
     )(mode)
@@ -192,9 +199,31 @@ def __init__(self, subr: BlackboxWrapper, mode: Mode):
 
         self._pyteal_lambda: Callable[..., Expr] = approval
 
+        self.traces: list = []
+
+    def add_trace(self, trace: Any) -> None:
+        self.traces.append(trace)
+
     def is_abi(self) -> bool:
         return isinstance(self.subr.subroutine, ABIReturnSubroutine)
 
+    def abi_method_signature(self) -> None | str:
+        if self.is_abi():
+            abi_subr = cast(ABIReturnSubroutine, self.subr.subroutine)
+            return abi_subr.method_signature()
+
+        # create an artificial method signature
+        # based on the `abi_argument_types()` and `abi_return_type()`
+        if arg_types := self.abi_argument_types():
+            if all(t is None for t in arg_types):
+                return None
+
+            ret_type = self.abi_return_type()
+            ret = str(ret_type) if ret_type else "void"
+            return f"ptdre_foo({','.join(map(str, arg_types))}){ret}"
+
+        return None
+
     def abi_argument_types(self) -> None | list[algosdk.abi.ABIType]:
         if not (self.input_types or self.is_abi()):
             return None
@@ -389,6 +418,7 @@ def approval():
 
     def compile(self, version: int, assemble_constants: bool = False) -> str:
         return _MatchMode(
+            runner=self,
             app_case=lambda: compileTeal(
                 self.program(),
                 self.mode,
@@ -408,21 +438,24 @@ def dryrun_on_sequence(
         inputs: list[Sequence[PyTypes]],
         compiler_version=6,
     ) -> list[DryRunInspector]:
+        teal = self.compile(compiler_version)
         return _MatchMode(
+            self,
             app_case=lambda: DryRunExecutor.dryrun_app_on_sequence(
                 algod=algod_with_assertion(),
-                teal=self.compile(compiler_version),
+                teal=teal,
                 inputs=inputs,
-                abi_argument_types=self.abi_argument_types(),
-                abi_return_type=self.abi_return_type(),
+                abi_method_signature=self.abi_method_signature(),
+                omit_method_selector=True,
             ),
             signature_case=lambda: DryRunExecutor.dryrun_logicsig_on_sequence(
                 algod=algod_with_assertion(),
-                teal=self.compile(compiler_version),
+                teal=teal,
                 inputs=inputs,
-                abi_argument_types=self.abi_argument_types(),
-                abi_return_type=self.abi_return_type(),
+                abi_method_signature=self.abi_method_signature(),
+                omit_method_selector=True,
             ),
+            trace=teal,
         )(self.mode)
 
     def dryrun(
@@ -430,19 +463,22 @@ def dryrun(
         args: Sequence[bytes | str | int],
         compiler_version=6,
     ) -> DryRunInspector:
+        teal = self.compile(compiler_version)
         return _MatchMode(
+            self,
             app_case=lambda: DryRunExecutor.dryrun_app(
                 algod_with_assertion(),
-                self.compile(compiler_version),
+                teal,
                 args,
-                self.abi_argument_types(),
-                self.abi_return_type(),
+                abi_method_signature=self.abi_method_signature(),
+                omit_method_selector=True,
             ),
             signature_case=lambda: DryRunExecutor.dryrun_logicsig(
                 algod_with_assertion(),
-                self.compile(compiler_version),
+                teal,
                 args,
-                self.abi_argument_types(),
-                self.abi_return_type(),
+                abi_method_signature=self.abi_method_signature(),
+                omit_method_selector=True,
             ),
+            trace=teal,
         )(self.mode)
diff --git a/tests/integration/graviton_abi_test.py b/tests/integration/graviton_abi_test.py
index 0806ee96e..cdd6d417b 100644
--- a/tests/integration/graviton_abi_test.py
+++ b/tests/integration/graviton_abi_test.py
@@ -312,7 +312,7 @@ def test_integer65(version: int):
 
     bbpt_add = PyTealDryRunExecutor(int65_add, pt.Mode.Application)
 
-    def pynum_to_tuple(n):
+    def pynum_to_int65tuple(n):
         return (n >= 0, abs(n))
 
     def pytuple_to_num(t):
@@ -323,10 +323,10 @@ def pytuple_to_num(t):
     random.seed(42)
 
     choices = range(-9_999, 10_000)
-    unary_inputs = [(pynum_to_tuple(x),) for x in random.sample(choices, N)]
+    unary_inputs = [(pynum_to_int65tuple(x),) for x in random.sample(choices, N)]
 
     binary_inputs = [
-        (pynum_to_tuple(x), pynum_to_tuple(y))
+        (pynum_to_int65tuple(x), pynum_to_int65tuple(y))
         for x, y in zip(random.sample(choices, N), random.sample(choices, N))
     ]
 
diff --git a/tests/integration/graviton_test.py b/tests/integration/graviton_test.py
index 31f77bb04..d69a59037 100644
--- a/tests/integration/graviton_test.py
+++ b/tests/integration/graviton_test.py
@@ -23,7 +23,7 @@
     mode_has_property,
 )
 
-from graviton.invariant import Invariant
+from graviton.invariant import Invariant, PredicateKind
 
 PATH = Path.cwd() / "tests" / "integration"
 FIXTURES = PATH / "teal"
@@ -137,13 +137,6 @@ def fib(n):
     return a
 
 
-def fib_cost(args):
-    cost = 17
-    for n in range(1, args[0] + 1):
-        cost += 31 * fib(n - 1)
-    return cost
-
-
 # ---- Blackbox pure unit tests (Skipping for now due to flakiness) ---- #
 
 
@@ -163,6 +156,8 @@ def test_stable_teal_generation(subr, mode):
     case_name = subr.name()
     print(f"stable TEAL generation test for {case_name} in mode {mode}")
 
+    # HANG NOTE: I prefer not to modify this test, for it is skipped now on thread-unsafe behavior,
+    # and I would suggest revisiting later after we have satisfied solution for #199.
     _, _, tealfile = wrap_compile_and_save(subr, mode, 6, True, "stability", case_name)
     path2actual = GENERATED / "stability" / tealfile
     path2expected = FIXTURES / "stability" / tealfile
@@ -174,7 +169,7 @@ def test_stable_teal_generation(subr, mode):
         "inputs": [()],
         # since only a single input, just assert a constant in each case
         "assertions": {
-            DRProp.cost: 11,
+            DRProp.cost: lambda _, actual: actual in {11, 12},
             # int assertions on log outputs need encoding to varuint-hex:
             DRProp.lastLog: Encoder.hex(2**10),
             # dicts have a special meaning as assertions. So in the case of "finalScratch"
@@ -191,7 +186,7 @@ def test_stable_teal_generation(subr, mode):
     square_byref: {
         "inputs": [(i,) for i in range(100)],
         "assertions": {
-            DRProp.cost: lambda _, actual: 20 < actual < 22,
+            DRProp.cost: lambda _, actual: 20 < actual < 24,
             DRProp.lastLog: Encoder.hex(1337),
             # due to dry-run artifact of not reporting 0-valued scratchvars,
             # we have a special case for n=0:
@@ -199,7 +194,7 @@ def test_stable_teal_generation(subr, mode):
                 {1, 1337, (args[0] ** 2 if args[0] else 1)}
             ).issubset(set(actual.values())),
             DRProp.stackTop: 1337,
-            DRProp.maxStackHeight: 3,
+            DRProp.maxStackHeight: lambda _, actual: actual in [3, 4],
             DRProp.status: "PASS",
             DRProp.passed: True,
             DRProp.rejected: False,
@@ -212,14 +207,14 @@ def test_stable_teal_generation(subr, mode):
             DRProp.cost: 14,
             DRProp.lastLog: {
                 # since execution REJECTS for 0, expect last log for this case to be None
-                (i,): Encoder.hex(i * i) if i else None
+                (i,): Encoder.hex(i * i)
                 for i in range(100)
             },
             DRProp.finalScratch: lambda args: (
                 {0: args[0] ** 2, 1: args[0]} if args[0] else {}
             ),
             DRProp.stackTop: lambda args: args[0] ** 2,
-            DRProp.maxStackHeight: 2,
+            DRProp.maxStackHeight: lambda _, actual: actual in range(2, 5),
             DRProp.status: lambda i: "PASS" if i[0] > 0 else "REJECT",
             DRProp.passed: lambda i: i[0] > 0,
             DRProp.rejected: lambda i: i[0] == 0,
@@ -229,7 +224,7 @@ def test_stable_teal_generation(subr, mode):
     swap: {
         "inputs": [(1, 2), (1, "two"), ("one", 2), ("one", "two")],
         "assertions": {
-            DRProp.cost: 27,
+            DRProp.cost: lambda _, actual: actual in [27, 30],
             DRProp.lastLog: Encoder.hex(1337),
             DRProp.finalScratch: lambda args: {
                 0: 1337,
@@ -240,7 +235,7 @@ def test_stable_teal_generation(subr, mode):
                 5: Encoder.hex0x(args[0]),
             },
             DRProp.stackTop: 1337,
-            DRProp.maxStackHeight: 2,
+            DRProp.maxStackHeight: lambda _, actual: actual in [2, 4],
             DRProp.status: "PASS",
             DRProp.passed: True,
             DRProp.rejected: False,
@@ -250,10 +245,7 @@ def test_stable_teal_generation(subr, mode):
     string_mult: {
         "inputs": [("xyzw", i) for i in range(100)],
         "assertions": {
-            DRProp.cost: lambda args: 30 + 15 * args[1],
-            DRProp.lastLog: (
-                lambda args: Encoder.hex(args[0] * args[1]) if args[1] else None
-            ),
+            DRProp.lastLog: (lambda args: Encoder.hex(args[0] * args[1])),
             # due to dryrun 0-scratchvar artifact, special case for i == 0:
             DRProp.finalScratch: lambda args: (
                 {
@@ -272,7 +264,6 @@ def test_stable_teal_generation(subr, mode):
                 }
             ),
             DRProp.stackTop: lambda args: len(args[0] * args[1]),
-            DRProp.maxStackHeight: lambda args: 3 if args[1] else 2,
             DRProp.status: lambda args: ("PASS" if 0 < args[1] < 45 else "REJECT"),
             DRProp.passed: lambda args: 0 < args[1] < 45,
             DRProp.rejected: lambda args: 0 >= args[1] or args[1] >= 45,
@@ -282,9 +273,6 @@ def test_stable_teal_generation(subr, mode):
     oldfac: {
         "inputs": [(i,) for i in range(25)],
         "assertions": {
-            DRProp.cost: lambda args, actual: (
-                actual - 40 <= 17 * args[0] <= actual + 40
-            ),
             DRProp.lastLog: lambda args: (
                 Encoder.hex(fac_with_overflow(args[0])) if args[0] < 21 else None
             ),
@@ -298,7 +286,6 @@ def test_stable_teal_generation(subr, mode):
                 )
             ),
             DRProp.stackTop: lambda args: fac_with_overflow(args[0]),
-            DRProp.maxStackHeight: lambda args: max(2, 2 * args[0]),
             DRProp.status: lambda args: "PASS" if args[0] < 21 else "REJECT",
             DRProp.passed: lambda args: args[0] < 21,
             DRProp.rejected: lambda args: args[0] >= 21,
@@ -308,11 +295,10 @@ def test_stable_teal_generation(subr, mode):
         },
     },
     slow_fibonacci: {
-        "inputs": [(i,) for i in range(18)],
+        "inputs": [(i,) for i in range(17)],
         "assertions": {
-            DRProp.cost: lambda args: (fib_cost(args) if args[0] < 17 else 70_000),
-            DRProp.lastLog: lambda args: (
-                Encoder.hex(fib(args[0])) if 0 < args[0] < 17 else None
+            DRProp.lastLog: lambda args, actual: (
+                Encoder.hex(fib(args[0])) == actual if 0 <= args[0] < 17 else True
             ),
             DRProp.finalScratch: lambda args, actual: (
                 actual == {1: args[0], 0: fib(args[0])}
@@ -323,10 +309,6 @@ def test_stable_teal_generation(subr, mode):
             DRProp.stackTop: lambda args, actual: (
                 actual == fib(args[0]) if args[0] < 17 else True
             ),
-            # similarly, we don't care about max stack height for n >= 17
-            DRProp.maxStackHeight: lambda args, actual: (
-                actual == max(2, 2 * args[0]) if args[0] < 17 else True
-            ),
             DRProp.status: lambda args: "PASS" if 0 < args[0] < 8 else "REJECT",
             DRProp.passed: lambda args: 0 < args[0] < 8,
             DRProp.rejected: lambda args: 0 >= args[0] or args[0] >= 8,
@@ -359,15 +341,13 @@ def test_stable_teal_generation(subr, mode):
     square_byref: {
         "inputs": [(i,) for i in range(100)],
         "assertions": {
-            # DRProp.cost: lambda _, actual: 20 < actual < 22,
-            # DRProp.lastLog: Encoder.hex(1337),
             # due to dry-run artifact of not reporting 0-valued scratchvars,
             # we have a special case for n=0:
-            DRProp.finalScratch: lambda args: (
-                {0: 1, 1: args[0] ** 2} if args[0] else {0: 1}
-            ),
+            DRProp.finalScratch: lambda args, actual: (
+                {1, 1337, (args[0] ** 2 if args[0] else 1)}
+            ).issubset(set(actual.values())),
             DRProp.stackTop: 1337,
-            DRProp.maxStackHeight: 3,
+            DRProp.maxStackHeight: lambda _, actual: actual in [3, 4],
             DRProp.status: "PASS",
             DRProp.passed: True,
             DRProp.rejected: False,
@@ -377,11 +357,11 @@ def test_stable_teal_generation(subr, mode):
     square: {
         "inputs": [(i,) for i in range(100)],
         "assertions": {
-            # DRProp.cost: 14,
-            # DRProp.lastLog: {(i,): Encoder.hex(i * i) if i else None for i in range(100)},
-            DRProp.finalScratch: lambda args: ({0: args[0]} if args[0] else {}),
+            DRProp.finalScratch: lambda args: (
+                {0: args[0] ** 2, 1: args[0]} if args[0] else {}
+            ),
             DRProp.stackTop: lambda args: args[0] ** 2,
-            DRProp.maxStackHeight: 2,
+            DRProp.maxStackHeight: lambda _, actual: actual in range(2, 5),
             DRProp.status: lambda i: "PASS" if i[0] > 0 else "REJECT",
             DRProp.passed: lambda i: i[0] > 0,
             DRProp.rejected: lambda i: i[0] == 0,
@@ -391,17 +371,16 @@ def test_stable_teal_generation(subr, mode):
     swap: {
         "inputs": [(1, 2), (1, "two"), ("one", 2), ("one", "two")],
         "assertions": {
-            # DRProp.cost: 27,
-            # DRProp.lastLog: Encoder.hex(1337),
             DRProp.finalScratch: lambda args: {
-                0: 3,
-                1: 4,
+                0: 1337,
+                1: Encoder.hex0x(args[1]),
                 2: Encoder.hex0x(args[0]),
-                3: Encoder.hex0x(args[1]),
-                4: Encoder.hex0x(args[0]),
+                3: 1,
+                4: 2,
+                5: Encoder.hex0x(args[0]),
             },
             DRProp.stackTop: 1337,
-            DRProp.maxStackHeight: 2,
+            DRProp.maxStackHeight: lambda _, actual: actual in [2, 4],
             DRProp.status: "PASS",
             DRProp.passed: True,
             DRProp.rejected: False,
@@ -411,25 +390,24 @@ def test_stable_teal_generation(subr, mode):
     string_mult: {
         "inputs": [("xyzw", i) for i in range(100)],
         "assertions": {
-            # DRProp.cost: lambda args: 30 + 15 * args[1],
-            # DRProp.lastLog: lambda args: Encoder.hex(args[0] * args[1]) if args[1] else None,
+            # due to dryrun 0-scratchvar artifact, special case for i == 0:
             DRProp.finalScratch: lambda args: (
                 {
-                    0: len(args[0]),
-                    1: args[1],
-                    2: args[1] + 1,
-                    3: Encoder.hex0x(args[0]),
-                    4: Encoder.hex0x(args[0] * args[1]),
+                    0: Encoder.hex0x(args[0] * args[1]),
+                    1: Encoder.hex0x(args[0] * args[1]),
+                    2: 1,
+                    3: args[1],
+                    4: args[1] + 1,
+                    5: Encoder.hex0x(args[0]),
                 }
                 if args[1]
                 else {
-                    0: len(args[0]),
-                    2: args[1] + 1,
-                    3: Encoder.hex0x(args[0]),
+                    2: 1,
+                    4: args[1] + 1,
+                    5: Encoder.hex0x(args[0]),
                 }
             ),
             DRProp.stackTop: lambda args: len(args[0] * args[1]),
-            DRProp.maxStackHeight: lambda args: 3 if args[1] else 2,
             DRProp.status: lambda args: "PASS" if args[1] else "REJECT",
             DRProp.passed: lambda args: bool(args[1]),
             DRProp.rejected: lambda args: not bool(args[1]),
@@ -439,40 +417,36 @@ def test_stable_teal_generation(subr, mode):
     oldfac: {
         "inputs": [(i,) for i in range(25)],
         "assertions": {
-            # DRProp.cost: lambda args, actual: actual - 40 <= 17 * args[0] <= actual + 40,
-            # DRProp.lastLog: lambda args, actual: (actual is None) or (int(actual, base=16) == fac_with_overflow(args[0])),
             DRProp.finalScratch: lambda args: (
-                {0: min(args[0], 21)} if args[0] else {}
+                {1: args[0], 0: fac_with_overflow(args[0])}
+                if 0 < args[0] < 21
+                else (
+                    {1: min(21, args[0])}
+                    if args[0]
+                    else {0: fac_with_overflow(args[0])}
+                )
             ),
             DRProp.stackTop: lambda args: fac_with_overflow(args[0]),
-            DRProp.maxStackHeight: lambda args: max(2, 2 * args[0]),
             DRProp.status: lambda args: "PASS" if args[0] < 21 else "REJECT",
             DRProp.passed: lambda args: args[0] < 21,
             DRProp.rejected: lambda args: args[0] >= 21,
             DRProp.errorMessage: lambda args, actual: (
-                actual is None
-                if args[0] < 21
-                else "logic 0 failed at line 21: * overflowed" in actual
+                actual is None if args[0] < 21 else "overflowed" in actual
             ),
         },
     },
     slow_fibonacci: {
-        "inputs": [(i,) for i in range(18)],
+        "inputs": [(i,) for i in range(17)],
         "assertions": {
-            # DRProp.cost: fib_cost,
-            # DRProp.lastLog: fib_last_log,
-            # by returning True for n >= 15, we're declaring that we don't care about the scratchvar's for such cases:
             DRProp.finalScratch: lambda args, actual: (
-                actual == {0: args[0]}
-                if 0 < args[0] < 15
-                else (True if args[0] else actual == {})
+                actual == {1: args[0], 0: fib(args[0])}
+                if 0 < args[0] < 17
+                else (True if args[0] >= 17 else actual == {})
             ),
+            # we declare to "not care" about the top of the stack for n >= 15
             DRProp.stackTop: lambda args, actual: (
                 actual == fib(args[0]) if args[0] < 15 else True
             ),
-            DRProp.maxStackHeight: lambda args, actual: (
-                actual == max(2, 2 * args[0]) if args[0] < 15 else True
-            ),
             DRProp.status: lambda args: "PASS" if 0 < args[0] < 15 else "REJECT",
             DRProp.passed: lambda args: 0 < args[0] < 15,
             DRProp.rejected: lambda args: not (0 < args[0] < 15),
@@ -485,6 +459,16 @@ def test_stable_teal_generation(subr, mode):
     },
 }
 
+APP_IDENTICAL_PREDICATES = {
+    DRProp.lastLog: PredicateKind.IdenticalPair,
+    DRProp.status: PredicateKind.IdenticalPair,
+    DRProp.error: PredicateKind.IdenticalPair,
+}
+LSIG_IDENTICAL_PREDICATES = {
+    DRProp.status: PredicateKind.IdenticalPair,
+    DRProp.error: PredicateKind.IdenticalPair,
+}
+
 
 def blackbox_test_runner(
     subr: pt.SubroutineFnWrapper,
@@ -492,7 +476,7 @@ def blackbox_test_runner(
     scenario: Dict[str, Any],
     version: int,
     assemble_constants: bool = True,
-):
+) -> list[DryRunInspector]:
     case_name = subr.name()
     print(f"blackbox test of {case_name} with mode {mode}")
     exec_mode = mode_to_execution_mode(mode)
@@ -510,9 +494,11 @@ def blackbox_test_runner(
     algod = algod_with_assertion()
 
     # 2. validate dry run scenarios:
-    inputs, predicates = Invariant.inputs_and_invariants(
-        scenario, exec_mode, raw_predicates=True
-    )
+    inputs = scenario["inputs"]
+    predicates = scenario["assertions"]
+    assert inputs and isinstance(inputs, list)
+    assert predicates, "Must configure >= 1 predicate"
+    assert isinstance(predicates, dict)
 
     # 3. execute dry run sequence:
     execute = DryRunExecutor.execute_one_dryrun
@@ -526,8 +512,7 @@ def blackbox_test_runner(
     print(f"Saved Dry Run CSV report to {csvpath}")
 
     # 5. Sequential assertions (if provided any)
-    for i, type_n_assertion in enumerate(predicates.items()):
-        dr_prop, predicate = type_n_assertion
+    for i, (dr_prop, predicate) in enumerate(predicates.items()):
 
         if SKIP_SCRATCH_ASSERTIONS and dr_prop == DRProp.finalScratch:
             print("skipping scratch assertions because unstable slots produced")
@@ -539,6 +524,8 @@ def blackbox_test_runner(
         print(f"{i+1}. Assertion for {case_name}-{mode}: {dr_prop} <<{predicate}>>")
         invariant.validates(dr_prop, inspectors)
 
+    return inspectors
+
 
 # ---- Graviton / Blackbox tests ---- #
 
@@ -548,8 +535,15 @@ def test_blackbox_subroutines_as_apps(
     subr: pt.SubroutineFnWrapper,
     scenario: Dict[str, Any],
 ):
-    blackbox_test_runner(subr, pt.Mode.Application, scenario, 6)
-    blackbox_test_runner(subr, pt.Mode.Application, scenario, 8)
+    inspectors6 = blackbox_test_runner(subr, pt.Mode.Application, scenario, 6)
+    inspectors8 = blackbox_test_runner(subr, pt.Mode.Application, scenario, 8)
+
+    Invariant.full_validation(
+        APP_IDENTICAL_PREDICATES,
+        inspectors=inspectors6,
+        identities=inspectors8,
+        msg=f"{subr.name()=}",
+    )
 
 
 @pytest.mark.parametrize("subr, scenario", LOGICSIG_SCENARIOS.items())
@@ -557,8 +551,14 @@ def test_blackbox_subroutines_as_logic_sigs(
     subr: pt.SubroutineFnWrapper,
     scenario: Dict[str, Any],
 ):
-    blackbox_test_runner(subr, pt.Mode.Signature, scenario, 6)
-    blackbox_test_runner(subr, pt.Mode.Signature, scenario, 8)
+    inspectors6 = blackbox_test_runner(subr, pt.Mode.Signature, scenario, 6)
+    inspectors8 = blackbox_test_runner(subr, pt.Mode.Signature, scenario, 8)
+    Invariant.full_validation(
+        LSIG_IDENTICAL_PREDICATES,
+        inspectors=inspectors6,
+        identities=inspectors8,
+        msg=f"{subr.name()=}",
+    )
 
 
 def blackbox_pyteal_example1():
@@ -599,8 +599,22 @@ def evaluate_and_check(version: int):
             args, "last_log() gave unexpected results from app"
         )
 
-    evaluate_and_check(6)
-    evaluate_and_check(8)
+        return app_result, lsig_result
+
+    app6, lsig6 = evaluate_and_check(6)
+    app8, lsig8 = evaluate_and_check(8)
+    Invariant.full_validation(
+        APP_IDENTICAL_PREDICATES,
+        inspectors=[app6],
+        identities=[app8],
+        msg="Mode.Application example 1",
+    )
+    Invariant.full_validation(
+        LSIG_IDENTICAL_PREDICATES,
+        inspectors=[lsig6],
+        identities=[lsig8],
+        msg="Mode.Signature example 1",
+    )
 
 
 def blackbox_pyteal_example2():
@@ -665,8 +679,16 @@ def test_and_report(version: int):
         with open(Path.cwd() / f"euclid_v{version}.csv", "w") as f:
             f.write(euclid_csv)
 
-    test_and_report(6)
-    test_and_report(8)
+        return inspectors
+
+    inspectors6 = test_and_report(6)
+    inspectors8 = test_and_report(8)
+    Invariant.full_validation(
+        APP_IDENTICAL_PREDICATES,
+        inspectors=inspectors6,
+        identities=inspectors8,
+        msg="example 2",
+    )
 
 
 def blackbox_pyteal_example3():
@@ -737,22 +759,40 @@ def euclid(x, y):
         )
 
     # Execute on the input sequence to get a dry-run inspectors:
-    inspectors = PyTealDryRunExecutor(euclid, Mode.Application).dryrun_on_sequence(
-        inputs
+    inspectors6 = PyTealDryRunExecutor(euclid, Mode.Application).dryrun_on_sequence(
+        inputs, compiler_version=6
     )
 
     # Assert that each invariant holds on the sequences of inputs and dry-runs:
     for property, predicate in predicates.items():
-        Invariant(predicate).validates(property, inspectors)
+        Invariant(predicate).validates(property, inspectors6)
 
     # Execute on the input sequence to get a dry-run inspectors:
-    inspectors = PyTealDryRunExecutor(euclid, Mode.Application).dryrun_on_sequence(
+    inspectors8 = PyTealDryRunExecutor(euclid, Mode.Application).dryrun_on_sequence(
         inputs, compiler_version=8
     )
 
     # Assert that each invariant holds on the sequences of inputs and dry-runs:
     for property, predicate in predicates.items():
-        Invariant(predicate).validates(property, inspectors)
+        Invariant(predicate).validates(property, inspectors8)
+
+    Invariant.full_validation(
+        APP_IDENTICAL_PREDICATES,
+        inspectors=inspectors6,
+        identities=inspectors8,
+        msg="Mode.Application example 3",
+    )
+
+    # Assert that each invariant holds on the sequences of inputs and dry-runs:
+    for property, predicate in predicates.items():
+        Invariant(predicate).validates(property, inspectors8)
+
+    Invariant.full_validation(
+        APP_IDENTICAL_PREDICATES,
+        inspectors=inspectors6,
+        identities=inspectors8,
+        msg="Mode.Application example 3",
+    )
 
 
 def blackbox_pyteal_example4():
@@ -854,8 +894,22 @@ def report(kind):
         report("app")
         report("lsig")
 
-    test_and_report_for_app_and_lsig(6)
-    test_and_report_for_app_and_lsig(8)
+        return app_inspectors, lsig_inspectors
+
+    app_inspectors6, lsig_inspectors6 = test_and_report_for_app_and_lsig(6)
+    app_inspectors8, lsig_inspectors8 = test_and_report_for_app_and_lsig(8)
+    Invariant.full_validation(
+        APP_IDENTICAL_PREDICATES,
+        inspectors=app_inspectors6,
+        identities=app_inspectors8,
+        msg=f"Mode.Application example 4 {abi_sum.name()=}",
+    )
+    Invariant.full_validation(
+        LSIG_IDENTICAL_PREDICATES,
+        inspectors=lsig_inspectors6,
+        identities=lsig_inspectors8,
+        msg=f"Mode.Signature example 4 {abi_sum.name()=}",
+    )
 
 
 def blackbox_pyteal_example5():
@@ -896,9 +950,22 @@ def test_app_and_lsig(_version: int):
             assert inspect.stack_top() == input_var**3, inspect.report(
                 args=inputs[index], msg="stack_top() gave unexpected results from app"
             )
-
-    test_app_and_lsig(6)
-    test_app_and_lsig(8)
+        return app_inspect, lsig_inspect
+
+    app_inspectors6, lsig_inspectors6 = test_app_and_lsig(6)
+    app_inspectors8, lsig_inspectors8 = test_app_and_lsig(8)
+    Invariant.full_validation(
+        APP_IDENTICAL_PREDICATES,
+        inspectors=app_inspectors6,
+        identities=app_inspectors8,
+        msg="Mode.Application example 5",
+    )
+    Invariant.full_validation(
+        LSIG_IDENTICAL_PREDICATES,
+        inspectors=lsig_inspectors6,
+        identities=lsig_inspectors8,
+        msg="Mode.Signature example 5",
+    )
 
 
 def blackbox_pyteal_while_continue_test():
@@ -1009,15 +1076,33 @@ def named_tuple_field_access(
     lsig_pytealer = PyTealDryRunExecutor(named_tuple_field_access, Mode.Signature)
     args = (False, b"1" * 32, (0, False), b"0" * 10, [True] * 4, 0)
 
-    inspector = lsig_pytealer.dryrun(args)
+    inspector6 = lsig_pytealer.dryrun(args, compiler_version=6)
 
-    assert inspector.stack_top() == 1
-    assert inspector.passed()
+    assert inspector6.stack_top() == 1
+    assert inspector6.passed()
+
+    inspector8 = lsig_pytealer.dryrun(args, compiler_version=8)
+    assert inspector8.passed()
+    assert inspector8.stack_top() == 1
+
+    Invariant.full_validation(
+        LSIG_IDENTICAL_PREDICATES,
+        inspectors=[inspector6],
+        identities=[inspector8],
+        msg="Mode.Signature NamedTuple example",
+    )
 
     inspector = lsig_pytealer.dryrun(args, compiler_version=8)
     assert inspector.passed()
     assert inspector.stack_top() == 1
 
+    Invariant.full_validation(
+        LSIG_IDENTICAL_PREDICATES,
+        inspectors=[inspector6],
+        identities=[inspector8],
+        msg="Mode.Signature NamedTuple example",
+    )
+
 
 @pytest.mark.parametrize(
     "example",
diff --git a/tests/integration/opup_test.py b/tests/integration/opup_test.py
index afdb82f1d..7c44cb5db 100644
--- a/tests/integration/opup_test.py
+++ b/tests/integration/opup_test.py
@@ -27,8 +27,6 @@ def _dryrun(
         e.compile(pt.compiler.MAX_PROGRAM_VERSION),
         [],
         ExecutionMode.Application,
-        e.abi_argument_types(),
-        e.abi_return_type(),
         txn_params=DryRunExecutor.transaction_params(
             sender=graviton.models.ZERO_ADDRESS,
             sp=sp,