Repository: algorand/pyteal

Issue:
Add support for dynamic gtxn indexes from TEAL 3
## Summary
TEAL 3 allows dynamic indexes to be used when referencing group transactions, so we should support that too.

## Scope
TODO

## Urgency
This would be nice to do before v0.7.0.

Discussion:

Patch:
diff --git a/README.md b/README.md
index 7aef08e..faad254 100644
--- a/README.md
+++ b/README.md
@@ -12,7 +12,7 @@ This a non-Turing complete language that allows branch forwards but prevents rec
 to maximize safety and performance. 
 
 However, TEAL is essentially an assembly language. With PyTeal, developers can express smart contract logic purely using Python. 
-PyTeal provides high level, functional programming style abstactions over TEAL and does type checking at construction time.
+PyTeal provides high level, functional programming style abstractions over TEAL and does type checking at construction time.
 
 PyTeal **hasn't been security audited**. Use it at your own risk.
 
diff --git a/pyteal/ast/assert_.py b/pyteal/ast/assert_.py
index 3d37bf0..dac05af 100644
--- a/pyteal/ast/assert_.py
+++ b/pyteal/ast/assert_.py
@@ -21,7 +21,7 @@ class Assert(Expr):
         self.cond = cond
     
     def __teal__(self, options: 'CompileOptions'):
-        if options.version >= 3:
+        if options.version >= Op.assert_.min_version:
             # use assert op if available
             return TealBlock.FromOp(options, TealOp(self, Op.assert_), self.cond)
         
diff --git a/pyteal/ast/gtxn.py b/pyteal/ast/gtxn.py
index e924148..446ad57 100644
--- a/pyteal/ast/gtxn.py
+++ b/pyteal/ast/gtxn.py
@@ -1,8 +1,8 @@
-from typing import TYPE_CHECKING
+from typing import Union, cast, TYPE_CHECKING
 
-from ..types import TealType
+from ..types import TealType, require_type
 from ..ir import TealOp, Op, TealBlock
-from ..errors import TealInputError, verifyFieldVersion
+from ..errors import TealInputError, verifyFieldVersion, verifyTealVersion
 from ..config import MAX_GROUP_SIZE
 from .expr import Expr
 from .leafexpr import LeafExpr
@@ -15,7 +15,7 @@ if TYPE_CHECKING:
 class GtxnExpr(TxnExpr):
     """An expression that accesses a transaction field from a transaction in the current group."""
 
-    def __init__(self, txnIndex: int, field: TxnField) -> None:
+    def __init__(self, txnIndex: Union[int, Expr], field: TxnField) -> None:
         super().__init__(field)
         self.txnIndex = txnIndex
 
@@ -25,35 +25,50 @@ class GtxnExpr(TxnExpr):
     def __teal__(self, options: 'CompileOptions'):
         verifyFieldVersion(self.field.arg_name, self.field.min_version, options.version)
 
-        op = TealOp(self, Op.gtxn, self.txnIndex, self.field.arg_name)
-        return TealBlock.FromOp(options, op)
+        if type(self.txnIndex) == int:
+            op = TealOp(self, Op.gtxn, cast(int, self.txnIndex), self.field.arg_name)
+            return TealBlock.FromOp(options, op)
+
+        verifyTealVersion(Op.gtxns.min_version, options.version, "TEAL version too low to index Gtxn with dynamic values")
+        
+        op = TealOp(self, Op.gtxns, self.field.arg_name)
+        return TealBlock.FromOp(options, op, cast(Expr, self.txnIndex))
 
 GtxnExpr.__module__ = "pyteal"
 
 class GtxnaExpr(TxnaExpr):
     """An expression that accesses a transaction array field from a transaction in the current group."""
 
-    def __init__(self, txnIndex: int, field: TxnField, index: int) -> None:
+    def __init__(self, txnIndex: Union[int, Expr], field: TxnField, index: int) -> None:
         super().__init__(field, index)
         self.txnIndex = txnIndex
 
     def __str__(self):
-        return "(Gtxna {} {} {})".format(self.index, self.field.arg_name, self.index)
+        return "(Gtxna {} {} {})".format(self.txnIndex, self.field.arg_name, self.index)
 
     def __teal__(self, options: 'CompileOptions'):
         verifyFieldVersion(self.field.arg_name, self.field.min_version, options.version)
 
-        op = TealOp(self, Op.gtxna, self.txnIndex, self.field.arg_name, self.index)
-        return TealBlock.FromOp(options, op)
+        if type(self.txnIndex) == int:
+            op = TealOp(self, Op.gtxna, cast(int, self.txnIndex), self.field.arg_name, self.index)
+            return TealBlock.FromOp(options, op)
+        
+        verifyTealVersion(Op.gtxnsa.min_version, options.version, "TEAL version too low to index Gtxn with dynamic values")
+        
+        op = TealOp(self, Op.gtxnsa, self.field.arg_name, self.index)
+        return TealBlock.FromOp(options, op, cast(Expr, self.txnIndex))
 
 GtxnaExpr.__module__ = "pyteal"
 
 class TxnGroup:
     """Represents a group of transactions."""
 
-    def __getitem__(self, txnIndex: int) -> TxnObject:
-        if txnIndex < 0 or txnIndex >= MAX_GROUP_SIZE:
-            raise TealInputError("Invalid Gtxn index {}, shoud be in [0, {})".format(txnIndex, MAX_GROUP_SIZE))
+    def __getitem__(self, txnIndex: Union[int, Expr]) -> TxnObject:
+        if type(txnIndex) == int:
+            if txnIndex < 0 or txnIndex >= MAX_GROUP_SIZE:
+                raise TealInputError("Invalid Gtxn index {}, shoud be in [0, {})".format(txnIndex, MAX_GROUP_SIZE))
+        else:
+            require_type(cast(Expr, txnIndex).type_of(), TealType.uint64)
         return TxnObject(lambda field: GtxnExpr(txnIndex, field), lambda field, index: GtxnaExpr(txnIndex, field, index))
 
 TxnGroup.__module__ = "pyteal"
diff --git a/pyteal/compiler.py b/pyteal/compiler.py
index fab47c9..db3a7f3 100644
--- a/pyteal/compiler.py
+++ b/pyteal/compiler.py
@@ -119,7 +119,7 @@ def verifyOpsForVersion(teal: List[TealComponent], version: int):
         if isinstance(stmt, TealOp):
             op = stmt.getOp()
             if op.min_version > version:
-                raise TealInputError("Op not supported in TEAL version {}: {}".format(version, op))
+                raise TealInputError("Op not supported in TEAL version {}: {}. Minimum required version is {}".format(version, op, op.min_version))
 
 def verifyOpsForMode(teal: List[TealComponent], mode: Mode):
     """Verify that all TEAL operations are allowed in mode.
diff --git a/pyteal/errors.py b/pyteal/errors.py
index 1bb4429..664a111 100644
--- a/pyteal/errors.py
+++ b/pyteal/errors.py
@@ -52,7 +52,10 @@ class TealCompileError(Exception):
 
 TealCompileError.__module__ = "pyteal"
 
+def verifyTealVersion(minVersion: int, version: int, msg: str):
+    if minVersion > version:
+        msg = "{}. Minimum version needed is {}, but current version being compiled is {}".format(msg, minVersion, version)
+        raise TealInputError(msg)
+
 def verifyFieldVersion(fieldName: str, fieldMinVersion: int, version: int):
-    if fieldMinVersion > version:
-        msg = "TEAL version too low to use field {}. Minimum version needed is {}, but current version being compiled is {}"
-        raise TealInputError(msg.format(fieldName, fieldMinVersion, version))
+    verifyTealVersion(fieldMinVersion, version, "TEAL version too low to use field {}".format(fieldName))


Test patch:
diff --git a/pyteal/ast/gtxn_test.py b/pyteal/ast/gtxn_test.py
index 9e62433..173c087 100644
--- a/pyteal/ast/gtxn_test.py
+++ b/pyteal/ast/gtxn_test.py
@@ -15,6 +15,16 @@ def test_gtxn_invalid():
 
     with pytest.raises(TealInputError):
         Gtxn[MAX_GROUP_SIZE+1].sender()
+    
+    with pytest.raises(TealTypeError):
+        Gtxn[Pop(Int(0))].sender()
+    
+    with pytest.raises(TealTypeError):
+        Gtxn[Bytes("index")].sender()
+
+def test_gtxn_dynamic_teal_2():
+    with pytest.raises(TealInputError):
+        Gtxn[Int(0)].sender().__teal__(teal2Options)
 
 def test_gtxn_sender():
     for i in GTXN_RANGE:
@@ -29,6 +39,22 @@ def test_gtxn_sender():
 
         assert actual == expected
 
+def test_gtxn_sender_dynamic():
+    index = Int(0)
+    expr = Gtxn[index].sender()
+    assert expr.type_of() == TealType.bytes
+    
+    expected = TealSimpleBlock([
+        TealOp(index, Op.int, 0),
+        TealOp(expr, Op.gtxns, "Sender")
+    ])
+
+    actual, _ = expr.__teal__(teal3Options)
+    actual.addIncoming()
+    actual = TealBlock.NormalizeBlocks(actual)
+
+    assert actual == expected
+
 def test_gtxn_fee():
     for i in GTXN_RANGE:
         expr = Gtxn[i].fee()
@@ -42,6 +68,22 @@ def test_gtxn_fee():
 
         assert actual == expected
 
+def test_gtxn_fee_dynamic():
+    index = Int(0)
+    expr = Gtxn[index].fee()
+    assert expr.type_of() == TealType.uint64
+    
+    expected = TealSimpleBlock([
+        TealOp(index, Op.int, 0),
+        TealOp(expr, Op.gtxns, "Fee")
+    ])
+
+    actual, _ = expr.__teal__(teal3Options)
+    actual.addIncoming()
+    actual = TealBlock.NormalizeBlocks(actual)
+
+    assert actual == expected
+
 def test_gtxn_first_valid():
     for i in GTXN_RANGE:
         expr = Gtxn[i].first_valid()
@@ -55,6 +97,22 @@ def test_gtxn_first_valid():
 
         assert actual == expected
 
+def test_gtxn_first_valid_dynamic():
+    index = Int(0)
+    expr = Gtxn[index].first_valid()
+    assert expr.type_of() == TealType.uint64
+    
+    expected = TealSimpleBlock([
+        TealOp(index, Op.int, 0),
+        TealOp(expr, Op.gtxns, "FirstValid")
+    ])
+
+    actual, _ = expr.__teal__(teal3Options)
+    actual.addIncoming()
+    actual = TealBlock.NormalizeBlocks(actual)
+
+    assert actual == expected
+
 def test_gtxn_last_valid():
     for i in GTXN_RANGE:
         expr = Gtxn[i].last_valid()
@@ -68,6 +126,22 @@ def test_gtxn_last_valid():
 
         assert actual == expected
 
+def test_gtxn_last_valid_dynamic():
+    index = Int(0)
+    expr = Gtxn[index].last_valid()
+    assert expr.type_of() == TealType.uint64
+    
+    expected = TealSimpleBlock([
+        TealOp(index, Op.int, 0),
+        TealOp(expr, Op.gtxns, "LastValid")
+    ])
+
+    actual, _ = expr.__teal__(teal3Options)
+    actual.addIncoming()
+    actual = TealBlock.NormalizeBlocks(actual)
+
+    assert actual == expected
+
 def test_gtxn_note():
     for i in GTXN_RANGE:
         expr = Gtxn[i].note()
@@ -81,6 +155,22 @@ def test_gtxn_note():
 
         assert actual == expected
 
+def test_gtxn_note_dynamic():
+    index = Int(0)
+    expr = Gtxn[index].note()
+    assert expr.type_of() == TealType.bytes
+    
+    expected = TealSimpleBlock([
+        TealOp(index, Op.int, 0),
+        TealOp(expr, Op.gtxns, "Note")
+    ])
+
+    actual, _ = expr.__teal__(teal3Options)
+    actual.addIncoming()
+    actual = TealBlock.NormalizeBlocks(actual)
+
+    assert actual == expected
+
 def test_gtxn_lease():
     for i in GTXN_RANGE:
         expr = Gtxn[i].lease()
@@ -94,6 +184,22 @@ def test_gtxn_lease():
 
         assert actual == expected
 
+def test_gtxn_lease_dynamic():
+    index = Int(0)
+    expr = Gtxn[index].lease()
+    assert expr.type_of() == TealType.bytes
+    
+    expected = TealSimpleBlock([
+        TealOp(index, Op.int, 0),
+        TealOp(expr, Op.gtxns, "Lease")
+    ])
+
+    actual, _ = expr.__teal__(teal3Options)
+    actual.addIncoming()
+    actual = TealBlock.NormalizeBlocks(actual)
+
+    assert actual == expected
+
 def test_gtxn_receiver():
     for i in GTXN_RANGE:
         expr = Gtxn[i].receiver()
@@ -107,6 +213,22 @@ def test_gtxn_receiver():
 
         assert actual == expected
 
+def test_gtxn_receiver_dynamic():
+    index = Int(0)
+    expr = Gtxn[index].receiver()
+    assert expr.type_of() == TealType.bytes
+    
+    expected = TealSimpleBlock([
+        TealOp(index, Op.int, 0),
+        TealOp(expr, Op.gtxns, "Receiver")
+    ])
+
+    actual, _ = expr.__teal__(teal3Options)
+    actual.addIncoming()
+    actual = TealBlock.NormalizeBlocks(actual)
+
+    assert actual == expected
+
 def test_gtxn_amount():
     for i in GTXN_RANGE:
         expr = Gtxn[i].amount()
@@ -120,6 +242,22 @@ def test_gtxn_amount():
 
         assert actual == expected
 
+def test_gtxn_amount_dynamic():
+    index = Int(0)
+    expr = Gtxn[index].amount()
+    assert expr.type_of() == TealType.uint64
+    
+    expected = TealSimpleBlock([
+        TealOp(index, Op.int, 0),
+        TealOp(expr, Op.gtxns, "Amount")
+    ])
+
+    actual, _ = expr.__teal__(teal3Options)
+    actual.addIncoming()
+    actual = TealBlock.NormalizeBlocks(actual)
+
+    assert actual == expected
+
 def test_gtxn_close_remainder_to():
     for i in GTXN_RANGE:
         expr = Gtxn[i].close_remainder_to()
@@ -133,6 +271,22 @@ def test_gtxn_close_remainder_to():
 
         assert actual == expected
 
+def test_gtxn_close_remainder_to_dynamic():
+    index = Int(0)
+    expr = Gtxn[index].close_remainder_to()
+    assert expr.type_of() == TealType.bytes
+    
+    expected = TealSimpleBlock([
+        TealOp(index, Op.int, 0),
+        TealOp(expr, Op.gtxns, "CloseRemainderTo")
+    ])
+
+    actual, _ = expr.__teal__(teal3Options)
+    actual.addIncoming()
+    actual = TealBlock.NormalizeBlocks(actual)
+
+    assert actual == expected
+
 def test_gtxn_vote_pk():
     for i in GTXN_RANGE:
         expr = Gtxn[i].vote_pk()
@@ -146,6 +300,22 @@ def test_gtxn_vote_pk():
 
         assert actual == expected
 
+def test_gtxn_vote_pk_dynamic():
+    index = Int(0)
+    expr = Gtxn[index].vote_pk()
+    assert expr.type_of() == TealType.bytes
+    
+    expected = TealSimpleBlock([
+        TealOp(index, Op.int, 0),
+        TealOp(expr, Op.gtxns, "VotePK")
+    ])
+
+    actual, _ = expr.__teal__(teal3Options)
+    actual.addIncoming()
+    actual = TealBlock.NormalizeBlocks(actual)
+
+    assert actual == expected
+
 def test_gtxn_selection_pk():
     for i in GTXN_RANGE:
         expr = Gtxn[i].selection_pk()
@@ -159,6 +329,22 @@ def test_gtxn_selection_pk():
 
         assert actual == expected
 
+def test_gtxn_selection_pk_dynamic():
+    index = Int(0)
+    expr = Gtxn[index].selection_pk()
+    assert expr.type_of() == TealType.bytes
+    
+    expected = TealSimpleBlock([
+        TealOp(index, Op.int, 0),
+        TealOp(expr, Op.gtxns, "SelectionPK")
+    ])
+
+    actual, _ = expr.__teal__(teal3Options)
+    actual.addIncoming()
+    actual = TealBlock.NormalizeBlocks(actual)
+
+    assert actual == expected
+
 def test_gtxn_vote_first():
     for i in GTXN_RANGE:
         expr = Gtxn[i].vote_first()
@@ -172,6 +358,22 @@ def test_gtxn_vote_first():
 
         assert actual == expected
 
+def test_gtxn_vote_first_dynamic():
+    index = Int(0)
+    expr = Gtxn[index].vote_first()
+    assert expr.type_of() == TealType.uint64
+    
+    expected = TealSimpleBlock([
+        TealOp(index, Op.int, 0),
+        TealOp(expr, Op.gtxns, "VoteFirst")
+    ])
+
+    actual, _ = expr.__teal__(teal3Options)
+    actual.addIncoming()
+    actual = TealBlock.NormalizeBlocks(actual)
+
+    assert actual == expected
+
 def test_gtxn_vote_last():
     for i in GTXN_RANGE:
         expr = Gtxn[i].vote_last()
@@ -185,6 +387,22 @@ def test_gtxn_vote_last():
 
         assert actual == expected
 
+def test_gtxn_vote_last_dynamic():
+    index = Int(0)
+    expr = Gtxn[index].vote_last()
+    assert expr.type_of() == TealType.uint64
+    
+    expected = TealSimpleBlock([
+        TealOp(index, Op.int, 0),
+        TealOp(expr, Op.gtxns, "VoteLast")
+    ])
+
+    actual, _ = expr.__teal__(teal3Options)
+    actual.addIncoming()
+    actual = TealBlock.NormalizeBlocks(actual)
+
+    assert actual == expected
+
 def test_gtxn_vote_key_dilution():
     for i in GTXN_RANGE:
         expr = Gtxn[i].vote_key_dilution()
@@ -198,6 +416,22 @@ def test_gtxn_vote_key_dilution():
 
         assert actual == expected
 
+def test_gtxn_vote_key_dilution_dynamic():
+    index = Int(0)
+    expr = Gtxn[index].vote_key_dilution()
+    assert expr.type_of() == TealType.uint64
+    
+    expected = TealSimpleBlock([
+        TealOp(index, Op.int, 0),
+        TealOp(expr, Op.gtxns, "VoteKeyDilution")
+    ])
+
+    actual, _ = expr.__teal__(teal3Options)
+    actual.addIncoming()
+    actual = TealBlock.NormalizeBlocks(actual)
+
+    assert actual == expected
+
 def test_gtxn_type():
     for i in GTXN_RANGE:
         expr = Gtxn[i].type()
@@ -211,6 +445,22 @@ def test_gtxn_type():
 
         assert actual == expected
 
+def test_gtxn_type_dynamic():
+    index = Int(0)
+    expr = Gtxn[index].type()
+    assert expr.type_of() == TealType.bytes
+    
+    expected = TealSimpleBlock([
+        TealOp(index, Op.int, 0),
+        TealOp(expr, Op.gtxns, "Type")
+    ])
+
+    actual, _ = expr.__teal__(teal3Options)
+    actual.addIncoming()
+    actual = TealBlock.NormalizeBlocks(actual)
+
+    assert actual == expected
+
 def test_gtxn_type_enum():
     for i in GTXN_RANGE:
         expr = Gtxn[i].type_enum()
@@ -224,6 +474,22 @@ def test_gtxn_type_enum():
 
         assert actual == expected
 
+def test_gtxn_type_enum_dynamic():
+    index = Int(0)
+    expr = Gtxn[index].type_enum()
+    assert expr.type_of() == TealType.uint64
+    
+    expected = TealSimpleBlock([
+        TealOp(index, Op.int, 0),
+        TealOp(expr, Op.gtxns, "TypeEnum")
+    ])
+
+    actual, _ = expr.__teal__(teal3Options)
+    actual.addIncoming()
+    actual = TealBlock.NormalizeBlocks(actual)
+
+    assert actual == expected
+
 def test_gtxn_xfer_asset():
     for i in GTXN_RANGE:
         expr = Gtxn[i].xfer_asset()
@@ -237,6 +503,22 @@ def test_gtxn_xfer_asset():
 
         assert actual == expected
 
+def test_gtxn_xfer_asset_dynamic():
+    index = Int(0)
+    expr = Gtxn[index].xfer_asset()
+    assert expr.type_of() == TealType.uint64
+    
+    expected = TealSimpleBlock([
+        TealOp(index, Op.int, 0),
+        TealOp(expr, Op.gtxns, "XferAsset")
+    ])
+
+    actual, _ = expr.__teal__(teal3Options)
+    actual.addIncoming()
+    actual = TealBlock.NormalizeBlocks(actual)
+
+    assert actual == expected
+
 def test_gtxn_asset_amount():
     for i in GTXN_RANGE:
         expr = Gtxn[i].asset_amount()
@@ -250,6 +532,22 @@ def test_gtxn_asset_amount():
 
         assert actual == expected
 
+def test_gtxn_asset_amount_dynamic():
+    index = Int(0)
+    expr = Gtxn[index].asset_amount()
+    assert expr.type_of() == TealType.uint64
+    
+    expected = TealSimpleBlock([
+        TealOp(index, Op.int, 0),
+        TealOp(expr, Op.gtxns, "AssetAmount")
+    ])
+
+    actual, _ = expr.__teal__(teal3Options)
+    actual.addIncoming()
+    actual = TealBlock.NormalizeBlocks(actual)
+
+    assert actual == expected
+
 def test_gtxn_asset_sender():
     for i in GTXN_RANGE:
         expr = Gtxn[i].asset_sender()
@@ -263,6 +561,22 @@ def test_gtxn_asset_sender():
 
         assert actual == expected
 
+def test_gtxn_asset_sender_dynamic():
+    index = Int(0)
+    expr = Gtxn[index].asset_sender()
+    assert expr.type_of() == TealType.bytes
+    
+    expected = TealSimpleBlock([
+        TealOp(index, Op.int, 0),
+        TealOp(expr, Op.gtxns, "AssetSender")
+    ])
+
+    actual, _ = expr.__teal__(teal3Options)
+    actual.addIncoming()
+    actual = TealBlock.NormalizeBlocks(actual)
+
+    assert actual == expected
+
 def test_gtxn_asset_receiver():
     for i in GTXN_RANGE:
         expr = Gtxn[i].asset_receiver()
@@ -276,6 +590,22 @@ def test_gtxn_asset_receiver():
 
         assert actual == expected
 
+def test_gtxn_asset_receiver_dynamic():
+    index = Int(0)
+    expr = Gtxn[index].asset_receiver()
+    assert expr.type_of() == TealType.bytes
+    
+    expected = TealSimpleBlock([
+        TealOp(index, Op.int, 0),
+        TealOp(expr, Op.gtxns, "AssetReceiver")
+    ])
+
+    actual, _ = expr.__teal__(teal3Options)
+    actual.addIncoming()
+    actual = TealBlock.NormalizeBlocks(actual)
+
+    assert actual == expected
+
 def test_gtxn_asset_close_to():
     for i in GTXN_RANGE:
         expr = Gtxn[i].asset_close_to()
@@ -289,6 +619,22 @@ def test_gtxn_asset_close_to():
 
         assert actual == expected
 
+def test_gtxn_asset_close_to_dynamic():
+    index = Int(0)
+    expr = Gtxn[index].asset_close_to()
+    assert expr.type_of() == TealType.bytes
+    
+    expected = TealSimpleBlock([
+        TealOp(index, Op.int, 0),
+        TealOp(expr, Op.gtxns, "AssetCloseTo")
+    ])
+
+    actual, _ = expr.__teal__(teal3Options)
+    actual.addIncoming()
+    actual = TealBlock.NormalizeBlocks(actual)
+
+    assert actual == expected
+
 def test_gtxn_group_index():
     for i in GTXN_RANGE:
         expr = Gtxn[i].group_index()
@@ -302,6 +648,22 @@ def test_gtxn_group_index():
 
         assert actual == expected
 
+def test_gtxn_group_index_dynamic():
+    index = Int(0)
+    expr = Gtxn[index].group_index()
+    assert expr.type_of() == TealType.uint64
+    
+    expected = TealSimpleBlock([
+        TealOp(index, Op.int, 0),
+        TealOp(expr, Op.gtxns, "GroupIndex")
+    ])
+
+    actual, _ = expr.__teal__(teal3Options)
+    actual.addIncoming()
+    actual = TealBlock.NormalizeBlocks(actual)
+
+    assert actual == expected
+
 def test_gtxn_id():
     for i in GTXN_RANGE:
         expr = Gtxn[i].tx_id()
@@ -315,6 +677,22 @@ def test_gtxn_id():
 
         assert actual == expected
 
+def test_gtxn_id_dynamic():
+    index = Int(0)
+    expr = Gtxn[index].tx_id()
+    assert expr.type_of() == TealType.bytes
+    
+    expected = TealSimpleBlock([
+        TealOp(index, Op.int, 0),
+        TealOp(expr, Op.gtxns, "TxID")
+    ])
+
+    actual, _ = expr.__teal__(teal3Options)
+    actual.addIncoming()
+    actual = TealBlock.NormalizeBlocks(actual)
+
+    assert actual == expected
+
 def test_txn_application_id():
     for i in GTXN_RANGE:
         expr = Gtxn[i].application_id()
@@ -328,6 +706,22 @@ def test_txn_application_id():
 
         assert actual == expected
 
+def test_txn_application_id_dynamic():
+    index = Int(0)
+    expr = Gtxn[index].application_id()
+    assert expr.type_of() == TealType.uint64
+    
+    expected = TealSimpleBlock([
+        TealOp(index, Op.int, 0),
+        TealOp(expr, Op.gtxns, "ApplicationID")
+    ])
+
+    actual, _ = expr.__teal__(teal3Options)
+    actual.addIncoming()
+    actual = TealBlock.NormalizeBlocks(actual)
+
+    assert actual == expected
+
 def test_txn_on_completion():
     for i in GTXN_RANGE:
         expr = Gtxn[i].on_completion()
@@ -341,6 +735,22 @@ def test_txn_on_completion():
 
         assert actual == expected
 
+def test_txn_on_completion_dynamic():
+    index = Int(0)
+    expr = Gtxn[index].on_completion()
+    assert expr.type_of() == TealType.uint64
+    
+    expected = TealSimpleBlock([
+        TealOp(index, Op.int, 0),
+        TealOp(expr, Op.gtxns, "OnCompletion")
+    ])
+
+    actual, _ = expr.__teal__(teal3Options)
+    actual.addIncoming()
+    actual = TealBlock.NormalizeBlocks(actual)
+
+    assert actual == expected
+
 def test_txn_application_args():
     for i in GTXN_RANGE:
         for j in range(32):
@@ -355,6 +765,23 @@ def test_txn_application_args():
 
             assert actual == expected
 
+def test_txn_application_args_dynamic():
+    index = Int(0)
+    for j in range(32):
+        expr = Gtxn[index].application_args[j]
+        assert expr.type_of() == TealType.bytes
+        
+        expected = TealSimpleBlock([
+            TealOp(index, Op.int, 0),
+            TealOp(expr, Op.gtxnsa, "ApplicationArgs", j)
+        ])
+
+        actual, _ = expr.__teal__(teal3Options)
+        actual.addIncoming()
+        actual = TealBlock.NormalizeBlocks(actual)
+
+        assert actual == expected
+
 def test_txn_application_args_length():
     for i in GTXN_RANGE:
         expr = Gtxn[i].application_args.length()
@@ -368,6 +795,22 @@ def test_txn_application_args_length():
 
         assert actual == expected
 
+def test_txn_application_args_length_dynamic():
+    index = Int(0)
+    expr = Gtxn[index].application_args.length()
+    assert expr.type_of() == TealType.uint64
+    
+    expected = TealSimpleBlock([
+        TealOp(index, Op.int, 0),
+        TealOp(expr, Op.gtxns, "NumAppArgs")
+    ])
+
+    actual, _ = expr.__teal__(teal3Options)
+    actual.addIncoming()
+    actual = TealBlock.NormalizeBlocks(actual)
+
+    assert actual == expected
+
 def test_txn_accounts():
     for i in GTXN_RANGE:
         for j in range(32):
@@ -382,6 +825,23 @@ def test_txn_accounts():
 
             assert actual == expected
 
+def test_txn_accounts_dynamic():
+    index = Int(0)
+    for j in range(32):
+        expr = Gtxn[index].accounts[j]
+        assert expr.type_of() == TealType.bytes
+        
+        expected = TealSimpleBlock([
+            TealOp(index, Op.int, 0),
+            TealOp(expr, Op.gtxnsa, "Accounts", j)
+        ])
+
+        actual, _ = expr.__teal__(teal3Options)
+        actual.addIncoming()
+        actual = TealBlock.NormalizeBlocks(actual)
+
+        assert actual == expected
+
 def test_txn_accounts_length():
     for i in GTXN_RANGE:
         expr = Gtxn[i].accounts.length()
@@ -395,6 +855,22 @@ def test_txn_accounts_length():
 
         assert actual == expected
 
+def test_txn_accounts_length_dynamic():
+    index = Int(0)
+    expr = Gtxn[index].accounts.length()
+    assert expr.type_of() == TealType.uint64
+    
+    expected = TealSimpleBlock([
+        TealOp(index, Op.int, 0),
+        TealOp(expr, Op.gtxns, "NumAccounts")
+    ])
+
+    actual, _ = expr.__teal__(teal3Options)
+    actual.addIncoming()
+    actual = TealBlock.NormalizeBlocks(actual)
+
+    assert actual == expected
+
 def test_txn_approval_program():
     for i in GTXN_RANGE:
         expr = Gtxn[i].approval_program()
@@ -408,6 +884,22 @@ def test_txn_approval_program():
 
         assert actual == expected
 
+def test_txn_approval_program_dynamic():
+    index = Int(0)
+    expr = Gtxn[index].approval_program()
+    assert expr.type_of() == TealType.bytes
+    
+    expected = TealSimpleBlock([
+        TealOp(index, Op.int, 0),
+        TealOp(expr, Op.gtxns, "ApprovalProgram")
+    ])
+
+    actual, _ = expr.__teal__(teal3Options)
+    actual.addIncoming()
+    actual = TealBlock.NormalizeBlocks(actual)
+
+    assert actual == expected
+
 def test_txn_clear_state_program():
     for i in GTXN_RANGE:
         expr = Gtxn[i].clear_state_program()
@@ -421,6 +913,22 @@ def test_txn_clear_state_program():
 
         assert actual == expected
 
+def test_txn_clear_state_program_dynamic():
+    index = Int(0)
+    expr = Gtxn[index].clear_state_program()
+    assert expr.type_of() == TealType.bytes
+    
+    expected = TealSimpleBlock([
+        TealOp(index, Op.int, 0),
+        TealOp(expr, Op.gtxns, "ClearStateProgram")
+    ])
+
+    actual, _ = expr.__teal__(teal3Options)
+    actual.addIncoming()
+    actual = TealBlock.NormalizeBlocks(actual)
+
+    assert actual == expected
+
 def test_txn_rekey_to():
     for i in GTXN_RANGE:
         expr = Gtxn[i].rekey_to()
@@ -434,6 +942,22 @@ def test_txn_rekey_to():
 
         assert actual == expected
 
+def test_txn_rekey_to_dynamic():
+    index = Int(0)
+    expr = Gtxn[index].rekey_to()
+    assert expr.type_of() == TealType.bytes
+    
+    expected = TealSimpleBlock([
+        TealOp(index, Op.int, 0),
+        TealOp(expr, Op.gtxns, "RekeyTo")
+    ])
+
+    actual, _ = expr.__teal__(teal3Options)
+    actual.addIncoming()
+    actual = TealBlock.NormalizeBlocks(actual)
+
+    assert actual == expected
+
 def test_txn_config_asset():
     for i in GTXN_RANGE:
         expr = Gtxn[i].config_asset()
@@ -447,6 +971,22 @@ def test_txn_config_asset():
 
         assert actual == expected
 
+def test_txn_config_asset_dynamic():
+    index = Int(0)
+    expr = Gtxn[index].config_asset()
+    assert expr.type_of() == TealType.uint64
+    
+    expected = TealSimpleBlock([
+        TealOp(index, Op.int, 0),
+        TealOp(expr, Op.gtxns, "ConfigAsset")
+    ])
+
+    actual, _ = expr.__teal__(teal3Options)
+    actual.addIncoming()
+    actual = TealBlock.NormalizeBlocks(actual)
+
+    assert actual == expected
+
 def test_txn_config_asset_total():
     for i in GTXN_RANGE:
         expr = Gtxn[i].config_asset_total()
@@ -460,6 +1000,22 @@ def test_txn_config_asset_total():
 
         assert actual == expected
 
+def test_txn_config_asset_total_dynamic():
+    index = Int(0)
+    expr = Gtxn[index].config_asset_total()
+    assert expr.type_of() == TealType.uint64
+    
+    expected = TealSimpleBlock([
+        TealOp(index, Op.int, 0),
+        TealOp(expr, Op.gtxns, "ConfigAssetTotal")
+    ])
+
+    actual, _ = expr.__teal__(teal3Options)
+    actual.addIncoming()
+    actual = TealBlock.NormalizeBlocks(actual)
+
+    assert actual == expected
+
 def test_txn_config_asset_decimals():
     for i in GTXN_RANGE:
         expr = Gtxn[i].config_asset_decimals()
@@ -473,6 +1029,22 @@ def test_txn_config_asset_decimals():
 
         assert actual == expected
 
+def test_txn_config_asset_decimals_dynamic():
+    index = Int(0)
+    expr = Gtxn[index].config_asset_decimals()
+    assert expr.type_of() == TealType.uint64
+    
+    expected = TealSimpleBlock([
+        TealOp(index, Op.int, 0),
+        TealOp(expr, Op.gtxns, "ConfigAssetDecimals")
+    ])
+
+    actual, _ = expr.__teal__(teal3Options)
+    actual.addIncoming()
+    actual = TealBlock.NormalizeBlocks(actual)
+
+    assert actual == expected
+
 def test_txn_config_asset_default_frozen():
     for i in GTXN_RANGE:
         expr = Gtxn[i].config_asset_default_frozen()
@@ -486,6 +1058,22 @@ def test_txn_config_asset_default_frozen():
 
         assert actual == expected
 
+def test_txn_config_asset_default_frozen_dynamic():
+    index = Int(0)
+    expr = Gtxn[index].config_asset_default_frozen()
+    assert expr.type_of() == TealType.uint64
+    
+    expected = TealSimpleBlock([
+        TealOp(index, Op.int, 0),
+        TealOp(expr, Op.gtxns, "ConfigAssetDefaultFrozen")
+    ])
+
+    actual, _ = expr.__teal__(teal3Options)
+    actual.addIncoming()
+    actual = TealBlock.NormalizeBlocks(actual)
+
+    assert actual == expected
+
 def test_txn_config_asset_unit_name():
     for i in GTXN_RANGE:
         expr = Gtxn[i].config_asset_unit_name()
@@ -499,6 +1087,22 @@ def test_txn_config_asset_unit_name():
 
         assert actual == expected
 
+def test_txn_config_asset_unit_name_dynamic():
+    index = Int(0)
+    expr = Gtxn[index].config_asset_unit_name()
+    assert expr.type_of() == TealType.bytes
+    
+    expected = TealSimpleBlock([
+        TealOp(index, Op.int, 0),
+        TealOp(expr, Op.gtxns, "ConfigAssetUnitName")
+    ])
+
+    actual, _ = expr.__teal__(teal3Options)
+    actual.addIncoming()
+    actual = TealBlock.NormalizeBlocks(actual)
+
+    assert actual == expected
+
 def test_txn_config_asset_name():
     for i in GTXN_RANGE:
         expr = Gtxn[i].config_asset_name()
@@ -512,6 +1116,22 @@ def test_txn_config_asset_name():
 
         assert actual == expected
 
+def test_txn_config_asset_name_dynamic():
+    index = Int(0)
+    expr = Gtxn[index].config_asset_name()
+    assert expr.type_of() == TealType.bytes
+    
+    expected = TealSimpleBlock([
+        TealOp(index, Op.int, 0),
+        TealOp(expr, Op.gtxns, "ConfigAssetName")
+    ])
+
+    actual, _ = expr.__teal__(teal3Options)
+    actual.addIncoming()
+    actual = TealBlock.NormalizeBlocks(actual)
+
+    assert actual == expected
+
 def test_txn_config_asset_url():
     for i in GTXN_RANGE:
         expr = Gtxn[i].config_asset_url()
@@ -525,6 +1145,22 @@ def test_txn_config_asset_url():
 
         assert actual == expected
 
+def test_txn_config_asset_url_dynamic():
+    index = Int(0)
+    expr = Gtxn[index].config_asset_url()
+    assert expr.type_of() == TealType.bytes
+    
+    expected = TealSimpleBlock([
+        TealOp(index, Op.int, 0),
+        TealOp(expr, Op.gtxns, "ConfigAssetURL")
+    ])
+
+    actual, _ = expr.__teal__(teal3Options)
+    actual.addIncoming()
+    actual = TealBlock.NormalizeBlocks(actual)
+
+    assert actual == expected
+
 def test_txn_config_asset_metadata_hash():
     for i in GTXN_RANGE:
         expr = Gtxn[i].config_asset_metadata_hash()
@@ -538,6 +1174,22 @@ def test_txn_config_asset_metadata_hash():
 
         assert actual == expected
 
+def test_txn_config_asset_metadata_hash_dynamic():
+    index = Int(0)
+    expr = Gtxn[index].config_asset_metadata_hash()
+    assert expr.type_of() == TealType.bytes
+    
+    expected = TealSimpleBlock([
+        TealOp(index, Op.int, 0),
+        TealOp(expr, Op.gtxns, "ConfigAssetMetadataHash")
+    ])
+
+    actual, _ = expr.__teal__(teal3Options)
+    actual.addIncoming()
+    actual = TealBlock.NormalizeBlocks(actual)
+
+    assert actual == expected
+
 def test_txn_config_asset_manager():
     for i in GTXN_RANGE:
         expr = Gtxn[i].config_asset_manager()
@@ -551,6 +1203,22 @@ def test_txn_config_asset_manager():
 
         assert actual == expected
 
+def test_txn_config_asset_manager_dynamic():
+    index = Int(0)
+    expr = Gtxn[index].config_asset_manager()
+    assert expr.type_of() == TealType.bytes
+    
+    expected = TealSimpleBlock([
+        TealOp(index, Op.int, 0),
+        TealOp(expr, Op.gtxns, "ConfigAssetManager")
+    ])
+
+    actual, _ = expr.__teal__(teal3Options)
+    actual.addIncoming()
+    actual = TealBlock.NormalizeBlocks(actual)
+
+    assert actual == expected
+
 def test_txn_config_asset_reserve():
     for i in GTXN_RANGE:
         expr = Gtxn[i].config_asset_reserve()
@@ -564,6 +1232,22 @@ def test_txn_config_asset_reserve():
 
         assert actual == expected
 
+def test_txn_config_asset_reserve_dynamic():
+    index = Int(0)
+    expr = Gtxn[index].config_asset_reserve()
+    assert expr.type_of() == TealType.bytes
+    
+    expected = TealSimpleBlock([
+        TealOp(index, Op.int, 0),
+        TealOp(expr, Op.gtxns, "ConfigAssetReserve")
+    ])
+
+    actual, _ = expr.__teal__(teal3Options)
+    actual.addIncoming()
+    actual = TealBlock.NormalizeBlocks(actual)
+
+    assert actual == expected
+
 def test_txn_config_asset_freeze():
     for i in GTXN_RANGE:
         expr = Gtxn[i].config_asset_freeze()
@@ -577,6 +1261,22 @@ def test_txn_config_asset_freeze():
 
         assert actual == expected
 
+def test_txn_config_asset_freeze_dynamic():
+    index = Int(0)
+    expr = Gtxn[index].config_asset_freeze()
+    assert expr.type_of() == TealType.bytes
+    
+    expected = TealSimpleBlock([
+        TealOp(index, Op.int, 0),
+        TealOp(expr, Op.gtxns, "ConfigAssetFreeze")
+    ])
+
+    actual, _ = expr.__teal__(teal3Options)
+    actual.addIncoming()
+    actual = TealBlock.NormalizeBlocks(actual)
+
+    assert actual == expected
+
 def test_txn_config_asset_clawback():
     for i in GTXN_RANGE:
         expr = Gtxn[i].config_asset_clawback()
@@ -590,6 +1290,22 @@ def test_txn_config_asset_clawback():
 
         assert actual == expected
 
+def test_txn_config_asset_clawback_dynamic():
+    index = Int(0)
+    expr = Gtxn[index].config_asset_clawback()
+    assert expr.type_of() == TealType.bytes
+    
+    expected = TealSimpleBlock([
+        TealOp(index, Op.int, 0),
+        TealOp(expr, Op.gtxns, "ConfigAssetClawback")
+    ])
+
+    actual, _ = expr.__teal__(teal3Options)
+    actual.addIncoming()
+    actual = TealBlock.NormalizeBlocks(actual)
+
+    assert actual == expected
+
 def test_txn_freeze_asset():
     for i in GTXN_RANGE:
         expr = Gtxn[i].freeze_asset()
@@ -603,6 +1319,22 @@ def test_txn_freeze_asset():
 
         assert actual == expected
 
+def test_txn_freeze_asset_dynamic():
+    index = Int(0)
+    expr = Gtxn[index].freeze_asset()
+    assert expr.type_of() == TealType.uint64
+    
+    expected = TealSimpleBlock([
+        TealOp(index, Op.int, 0),
+        TealOp(expr, Op.gtxns, "FreezeAsset")
+    ])
+
+    actual, _ = expr.__teal__(teal3Options)
+    actual.addIncoming()
+    actual = TealBlock.NormalizeBlocks(actual)
+
+    assert actual == expected
+
 def test_txn_freeze_asset_account():
     for i in GTXN_RANGE:
         expr = Gtxn[i].freeze_asset_account()
@@ -616,6 +1348,22 @@ def test_txn_freeze_asset_account():
 
         assert actual == expected
 
+def test_txn_freeze_asset_account_dynamic():
+    index = Int(0)
+    expr = Gtxn[index].freeze_asset_account()
+    assert expr.type_of() == TealType.bytes
+    
+    expected = TealSimpleBlock([
+        TealOp(index, Op.int, 0),
+        TealOp(expr, Op.gtxns, "FreezeAssetAccount")
+    ])
+
+    actual, _ = expr.__teal__(teal3Options)
+    actual.addIncoming()
+    actual = TealBlock.NormalizeBlocks(actual)
+
+    assert actual == expected
+
 def test_txn_freeze_asset_frozen():
     for i in GTXN_RANGE:
         expr = Gtxn[i].freeze_asset_frozen()
@@ -629,6 +1377,22 @@ def test_txn_freeze_asset_frozen():
 
         assert actual == expected
 
+def test_txn_freeze_asset_frozen_dynamic():
+    index = Int(0)
+    expr = Gtxn[index].freeze_asset_frozen()
+    assert expr.type_of() == TealType.uint64
+    
+    expected = TealSimpleBlock([
+        TealOp(index, Op.int, 0),
+        TealOp(expr, Op.gtxns, "FreezeAssetFrozen")
+    ])
+
+    actual, _ = expr.__teal__(teal3Options)
+    actual.addIncoming()
+    actual = TealBlock.NormalizeBlocks(actual)
+
+    assert actual == expected
+
 def test_txn_assets():
     for i in GTXN_RANGE:
         for j in range(32):
@@ -646,6 +1410,26 @@ def test_txn_assets():
             with pytest.raises(TealInputError):
                 expr.__teal__(teal2Options)
 
+def test_txn_assets_dynamic():
+    index = Int(0)
+    for j in range(32):
+        expr = Gtxn[index].assets[j]
+        assert expr.type_of() == TealType.uint64
+        
+        expected = TealSimpleBlock([
+            TealOp(index, Op.int, 0),
+            TealOp(expr, Op.gtxnsa, "Assets", j)
+        ])
+
+        actual, _ = expr.__teal__(teal3Options)
+        actual.addIncoming()
+        actual = TealBlock.NormalizeBlocks(actual)
+
+        assert actual == expected
+
+        with pytest.raises(TealInputError):
+            expr.__teal__(teal2Options)
+
 def test_txn_assets_length():
     for i in GTXN_RANGE:
         expr = Gtxn[i].assets.length()
@@ -660,7 +1444,26 @@ def test_txn_assets_length():
         assert actual == expected
 
         with pytest.raises(TealInputError):
-                expr.__teal__(teal2Options)
+            expr.__teal__(teal2Options)
+
+def test_txn_assets_length_dynamic():
+    index = Int(0)
+    expr = Gtxn[index].assets.length()
+    assert expr.type_of() == TealType.uint64
+    
+    expected = TealSimpleBlock([
+        TealOp(index, Op.int, 0),
+        TealOp(expr, Op.gtxns, "NumAssets")
+    ])
+
+    actual, _ = expr.__teal__(teal3Options)
+    actual.addIncoming()
+    actual = TealBlock.NormalizeBlocks(actual)
+
+    assert actual == expected
+
+    with pytest.raises(TealInputError):
+            expr.__teal__(teal2Options)
 
 def test_txn_applications():
     for i in GTXN_RANGE:
@@ -679,6 +1482,26 @@ def test_txn_applications():
             with pytest.raises(TealInputError):
                 expr.__teal__(teal2Options)
 
+def test_txn_applications_dynamic():
+    index = Int(0)
+    for j in range(32):
+        expr = Gtxn[index].applications[j]
+        assert expr.type_of() == TealType.uint64
+        
+        expected = TealSimpleBlock([
+            TealOp(index, Op.int, 0),
+            TealOp(expr, Op.gtxnsa, "Applications", j)
+        ])
+
+        actual, _ = expr.__teal__(teal3Options)
+        actual.addIncoming()
+        actual = TealBlock.NormalizeBlocks(actual)
+
+        assert actual == expected
+
+        with pytest.raises(TealInputError):
+            expr.__teal__(teal2Options)
+
 def test_txn_applications_length():
     for i in GTXN_RANGE:
         expr = Gtxn[i].applications.length()
@@ -693,7 +1516,26 @@ def test_txn_applications_length():
         assert actual == expected
 
         with pytest.raises(TealInputError):
-                expr.__teal__(teal2Options)
+            expr.__teal__(teal2Options)
+
+def test_txn_applications_length_dynamic():
+    index = Int(0)
+    expr = Gtxn[index].applications.length()
+    assert expr.type_of() == TealType.uint64
+    
+    expected = TealSimpleBlock([
+        TealOp(index, Op.int, 0),
+        TealOp(expr, Op.gtxns, "NumApplications")
+    ])
+
+    actual, _ = expr.__teal__(teal3Options)
+    actual.addIncoming()
+    actual = TealBlock.NormalizeBlocks(actual)
+
+    assert actual == expected
+
+    with pytest.raises(TealInputError):
+            expr.__teal__(teal2Options)
 
 def test_txn_global_num_uints():
     for i in GTXN_RANGE:
@@ -711,6 +1553,25 @@ def test_txn_global_num_uints():
         with pytest.raises(TealInputError):
             expr.__teal__(teal2Options)
 
+def test_txn_global_num_uints_dynamic():
+    index = Int(0)
+    expr = Gtxn[index].global_num_uints()
+    assert expr.type_of() == TealType.uint64
+    
+    expected = TealSimpleBlock([
+        TealOp(index, Op.int, 0),
+        TealOp(expr, Op.gtxns, "GlobalNumUint")
+    ])
+
+    actual, _ = expr.__teal__(teal3Options)
+    actual.addIncoming()
+    actual = TealBlock.NormalizeBlocks(actual)
+
+    assert actual == expected
+
+    with pytest.raises(TealInputError):
+        expr.__teal__(teal2Options)
+
 def test_txn_global_num_byte_slices():
     for i in GTXN_RANGE:
         expr = Gtxn[i].global_num_byte_slices()
@@ -727,6 +1588,25 @@ def test_txn_global_num_byte_slices():
         with pytest.raises(TealInputError):
             expr.__teal__(teal2Options)
 
+def test_txn_global_num_byte_slices_dynamic():
+    index = Int(0)
+    expr = Gtxn[index].global_num_byte_slices()
+    assert expr.type_of() == TealType.uint64
+    
+    expected = TealSimpleBlock([
+        TealOp(index, Op.int, 0),
+        TealOp(expr, Op.gtxns, "GlobalNumByteSlice")
+    ])
+
+    actual, _ = expr.__teal__(teal3Options)
+    actual.addIncoming()
+    actual = TealBlock.NormalizeBlocks(actual)
+
+    assert actual == expected
+
+    with pytest.raises(TealInputError):
+        expr.__teal__(teal2Options)
+
 def test_txn_local_num_uints():
     for i in GTXN_RANGE:
         expr = Gtxn[i].local_num_uints()
@@ -743,6 +1623,25 @@ def test_txn_local_num_uints():
         with pytest.raises(TealInputError):
             expr.__teal__(teal2Options)
 
+def test_txn_local_num_uints_dynamic():
+    index = Int(0)
+    expr = Gtxn[index].local_num_uints()
+    assert expr.type_of() == TealType.uint64
+    
+    expected = TealSimpleBlock([
+        TealOp(index, Op.int, 0),
+        TealOp(expr, Op.gtxns, "LocalNumUint")
+    ])
+
+    actual, _ = expr.__teal__(teal3Options)
+    actual.addIncoming()
+    actual = TealBlock.NormalizeBlocks(actual)
+
+    assert actual == expected
+
+    with pytest.raises(TealInputError):
+        expr.__teal__(teal2Options)
+
 def test_txn_local_num_byte_slices():
     for i in GTXN_RANGE:
         expr = Gtxn[i].local_num_byte_slices()
@@ -758,3 +1657,22 @@ def test_txn_local_num_byte_slices():
 
         with pytest.raises(TealInputError):
             expr.__teal__(teal2Options)
+
+def test_txn_local_num_byte_slices_dynamic():
+    index = Int(0)
+    expr = Gtxn[index].local_num_byte_slices()
+    assert expr.type_of() == TealType.uint64
+    
+    expected = TealSimpleBlock([
+        TealOp(index, Op.int, 0),
+        TealOp(expr, Op.gtxns, "LocalNumByteSlice")
+    ])
+
+    actual, _ = expr.__teal__(teal3Options)
+    actual.addIncoming()
+    actual = TealBlock.NormalizeBlocks(actual)
+
+    assert actual == expected
+
+    with pytest.raises(TealInputError):
+        expr.__teal__(teal2Options)