Repository: algorand/pyteal

Issue:
Evaluate design by translating existing applications to PyTeal
- [x]  Voting application (moderate complexity)
- [x]  Security token (high complexity)


Discussion:
derbear: I wonder if we should reiterate in the `examples` directory that the programs shown have not gone through a security audit.

Patch:
diff --git a/examples/asset.py b/examples/asset.py
new file mode 100644
index 0000000..ae285f8
--- /dev/null
+++ b/examples/asset.py
@@ -0,0 +1,100 @@
+# This example is provided for informational purposes only and has not been audited for security.
+
+from pyteal import *
+
+def approval_program():
+    on_creation = Seq([
+        Assert(Txn.application_args.length() == Int(1)),
+        App.globalPut(Bytes("total supply"), Btoi(Txn.application_args[0])),
+        App.globalPut(Bytes("reserve"), Btoi(Txn.application_args[0])),
+        App.localPut(Int(0), Bytes("admin"), Int(1)),
+        App.localPut(Int(0), Bytes("balance"), Int(0)),
+        Return(Int(1))
+    ])
+
+    is_admin = App.localGet(Int(0), Bytes("admin"))
+
+    on_closeout = Seq([
+        App.globalPut(
+            Bytes("reserve"),
+            App.globalGet(Bytes("reserve")) + App.localGet(Int(0), Bytes("balance"))
+        ),
+        Return(Int(1))
+    ])
+
+    register = Seq([
+        App.localPut(Int(0), Bytes("balance"), Int(0)),
+        Return(Int(1))
+    ])
+
+    # configure the admin status of the account Txn.accounts[0]
+    # sender must be admin
+    new_admin_status = Btoi(Txn.application_args[1])
+    set_admin = Seq([
+        Assert(And(is_admin, Txn.application_args.length() == Int(2))),
+        App.localPut(Int(1), Bytes("admin"), new_admin_status),
+        Return(Int(1))
+    ])
+    # NOTE: The above set_admin code is carefully constructed. If instead we used the following code:
+    # Seq([
+    #     Assert(Txn.application_args.length() == Int(2)),
+    #     App.localPut(Int(1), Bytes("admin"), new_admin_status),
+    #     Return(is_admin)
+    # ])
+    # It would be vulnerable to the following attack: a sender passes in their own address as
+    # Txn.accounts[0], so then the line App.localPut(Int(1), Bytes("admin"), new_admin_status)
+    # changes the sender's admin status, meaning the final Return(is_admin) can return anything the
+    # sender wants. This allows anyone to become an admin!
+
+    # move assets from the reserve to Txn.accounts[0]
+    # sender must be admin
+    mint_amount = Btoi(Txn.application_args[1])
+    mint = Seq([
+        Assert(Txn.application_args.length() == Int(2)),
+        Assert(mint_amount <= App.globalGet(Bytes("reserve"))),
+        App.globalPut(Bytes("reserve"), App.globalGet(Bytes("reserve")) - mint_amount),
+        App.localPut(Int(1), Bytes("balance"), App.localGet(Int(1), Bytes("balance")) + mint_amount),
+        Return(is_admin)
+    ])
+
+    # transfer assets from the sender to Txn.accounts[0]
+    transfer_amount = Btoi(Txn.application_args[1])
+    transfer = Seq([
+        Assert(Txn.application_args.length() == Int(2)),
+        Assert(transfer_amount <= App.localGet(Int(0), Bytes("balance"))),
+        App.localPut(Int(0), Bytes("balance"), App.localGet(Int(0), Bytes("balance")) - transfer_amount),
+        App.localPut(Int(1), Bytes("balance"), App.localGet(Int(1), Bytes("balance")) + transfer_amount),
+        Return(Int(1))
+    ])
+
+    program = Cond(
+        [Txn.application_id() == Int(0), on_creation],
+        [Txn.on_completion() == OnComplete.DeleteApplication, Return(is_admin)],
+        [Txn.on_completion() == OnComplete.UpdateApplication, Return(is_admin)],
+        [Txn.on_completion() == OnComplete.CloseOut, on_closeout],
+        [Txn.on_completion() == OnComplete.OptIn, register],
+        [Txn.application_args[0] == Bytes("set admin"), set_admin],
+        [Txn.application_args[0] == Bytes("mint"), mint],
+        [Txn.application_args[0] == Bytes("transfer"), transfer]
+    )
+
+    return program
+
+def close_out_program():
+    program = Seq([
+        App.globalPut(
+            Bytes("reserve"),
+            App.globalGet(Bytes("reserve")) + App.localGet(Int(0), Bytes("balance"))
+        ),
+        Return(Int(1))
+    ])
+
+    return program
+
+with open('asset_approval.teal', 'w') as f:
+    compiled = compileTeal(approval_program(), Mode.Application)
+    f.write(compiled)
+
+with open('asset_close_out.teal', 'w') as f:
+    compiled = compileTeal(close_out_program(), Mode.Application)
+    f.write(compiled)
diff --git a/examples/atomic_swap.py b/examples/atomic_swap.py
index 4f99a4e..746c48c 100644
--- a/examples/atomic_swap.py
+++ b/examples/atomic_swap.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python3
+# This example is provided for informational purposes only and has not been audited for security.
 
 from pyteal import *
 
diff --git a/examples/dutch_auction.py b/examples/dutch_auction.py
index 47907da..8b69e68 100644
--- a/examples/dutch_auction.py
+++ b/examples/dutch_auction.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python3
+# This example is provided for informational purposes only and has not been audited for security.
 
 from pyteal import *
 
@@ -25,54 +25,54 @@ redeem_round = Tmpl.Int("TMPL_REDEEM_ROUND")
 wrapup_time = Tmpl.Int("TMPL_WRAPUP_ROUND")
 
 # the definition of i simplifies the original desgin by only constraining last_valid here
-i_upper = (Gtxn.last_valid(0)- start_round)/ period
+i_upper = (Gtxn[0].last_valid()- start_round)/ period
 
-bid = And(Gtxn.last_valid(0) == Gtxn.last_valid(1),
-          Gtxn.last_valid(1) == Gtxn.last_valid(2),
-          Gtxn.last_valid(2) == Gtxn.last_valid(3),
-          Gtxn.last_valid(3) == Gtxn.last_valid(4),
-          Gtxn.last_valid(4) < (start_round + i_upper * period),
-          Gtxn.type_enum(0) == Int(4), # asset transfer
-          Gtxn.xfer_asset(0) == asset_d,
-          Gtxn.receiver(0) == receiver,
-          Gtxn.type_enum(1) == Int(4),
-          Gtxn.xfer_asset(1) == asset_b,
-          Gtxn.type_enum(2) == Int(4),
-          Gtxn.xfer_asset(2) == asset_c,
-          Gtxn.type_enum(3) == Int(4),
-          Gtxn.xfer_asset(3) == asset_c,
-          Gtxn.type_enum(4) == Int(1),
-          Gtxn.amount(4) == (Gtxn.fee(1) + Gtxn.fee(2) + Gtxn.fee(3)), #? why only 1, 2, 3
-          Gtxn.asset_amount(0) == Gtxn.asset_amount(1),
-          Gtxn.asset_amount(1) == Gtxn.asset_amount(2),
-          Gtxn.asset_amount(3) == i_upper * supply * price_decrement,
-          Gtxn.sender(0) == Gtxn.receiver(1),
-          Gtxn.receiver(2) == asset_c_sink,
-          Gtxn.sender(1) == Gtxn.sender(2),
-          Gtxn.sender(2) == Gtxn.sender(3),
-          Gtxn.sender(3) == Gtxn.receiver(3),
-          Gtxn.receiver(3) == Gtxn.receiver(4))
+bid = And(Gtxn[0].last_valid() == Gtxn[1].last_valid(),
+          Gtxn[1].last_valid() == Gtxn[2].last_valid(),
+          Gtxn[2].last_valid() == Gtxn[3].last_valid(),
+          Gtxn[3].last_valid() == Gtxn[4].last_valid(),
+          Gtxn[4].last_valid() < (start_round + i_upper * period),
+          Gtxn[0].type_enum() == Int(4), # asset transfer
+          Gtxn[0].xfer_asset() == asset_d,
+          Gtxn[0].receiver() == receiver,
+          Gtxn[1].type_enum() == Int(4),
+          Gtxn[1].xfer_asset() == asset_b,
+          Gtxn[2].type_enum() == Int(4),
+          Gtxn[2].xfer_asset() == asset_c,
+          Gtxn[3].type_enum() == Int(4),
+          Gtxn[4].xfer_asset() == asset_c,
+          Gtxn[4].type_enum() == Int(1),
+          Gtxn[4].amount() == (Gtxn[1].fee() + Gtxn[2].fee() + Gtxn[3].fee()), #? why only 1, 2, 3
+          Gtxn[0].asset_amount() == Gtxn[1].asset_amount(),
+          Gtxn[1].asset_amount() == Gtxn[2].asset_amount(),
+          Gtxn[3].asset_amount() == i_upper * supply * price_decrement,
+          Gtxn[0].sender() == Gtxn[1].receiver(),
+          Gtxn[2].receiver() == asset_c_sink,
+          Gtxn[1].sender() == Gtxn[2].sender(),
+          Gtxn[2].sender() == Gtxn[3].sender(),
+          Gtxn[3].sender() == Gtxn[3].receiver(),
+          Gtxn[3].receiver() == Gtxn[4].receiver())
 
-redeem = And(Gtxn.first_valid(0) == Gtxn.first_valid(1),
-             Gtxn.first_valid(1) == Gtxn.first_valid(2),
-             Gtxn.first_valid(2) == Gtxn.first_valid(3),
-             Gtxn.first_valid(3) >= redeem_round,
-             Gtxn.type_enum(0) == Int(4),
-             Gtxn.xfer_asset(0) == asset_b,
-             Gtxn.type_enum(1) == Int(4),
-             Gtxn.xfer_asset(1) == asset_a,
-             Gtxn.type_enum(2) == Int(4),
-             Gtxn.xfer_asset(2) == asset_c,
-             Gtxn.asset_amount(2) == redeem_round * supply * price_decrement,
-             Gtxn.type_enum(3) == Int(1),
-             Gtxn.amount(3) == Gtxn.fee(1) + Gtxn.fee(2),
-             Gtxn.asset_amount(1) ==
-             Gtxn.asset_amount(0) * (start_price - price_decrement * Btoi(Arg(0))),
-             Gtxn.sender(0) == Gtxn.receiver(1),
-             Gtxn.receiver(0) == Gtxn.sender(1),
-             Gtxn.sender(1) == Gtxn.sender(2),
-             Gtxn.sender(2) == Gtxn.receiver(2),
-             Gtxn.receiver(2) == Gtxn.receiver(3))
+redeem = And(Gtxn[0].first_valid() == Gtxn[1].first_valid(),
+             Gtxn[1].first_valid() == Gtxn[2].first_valid(),
+             Gtxn[2].first_valid() == Gtxn[3].first_valid(),
+             Gtxn[3].first_valid() >= redeem_round,
+             Gtxn[0].type_enum() == Int(4),
+             Gtxn[0].xfer_asset() == asset_b,
+             Gtxn[1].type_enum() == Int(4),
+             Gtxn[1].xfer_asset() == asset_a,
+             Gtxn[2].type_enum() == Int(4),
+             Gtxn[2].xfer_asset() == asset_c,
+             Gtxn[2].asset_amount() == redeem_round * supply * price_decrement,
+             Gtxn[3].type_enum() == Int(1),
+             Gtxn[3].amount() == Gtxn[1].fee() + Gtxn[2].fee(),
+             Gtxn[1].asset_amount() ==
+             Gtxn[0].asset_amount() * (start_price - price_decrement * Btoi(Arg(0))),
+             Gtxn[0].sender() == Gtxn[1].receiver(),
+             Gtxn[0].receiver() == Gtxn[1].sender(),
+             Gtxn[1].sender() == Gtxn[2].sender(),
+             Gtxn[2].sender() == Gtxn[2].receiver(),
+             Gtxn[2].receiver() == Gtxn[3].receiver())
              
 wrapup = And(Global.group_size() == Int(1),
              Txn.first_valid() >= start_round + wrapup_time,
diff --git a/examples/periodic_payment.py b/examples/periodic_payment.py
index a9ef87a..abe28a0 100644
--- a/examples/periodic_payment.py
+++ b/examples/periodic_payment.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python3
+# This example is provided for informational purposes only and has not been audited for security.
 
 from pyteal import *
 
diff --git a/examples/recurring_swap.py b/examples/recurring_swap.py
index 66f14c3..be6eb6d 100644
--- a/examples/recurring_swap.py
+++ b/examples/recurring_swap.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python3
+# This example is provided for informational purposes only and has not been audited for security.
 
 from pyteal import *
 
diff --git a/examples/security_token.py b/examples/security_token.py
new file mode 100644
index 0000000..dd764f7
--- /dev/null
+++ b/examples/security_token.py
@@ -0,0 +1,231 @@
+# This example is provided for informational purposes only and has not been audited for security.
+
+from pyteal import *
+
+def approval_program():
+    on_creation = Seq([
+        Assert(Txn.application_args.length() == Int(1)),
+        App.globalPut(Bytes("total supply"), Btoi(Txn.application_args[0])),
+        App.globalPut(Bytes("reserve"), Btoi(Txn.application_args[0])),
+        App.globalPut(Bytes("paused"), Int(0)),
+        App.localPut(Int(0), Bytes("contract admin"), Int(1)),
+        App.localPut(Int(0), Bytes("transfer admin"), Int(1)),
+        App.localPut(Int(0), Bytes("balance"), Int(0)),
+        Return(Int(1))
+    ])
+
+    is_contract_admin = App.localGet(Int(0), Bytes("contract admin"))
+    is_transfer_admin = App.localGet(Int(0), Bytes("transfer admin"))
+    is_any_admin = is_contract_admin.Or(is_transfer_admin)
+
+    can_delete = And(
+        is_contract_admin,
+        App.globalGet(Bytes("total supply")) == App.globalGet(Bytes("reserve"))
+    )
+
+    on_closeout = Seq([
+        App.globalPut(
+            Bytes("reserve"),
+            App.globalGet(Bytes("reserve")) + App.localGet(Int(0), Bytes("balance"))
+        ),
+        Return(Int(1))
+    ])
+
+    register = Seq([
+        App.localPut(Int(0), Bytes("balance"), Int(0)),
+        Return(Int(1))
+    ])
+
+    # pause all transfers
+    # sender must be any admin
+    new_pause_value = Btoi(Txn.application_args[1])
+    pause = Seq([
+        Assert(Txn.application_args.length() == Int(2)),
+        App.globalPut(Bytes("paused"), new_pause_value),
+        Return(is_any_admin)
+    ])
+
+    # configure the admin status of the account Txn.accounts[0]
+    # sender must be contract admin
+    new_admin_type = Txn.application_args[1]
+    new_admin_status = Btoi(Txn.application_args[2])
+    set_admin = Seq([
+        Assert(And(
+            is_contract_admin,
+            Txn.application_args.length() == Int(3),
+            Or(new_admin_type == Bytes("contract admin"), new_admin_type == Bytes("transfer admin")),
+            Txn.accounts.length() == Int(1)
+        )),
+        App.localPut(Int(1), new_admin_type, new_admin_status),
+        Return(Int(1))
+    ])
+
+    # freeze Txn.accounts[0]
+    # sender must be any admin
+    new_freeze_value = Btoi(Txn.application_args[1])
+    freeze = Seq([
+        Assert(And(
+            Txn.application_args.length() == Int(2),
+            Txn.accounts.length() == Int(1)
+        )),
+        App.localPut(Int(1), Bytes("frozen"), new_freeze_value),
+        Return(is_any_admin)
+    ])
+
+    # modify the max balance of Txn.accounts[0]
+    # if max_balance_value is 0, will delete the exisint max balance limitation on the account
+    # sender must be transfer admin
+    max_balance_value = Btoi(Txn.application_args[1])
+    max_balance = Seq([
+        Assert(And(
+            Txn.application_args.length() == Int(2),
+            Txn.accounts.length() == Int(1)
+        )),
+        If(max_balance_value == Int(0),
+            App.localDel(Int(1), Bytes("max balance")),
+            App.localPut(Int(1), Bytes("max balance"), max_balance_value)
+        ),
+        Return(is_transfer_admin)
+    ])
+
+    # lock Txn.accounts[0] until a UNIX timestamp
+    # sender must be transfer admin
+    lock_until_value = Btoi(Txn.application_args[1])
+    lock_until = Seq([
+        Assert(And(
+            Txn.application_args.length() == Int(2),
+            Txn.accounts.length() == Int(1)
+        )),
+        If(lock_until_value == Int(0),
+            App.localDel(Int(1), Bytes("lock until")),
+            App.localPut(Int(1), Bytes("lock until"), lock_until_value)
+        ),
+        Return(is_transfer_admin)
+    ])
+
+    set_transfer_group = Seq([
+        Assert(And(
+            Txn.application_args.length() == Int(3),
+            Txn.accounts.length() == Int(1)
+        )),
+        App.localPut(Int(1), Bytes("transfer group"), Btoi(Txn.application_args[2]))
+    ])
+
+    def getRuleKey(sendGroup, receiveGroup):
+        return Concat(Bytes("rule"), Itob(sendGroup), Itob(receiveGroup))
+
+    lock_transfer_key = getRuleKey(Btoi(Txn.application_args[2]), Btoi(Txn.application_args[3]))
+    lock_transfer_until = Btoi(Txn.application_args[4])
+    lock_transfer_group = Seq([
+        Assert(Txn.application_args.length() == Int(5)),
+        If(lock_transfer_until == Int(0),
+            App.globalDel(lock_transfer_key),
+            App.globalPut(lock_transfer_key, lock_transfer_until)
+        )
+    ])
+
+    # sender must be transfer admin
+    transfer_group = Seq([
+        Assert(Txn.application_args.length() > Int(2)),
+        Cond(
+            [Txn.application_args[1] == Bytes("set"), set_transfer_group],
+            [Txn.application_args[1] == Bytes("lock"), lock_transfer_group]
+        ),
+        Return(is_transfer_admin)
+    ])
+
+    # move assets from the reserve to Txn.accounts[0]
+    # sender must be contract admin
+    mint_amount = Btoi(Txn.application_args[1])
+    mint = Seq([
+        Assert(And(
+            Txn.application_args.length() == Int(2),
+            Txn.accounts.length() == Int(1),
+            mint_amount <= App.globalGet(Bytes("reserve"))
+        )),
+        App.globalPut(Bytes("reserve"), App.globalGet(Bytes("reserve")) - mint_amount),
+        App.localPut(Int(1), Bytes("balance"), App.localGet(Int(1), Bytes("balance")) + mint_amount),
+        Return(is_contract_admin)
+    ])
+
+    # move assets from Txn.accounts[0] to the reserve
+    # sender must be contract admin
+    burn_amount = Btoi(Txn.application_args[1])
+    burn = Seq([
+        Assert(And(
+            Txn.application_args.length() == Int(2),
+            Txn.accounts.length() == Int(1),
+            burn_amount <= App.localGet(Int(1), Bytes("balance"))
+        )),
+        App.globalPut(Bytes("reserve"), App.globalGet(Bytes("reserve")) + burn_amount),
+        App.localPut(Int(1), Bytes("balance"), App.localGet(Int(1), Bytes("balance")) - burn_amount),
+        Return(is_contract_admin)
+    ])
+
+    # transfer assets from the sender to Txn.accounts[0]
+    transfer_amount = Btoi(Txn.application_args[1])
+    receiver_max_balance = App.localGetEx(Int(1), App.id(), Bytes("max balance"))
+    transfer = Seq([
+        Assert(And(
+            Txn.application_args.length() == Int(2),
+            Txn.accounts.length() == Int(1),
+            transfer_amount <= App.localGet(Int(0), Bytes("balance"))
+        )),
+        receiver_max_balance,
+        If(
+            Or(
+                App.globalGet(Bytes("paused")),
+                App.localGet(Int(0), Bytes("frozen")),
+                App.localGet(Int(1), Bytes("frozen")),
+                App.localGet(Int(0), Bytes("lock until")) >= Global.latest_timestamp(),
+                App.localGet(Int(1), Bytes("lock until")) >= Global.latest_timestamp(),
+                App.globalGet(getRuleKey(App.localGet(Int(0), Bytes("transfer group")), App.localGet(Int(1), Bytes("transfer group")))) >= Global.latest_timestamp(),
+                And(
+                    receiver_max_balance.hasValue(),
+                    receiver_max_balance.value() < App.localGet(Int(1), Bytes("balance")) + transfer_amount
+                )
+            ),
+            Return(Int(0))
+        ),
+        App.localPut(Int(0), Bytes("balance"), App.localGet(Int(0), Bytes("balance")) - transfer_amount),
+        App.localPut(Int(1), Bytes("balance"), App.localGet(Int(1), Bytes("balance")) + transfer_amount),
+        Return(Int(1))
+    ])
+
+    program = Cond(
+        [Txn.application_id() == Int(0), on_creation],
+        [Txn.on_completion() == OnComplete.DeleteApplication, Return(can_delete)],
+        [Txn.on_completion() == OnComplete.UpdateApplication, Return(is_contract_admin)],
+        [Txn.on_completion() == OnComplete.CloseOut, on_closeout],
+        [Txn.on_completion() == OnComplete.OptIn, register],
+        [Txn.application_args[0] == Bytes("pause"), pause],
+        [Txn.application_args[0] == Bytes("set admin"), set_admin],
+        [Txn.application_args[0] == Bytes("freeze"), freeze],
+        [Txn.application_args[0] == Bytes("max balance"), max_balance],
+        [Txn.application_args[0] == Bytes("lock until"), lock_until],
+        [Txn.application_args[0] == Bytes("transfer group"), transfer_group],
+        [Txn.application_args[0] == Bytes("mint"), mint],
+        [Txn.application_args[0] == Bytes("burn"), burn],
+        [Txn.application_args[0] == Bytes("transfer"), transfer],
+    )
+
+    return program
+
+def close_out_program():
+    program = Seq([
+        App.globalPut(
+            Bytes("reserve"),
+            App.globalGet(Bytes("reserve")) + App.localGet(Int(0), Bytes("balance"))
+        ),
+        Return(Int(1))
+    ])
+
+    return program
+
+with open('security_token_approval.teal', 'w') as f:
+    compiled = compileTeal(approval_program(), Mode.Application)
+    f.write(compiled)
+
+with open('security_token_close_out.teal', 'w') as f:
+    compiled = compileTeal(close_out_program(), Mode.Application)
+    f.write(compiled)
diff --git a/examples/split.py b/examples/split.py
index 387206e..fe9e82a 100644
--- a/examples/split.py
+++ b/examples/split.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python3
+# This example is provided for informational purposes only and has not been audited for security.
 
 from pyteal import *
 
diff --git a/examples/vote.py b/examples/vote.py
new file mode 100644
index 0000000..8023528
--- /dev/null
+++ b/examples/vote.py
@@ -0,0 +1,102 @@
+# This example is provided for informational purposes only and has not been audited for security.
+
+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 = App.localGetEx(Int(0), Txn.application_id(), Bytes("voted"))
+    get_count = App.globalGetEx(Int(0), get_vote.value())
+
+    decrement_existing = Seq([
+        App.globalPut(get_vote.value(), get_count.value() - Int(1)),
+        Return(Int(1))
+    ])
+
+    if_voted = Seq([
+        get_count,
+        If(get_count.hasValue(), decrement_existing, Return(Int(1)))
+    ])
+
+    on_closeout = If(
+        Global.round() > App.globalGet(Bytes("VoteEnd")),
+        Return(Int(1)),
+        Seq([
+            get_vote,
+            If(get_vote.hasValue(), if_voted, Return(Int(1)))
+        ])
+    )
+
+    on_register = Return(And(
+        Global.round() >= App.globalGet(Bytes("RegBegin")),
+        Global.round() <= App.globalGet(Bytes("RegEnd"))
+    ))
+
+    get_candidate = App.globalGetEx(Int(0), Txn.application_args[1])
+
+    not_voted = Seq([
+        get_candidate,
+        App.globalPut(Txn.application_args[1], If(get_candidate.hasValue(), get_candidate.value() + Int(1), Int(1))),
+        App.localPut(Int(0), Bytes("voted"), Txn.application_args[1]),
+        Return(Int(1))
+    ])
+
+    on_vote = Seq([
+        Assert(And(Global.round() >= App.globalGet(Bytes("VoteBegin")), Global.round() <= App.globalGet(Bytes("VoteEnd")))),
+        get_vote,
+        If(get_vote.hasValue(), Return(Int(1)), not_voted)
+    ])
+
+    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],
+        [Bytes("vote") == Txn.application_args[0], on_vote]
+    )
+
+    return program
+
+def close_out_program():
+    get_voted = App.localGetEx(Int(0), Txn.application_id(), Bytes("voted"))
+    get_candidate = App.globalGetEx(Int(0), get_voted.value())
+
+    decrement_existing = Seq([
+        App.globalPut(get_voted.value(), get_candidate.value() - Int(1)),
+        Return(Int(1))
+    ])
+
+    voted = Seq([
+        get_candidate,
+        If(get_candidate.hasValue(), decrement_existing, Return(Int(1)))
+    ])
+
+    program = If(
+        Global.round() > App.globalGet(Bytes("VoteEnd")),
+        Return(Int(1)),
+        Seq([
+            get_voted,
+            If(get_voted.hasValue(), voted, Return(Int(1)))
+        ])
+    )
+
+    return program
+
+with open('vote_approval.teal', 'w') as f:
+    compiled = compileTeal(approval_program(), Mode.Application)
+    f.write(compiled)
+
+with open('vote_close_out.teal', 'w') as f:
+    compiled = compileTeal(close_out_program(), Mode.Application)
+    f.write(compiled)
diff --git a/pyteal/ast/__init__.py b/pyteal/ast/__init__.py
index ae73045..5e54cbd 100644
--- a/pyteal/ast/__init__.py
+++ b/pyteal/ast/__init__.py
@@ -10,8 +10,8 @@ from .int import Int, EnumInt
 
 # properties
 from .arg import Arg
-from .txn import TxnType, TxnField, Txn, Txna
-from .gtxn import Gtxn
+from .txn import TxnType, TxnField, TxnExpr, TxnaExpr, Txn
+from .gtxn import GtxnExpr, GtxnaExpr, Gtxn
 from .global_ import Global, GlobalField
 from .app import App, AppField, OnComplete
 from .asset import AssetHolding, AssetParam
diff --git a/pyteal/ast/array.py b/pyteal/ast/array.py
new file mode 100644
index 0000000..5e33f35
--- /dev/null
+++ b/pyteal/ast/array.py
@@ -0,0 +1,16 @@
+from abc import ABC, abstractmethod
+
+from .expr import Expr
+
+class Array(ABC):
+    """Represents a variable length array of objects."""
+    
+    @abstractmethod
+    def length(self) -> Expr:
+        """Get the length of the array."""
+        pass
+    
+    @abstractmethod
+    def __getitem__(self, index: int):
+        """Get the value at a given index in this array."""
+        pass
diff --git a/pyteal/ast/gtxn.py b/pyteal/ast/gtxn.py
index 16a6c30..2dd0266 100644
--- a/pyteal/ast/gtxn.py
+++ b/pyteal/ast/gtxn.py
@@ -1,249 +1,44 @@
-from ..types import TealType, require_type
+from ..types import TealType
 from ..ir import TealOp, Op
-from ..errors import TealInputError, TealTypeError
+from ..errors import TealInputError
 from ..config import MAX_GROUP_SIZE
+from .expr import Expr
 from .leafexpr import LeafExpr
-from .txn import TxnField
+from .txn import TxnField, TxnExpr, TxnaExpr, TxnObject
+from .global_ import Global
 
-class Gtxna(LeafExpr):
-    """An expression that accesses a transaction array field from a transaction in the current group."""
-
-    def __init__(self, txIndex: int, field: TxnField, index: int) -> None:
-        if type(index) != int:
-            raise TealTypeError(type(index), int)
-        if index < 0 or index >= MAX_GROUP_SIZE:
-            raise TealInputError("invalid Gtxn index {}, shoud [0, {})".format(index, MAX_GROUP_SIZE))
-        self.txIndex = txIndex
-        self.field = field
-        self.index = index
-    
-    def __str__(self):
-        return "(Gxna {} {} {})".format(self.txIndex, self.field.arg_name, self.index)
-    
-    def __teal__(self):
-        return [TealOp(Op.gtxna, self.txIndex, self.field.arg_name, self.index)]
-    
-    def type_of(self):
-        return self.field.type_of()
-
-class Gtxn(LeafExpr):
+class GtxnExpr(TxnExpr):
     """An expression that accesses a transaction field from a transaction in the current group."""
 
-    def __init__(self, index:int, field:TxnField) -> None:
-        if type(index) != int:
-            raise TealTypeError(type(index), int)
-        if index < 0 or index >= MAX_GROUP_SIZE:
-            raise TealInputError("invalid Gtxn index {}, shoud [0, {})".format(index, MAX_GROUP_SIZE))
-        self.index = index
-        self.field = field
+    def __init__(self, txnIndex: int, field: TxnField) -> None:
+        super().__init__(field)
+        self.txnIndex = txnIndex
 
     def __str__(self):
-        return "(Gtxn {} {})".format(self.index, self.field.arg_name)
+        return "(Gtxn {} {})".format(self.txnIndex, self.field.arg_name)
 
     def __teal__(self):
-        return [TealOp(Op.gtxn, self.index, self.field.arg_name)]
-    
-    def type_of(self):
-        return self.field.type_of()
-
-    @classmethod
-    def sender(cls, index):
-        return cls(index, TxnField.sender)
-   
-    @classmethod
-    def fee(cls, index):
-        return cls(index, TxnField.fee)
-
-    @classmethod
-    def first_valid(cls, index):
-        return cls(index, TxnField.first_valid)
-
-    @classmethod
-    def lease(cls, index):
-        return cls(index, TxnField.lease)
-   
-    @classmethod
-    def last_valid(cls, index):
-        return cls(index, TxnField.last_valid)
-
-    @classmethod
-    def note(cls, index):
-        return cls(index, TxnField.note)
-
-    @classmethod
-    def receiver(cls, index):
-        return cls(index, TxnField.receiver)
-
-    @classmethod
-    def amount(cls, index):
-        return cls(index, TxnField.amount)
-
-    @classmethod
-    def close_remainder_to(cls, index):
-        return cls(index, TxnField.close_remainder_to)
-
-    @classmethod
-    def vote_pk(cls, index):
-        return cls(index, TxnField.vote_pk)
-
-    @classmethod
-    def selection_pk(cls, index):
-        return cls(index, TxnField.selection_pk)
-
-    @classmethod
-    def vote_first(cls, index):
-        return cls(index, TxnField.vote_first)
-
-    @classmethod
-    def vote_last(cls, index):
-        return cls(index, TxnField.vote_last)
+        return [TealOp(Op.gtxn, self.txnIndex, self.field.arg_name)]
 
-    @classmethod
-    def vote_key_dilution(cls, index):
-        return cls(index, TxnField.vote_key_dilution)
-
-    @classmethod
-    def type(cls, index):
-        return cls(index, TxnField.type)
-
-    @classmethod
-    def type_enum(cls, index):
-        return cls(index, TxnField.type_enum)
-
-    @classmethod
-    def xfer_asset(cls, index):
-        return cls(index, TxnField.xfer_asset)
-
-    @classmethod
-    def asset_amount(cls, index):
-        return cls(index, TxnField.asset_amount)
-
-    @classmethod
-    def asset_sender(cls, index):
-        return cls(index, TxnField.asset_sender)
-
-    @classmethod
-    def asset_receiver(cls, index):
-        return cls(index, TxnField.asset_receiver)
-
-    @classmethod
-    def asset_close_to(cls, index):
-        return cls(index, TxnField.asset_close_to)
-
-    @classmethod
-    def group_index(cls, index):
-        return cls(index, TxnField.group_index)
-
-    @classmethod
-    def tx_id(cls, index):
-        return cls(index, TxnField.tx_id)
-
-    @classmethod
-    def application_id(cls, index):
-        return cls(index, TxnField.application_id)
-
-    @classmethod
-    def on_completion(cls, index):
-        return cls(index, TxnField.on_completion)
-
-    @classmethod
-    def approval_program(cls, index):
-        return cls(index, TxnField.approval_program)
-    
-    @classmethod
-    def clear_state_program(cls, index):
-        return cls(index, TxnField.clear_state_program)
-    
-    @classmethod
-    def rekey_to(cls, index):
-        return cls(index, TxnField.rekey_to)
-    
-    @classmethod
-    def config_asset(cls, index):
-        return cls(index, TxnField.config_asset)
-    
-    @classmethod
-    def config_asset_total(cls, index):
-        return cls(index, TxnField.config_asset_total)
+class GtxnaExpr(TxnaExpr):
+    """An expression that accesses a transaction array field from a transaction in the current group."""
 
-    @classmethod
-    def config_asset_decimals(cls, index):
-        return cls(index, TxnField.config_asset_decimals)
-    
-    @classmethod
-    def config_asset_default_frozen(cls, index):
-        return cls(index, TxnField.config_asset_default_frozen)
-    
-    @classmethod
-    def config_asset_unit_name(cls, index):
-        return cls(index, TxnField.config_asset_unit_name)
+    def __init__(self, txnIndex: int, field: TxnField, index: int) -> None:
+        super().__init__(field, index)
+        self.txnIndex = txnIndex
 
-    @classmethod
-    def config_asset_name(cls, index):
-        return cls(index, TxnField.config_asset_name)
-    
-    @classmethod
-    def config_asset_url(cls, index):
-        return cls(index, TxnField.config_asset_url)
-    
-    @classmethod
-    def config_asset_metadata_hash(cls, index):
-        return cls(index, TxnField.config_asset_metadata_hash)
-    
-    @classmethod
-    def config_asset_manager(cls, index):
-        return cls(index, TxnField.config_asset_manager)
-    
-    @classmethod
-    def config_asset_reserve(cls, index):
-        return cls(index, TxnField.config_asset_reserve)
-    
-    @classmethod
-    def config_asset_freeze(cls, index):
-        return cls(index, TxnField.config_asset_freeze)
-    
-    @classmethod
-    def config_asset_clawback(cls, index):
-        return cls(index, TxnField.config_asset_clawback)
-    
-    @classmethod
-    def freeze_asset(cls, index):
-        return cls(index, TxnField.freeze_asset)
-    
-    @classmethod
-    def freeze_asset_account(cls, index):
-        return cls(index, TxnField.freeze_asset_account)
-    
-    @classmethod
-    def freeze_asset_frozen(cls, index):
-        return cls(index, TxnField.freeze_asset_frozen)
-    
-    class ArrayAccessor:
+    def __str__(self):
+        return "(Gtxna {} {} {})".format(self.index, self.field.arg_name, self.index)
 
-        def __init__(self, txIndex: int, accessField: TxnField, lengthField: TxnField) -> None:
-            self.txIndex = txIndex
-            self.accessField = accessField
-            self.lengthField = lengthField
-        
-        def length(self):
-            """Get the length of this array."""
-            return Gtxn(self.txIndex, self.lengthField)
-        
-        def __getitem__(self, index: int):
-            """Get the value at an index in this array.
-            
-            Args:
-                index: Must not be negative.
-            """
-            if not isinstance(index, int) or index < 0:
-                raise TealInputError("Invalid array index: {}".format(index))
+    def __teal__(self):
+        return [TealOp(Op.gtxna, self.txnIndex, self.field.arg_name, self.index)]
 
-            return Gtxna(self.txIndex, self.accessField, index)
+class TxnGroup:
+    """Represents a group of transactions."""
 
-    @classmethod
-    def application_args(cls, index: int):
-        return cls.ArrayAccessor(index, TxnField.application_args, TxnField.num_app_args)
+    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))
+        return TxnObject(lambda field: GtxnExpr(txnIndex, field), lambda field, index: GtxnaExpr(txnIndex, field, index))
 
-    @classmethod
-    def accounts(cls, index: int):
-        return cls.ArrayAccessor(index, TxnField.accounts, TxnField.num_accounts)
+Gtxn: TxnGroup = TxnGroup()
diff --git a/pyteal/ast/if_.py b/pyteal/ast/if_.py
index bb05215..aaa0252 100644
--- a/pyteal/ast/if_.py
+++ b/pyteal/ast/if_.py
@@ -6,44 +6,57 @@ from .expr import Expr
 class If(Expr):
     """Simple two-way conditional expression."""
 
-    def __init__(self, cond: Expr, thenBranch: Expr, elseBranch: Expr) -> None:
+    def __init__(self, cond: Expr, thenBranch: Expr, elseBranch: Expr = None) -> None:
         """Create a new If expression.
 
         When this If expression is executed, the condition will be evaluated, and if it produces a
         true value, thenBranch will be executed and used as the return value for this expression.
-        Otherwise, elseBranch will be executed and used as the return value.
+        Otherwise, elseBranch will be executed and used as the return value, if it is provided.
 
         Args:
             cond: The condition to check. Must evaluate to uint64.
             thenBranch: Expression to evaluate if the condition is true.
-            elseBranch: Expression to evaluate if the condition is false. Must evaluate to the same
-            type as thenBranch.
+            elseBranch (optional): Expression to evaluate if the condition is false. Must evaluate
+            to the same type as thenBranch, if provided. Defaults to None.
         """
         require_type(cond.type_of(), TealType.uint64)
-        require_type(thenBranch.type_of(), elseBranch.type_of())
 
+        if elseBranch is None:
+            require_type(thenBranch.type_of(), TealType.none)
+        else:
+            require_type(thenBranch.type_of(), elseBranch.type_of())
+        
         self.cond = cond
         self.thenBranch = thenBranch
         self.elseBranch = elseBranch
 
     def __teal__(self):
-        cond = self.cond.__teal__()
-        l1 = new_label()
-        t_branch = self.thenBranch.__teal__()
-        e_branch = self.elseBranch.__teal__()
-        l2 = new_label()
-
-        teal = cond
-        teal.append(TealOp(Op.bnz, l1))
-        teal += e_branch
-        teal.append(TealOp(Op.b, l2))
-        teal.append(TealLabel(l1))
-        teal += t_branch
-        teal.append(TealLabel(l2))
+        teal = self.cond.__teal__()
+        end = new_label()
+        tealThen = self.thenBranch.__teal__()
+        tealElse = [] if self.elseBranch is None else self.elseBranch.__teal__()
+
+        if self.elseBranch is None:
+            teal.append(TealOp(Op.bz, end))
+            teal += tealThen
+        else:
+            # doing this swap so that labels remain consistent with previous If implementation.
+            then = end
+            end = new_label()
+
+            teal.append(TealOp(Op.bnz, then))
+            teal += tealElse
+            teal.append(TealOp(Op.b, end))
+            teal.append(TealLabel(then))
+            teal += tealThen
+        
+        teal.append(TealLabel(end))
 
         return teal
 
     def __str__(self):
+        if self.elseBranch is None:
+            return "(If {} {})".format(self.cond, self.thenBranch)
         return "(If {} {} {})".format(self.cond, self.thenBranch, self.elseBranch)
 
     def type_of(self):
diff --git a/pyteal/ast/txn.py b/pyteal/ast/txn.py
index e5e6c49..9c7ab24 100644
--- a/pyteal/ast/txn.py
+++ b/pyteal/ast/txn.py
@@ -1,10 +1,12 @@
 from enum import Enum
+from typing import Callable
 
 from ..types import TealType
 from ..errors import TealInputError
 from ..ir import TealOp, Op
 from .leafexpr import LeafExpr
 from .int import EnumInt
+from .array import Array
 
 class TxnType:
     """Enum of all possible transaction types."""
@@ -74,7 +76,22 @@ class TxnField(Enum):
     def type_of(self) -> TealType:
         return self.ret_type
 
-class Txna(LeafExpr):
+class TxnExpr(LeafExpr):
+    """An expression that accesses a transaction field from the current transaction."""
+
+    def __init__(self, field: TxnField) -> None:
+        self.field = field
+
+    def __str__(self):
+        return "(Txn {})".format(self.field.arg_name)
+
+    def __teal__(self):
+        return [TealOp(Op.txn, self.field.arg_name)]
+    
+    def type_of(self):
+        return self.field.type_of()
+
+class TxnaExpr(LeafExpr):
     """An expression that accesses a transaction array field from the current transaction."""
 
     def __init__(self, field: TxnField, index: int) -> None:
@@ -90,261 +107,212 @@ class Txna(LeafExpr):
     def type_of(self):
         return self.field.type_of()
 
-class Txn(LeafExpr):
-    """An expression that accesses a transaction field from the current transaction."""
+class TxnArray(Array):
+    """Represents a transaction array field."""
 
-    def __init__(self, field: TxnField) -> None:
-        self.field = field
+    def __init__(self, txnObject: 'TxnObject', accessField: TxnField, lengthField: TxnField) -> None:
+        self.txnObject = txnObject
+        self.accessField = accessField
+        self.lengthField = lengthField
+    
+    def length(self) -> TxnExpr:
+        return self.txnObject.txnType(self.lengthField)
+    
+    def __getitem__(self, index: int) -> TxnaExpr:
+        if not isinstance(index, int) or index < 0:
+            raise TealInputError("Invalid array index: {}".format(index))
 
-    def __str__(self):
-        return "(Txn {})".format(self.field.arg_name)
+        return self.txnObject.txnaType(self.accessField, index)
 
-    def __teal__(self):
-        return [TealOp(Op.txn, self.field.arg_name)]
-    
-    def type_of(self):
-        return self.field.type_of()
+class TxnObject:
+    """Represents a transaction and its fields."""
 
-    @classmethod
-    def sender(cls):
-        """Get the 32 byte address of the sender."""
-        return cls(TxnField.sender)
+    def __init__(self, txnType: Callable[[TxnField], TxnExpr], txnaType: Callable[[TxnField, int], TxnaExpr]) -> None:
+        self.txnType = txnType
+        self.txnaType = txnaType
 
-    @classmethod
-    def fee(cls):
+    def sender(self) -> TxnExpr:
+        """Get the 32 byte address of the sender."""
+        return self.txnType(TxnField.sender)
+   
+    def fee(self) -> TxnExpr:
         """Get the transaction fee in micro Algos."""
-        return cls(TxnField.fee)
+        return self.txnType(TxnField.fee)
 
-    @classmethod
-    def first_valid(cls):
+    def first_valid(self) -> TxnExpr:
         """Get the first valid round number."""
-        return cls(TxnField.first_valid)
+        return self.txnType(TxnField.first_valid)
    
-    @classmethod
-    def last_valid(cls):
+    def last_valid(self) -> TxnExpr:
         """Get the last valid round number."""
-        return cls(TxnField.last_valid)
+        return self.txnType(TxnField.last_valid)
 
-    @classmethod
-    def note(cls):
+    def note(self) -> TxnExpr:
         """Get the transaction note."""
-        return cls(TxnField.note)
+        return self.txnType(TxnField.note)
 
-    @classmethod
-    def lease(cls):
+    def lease(self) -> TxnExpr:
         """Get the transaction lease."""
-        return cls(TxnField.lease)
-   
-    @classmethod
-    def receiver(cls):
+        return self.txnType(TxnField.lease)
+
+    def receiver(self) -> TxnExpr:
         """Get the 32 byte address of the receiver."""
-        return cls(TxnField.receiver)
+        return self.txnType(TxnField.receiver)
 
-    @classmethod
-    def amount(cls):
+    def amount(self) -> TxnExpr:
         """Get the amount of the transaction in micro Algos."""
-        return cls(TxnField.amount)
+        return self.txnType(TxnField.amount)
 
-    @classmethod
-    def close_remainder_to(cls):
+    def close_remainder_to(self) -> TxnExpr:
         """Get the 32 byte address of the CloseRemainderTo field."""
-        return cls(TxnField.close_remainder_to)
+        return self.txnType(TxnField.close_remainder_to)
 
-    @classmethod
-    def vote_pk(cls):
-        return cls(TxnField.vote_pk)
+    def vote_pk(self) -> TxnExpr:
+        return self.txnType(TxnField.vote_pk)
 
-    @classmethod
-    def selection_pk(cls):
-        return cls(TxnField.selection_pk)
+    def selection_pk(self) -> TxnExpr:
+        return self.txnType(TxnField.selection_pk)
 
-    @classmethod
-    def vote_first(cls):
-        return cls(TxnField.vote_first)
+    def vote_first(self) -> TxnExpr:
+        return self.txnType(TxnField.vote_first)
 
-    @classmethod
-    def vote_last(cls):
-        return cls(TxnField.vote_last)
+    def vote_last(self) -> TxnExpr:
+        return self.txnType(TxnField.vote_last)
 
-    @classmethod
-    def vote_key_dilution(cls):
-        return cls(TxnField.vote_key_dilution)
+    def vote_key_dilution(self) -> TxnExpr:
+        return self.txnType(TxnField.vote_key_dilution)
 
-    @classmethod
-    def type(cls):
-        return cls(TxnField.type)
+    def type(self) -> TxnExpr:
+        return self.txnType(TxnField.type)
 
-    @classmethod
-    def type_enum(cls):
+    def type_enum(self) -> TxnExpr:
         """Get the type of this transaction.
         
         See the TxnType enum for possible values.
         """
-        return cls(TxnField.type_enum)
+        return self.txnType(TxnField.type_enum)
 
-    @classmethod
-    def xfer_asset(cls):
+    def xfer_asset(self) -> TxnExpr:
         """The ID of the asset being transferred."""
-        return cls(TxnField.xfer_asset)
+        return self.txnType(TxnField.xfer_asset)
 
-    @classmethod
-    def asset_amount(cls):
+    def asset_amount(self) -> TxnExpr:
         """The the amount of the asset being transferred, measured in the asset's units."""
-        return cls(TxnField.asset_amount)
+        return self.txnType(TxnField.asset_amount)
 
-    @classmethod
-    def asset_sender(cls):
+    def asset_sender(self) -> TxnExpr:
         """Get the 32 byte address of the subject of clawback.
         
         The transaction will clawback of all of an asset from this address if the transaction sender
         is the clawback address of the asset.
         """
-        return cls(TxnField.asset_sender)
+        return self.txnType(TxnField.asset_sender)
 
-    @classmethod
-    def asset_receiver(cls):
-        return cls(TxnField.asset_receiver)
+    def asset_receiver(self) -> TxnExpr:
+        return self.txnType(TxnField.asset_receiver)
 
-    @classmethod
-    def asset_close_to(cls):
-        return cls(TxnField.asset_close_to)
+    def asset_close_to(self) -> TxnExpr:
+        return self.txnType(TxnField.asset_close_to)
 
-    @classmethod
-    def group_index(cls):
-        """Get the position of the current transaction within the atomic transaction group.
+    def group_index(self) -> TxnExpr:
+        """Get the position of the transaction within the atomic transaction group.
         
         A stand-alone transaction is implictly element 0 in a group of 1.
         """
-        return cls(TxnField.group_index)
+        return self.txnType(TxnField.group_index)
 
-    @classmethod
-    def tx_id(cls):
-        """Get the 32 byte computed ID for the current transaction."""
-        return cls(TxnField.tx_id)
-    
-    @classmethod
-    def application_id(cls):
-        """Get the application ID from the ApplicationCall portion of the current transaction."""
-        return cls(TxnField.application_id)
-
-    @classmethod
-    def on_completion(cls):
-        """Get the on completion action from the ApplicationCall portion of the current transaction."""
-        return cls(TxnField.on_completion)
-
-    @classmethod
-    def approval_program(cls):
+    def tx_id(self) -> TxnExpr:
+        """Get the 32 byte computed ID for the transaction."""
+        return self.txnType(TxnField.tx_id)
+
+    def application_id(self) -> TxnExpr:
+        """Get the application ID from the ApplicationCall portion of the transaction."""
+        return self.txnType(TxnField.application_id)
+
+    def on_completion(self) -> TxnExpr:
+        """Get the on completion action from the ApplicationCall portion of the transaction."""
+        return self.txnType(TxnField.on_completion)
+
+    def approval_program(self) -> TxnExpr:
         """Get the approval program."""
-        return cls(TxnField.approval_program)
-    
-    @classmethod
-    def clear_state_program(cls):
+        return self.txnType(TxnField.approval_program)
+
+    def clear_state_program(self) -> TxnExpr:
         """Get the clear state program."""
-        return cls(TxnField.clear_state_program)
-    
-    @classmethod
-    def rekey_to(cls):
+        return self.txnType(TxnField.clear_state_program)
+
+    def rekey_to(self) -> TxnExpr:
         """Get the sender's new 32 byte AuthAddr"""
-        return cls(TxnField.rekey_to)
-    
-    @classmethod
-    def config_asset(cls):
+        return self.txnType(TxnField.rekey_to)
+
+    def config_asset(self) -> TxnExpr:
         """Get the asset ID in asset config transaction."""
-        return cls(TxnField.config_asset)
-    
-    @classmethod
-    def config_asset_total(cls):
+        return self.txnType(TxnField.config_asset)
+
+    def config_asset_total(self) -> TxnExpr:
         """Get the total number of units of this asset created."""
-        return cls(TxnField.config_asset_total)
+        return self.txnType(TxnField.config_asset_total)
 
-    @classmethod
-    def config_asset_decimals(cls):
+    def config_asset_decimals(self) -> TxnExpr:
         """Get the number of digits to display after the decimal place when displaying the asset."""
-        return cls(TxnField.config_asset_decimals)
-    
-    @classmethod
-    def config_asset_default_frozen(cls):
+        return self.txnType(TxnField.config_asset_decimals)
+
+    def config_asset_default_frozen(self) -> TxnExpr:
         """Check if the asset's slots are frozen by default or not."""
-        return cls(TxnField.config_asset_default_frozen)
-    
-    @classmethod
-    def config_asset_unit_name(cls):
+        return self.txnType(TxnField.config_asset_default_frozen)
+
+    def config_asset_unit_name(self) -> TxnExpr:
         """Get the unit name of the asset."""
-        return cls(TxnField.config_asset_unit_name)
+        return self.txnType(TxnField.config_asset_unit_name)
 
-    @classmethod
-    def config_asset_name(cls):
+    def config_asset_name(self) -> TxnExpr:
         """Get the asset name."""
-        return cls(TxnField.config_asset_name)
-    
-    @classmethod
-    def config_asset_url(cls):
+        return self.txnType(TxnField.config_asset_name)
+
+    def config_asset_url(self) -> TxnExpr:
         """Get the asset URL."""
-        return cls(TxnField.config_asset_url)
-    
-    @classmethod
-    def config_asset_metadata_hash(cls):
+        return self.txnType(TxnField.config_asset_url)
+
+    def config_asset_metadata_hash(self) -> TxnExpr:
         """Get the 32 byte commitment to some unspecified asset metdata."""
-        return cls(TxnField.config_asset_metadata_hash)
-    
-    @classmethod
-    def config_asset_manager(cls):
+        return self.txnType(TxnField.config_asset_metadata_hash)
+
+    def config_asset_manager(self) -> TxnExpr:
         """Get the 32 byte asset manager address."""
-        return cls(TxnField.config_asset_manager)
-    
-    @classmethod
-    def config_asset_reserve(cls):
+        return self.txnType(TxnField.config_asset_manager)
+
+    def config_asset_reserve(self) -> TxnExpr:
         """Get the 32 byte asset reserve address."""
-        return cls(TxnField.config_asset_reserve)
-    
-    @classmethod
-    def config_asset_freeze(cls):
+        return self.txnType(TxnField.config_asset_reserve)
+
+    def config_asset_freeze(self) -> TxnExpr:
         """Get the 32 byte asset freeze address."""
-        return cls(TxnField.config_asset_freeze)
-    
-    @classmethod
-    def config_asset_clawback(cls):
+        return self.txnType(TxnField.config_asset_freeze)
+
+    def config_asset_clawback(self) -> TxnExpr:
         """Get the 32 byte asset clawback address."""
-        return cls(TxnField.config_asset_clawback)
-    
-    @classmethod
-    def freeze_asset(cls):
+        return self.txnType(TxnField.config_asset_clawback)
+
+    def freeze_asset(self) -> TxnExpr:
         """Get the asset ID being frozen or un-frozen."""
-        return cls(TxnField.freeze_asset)
-    
-    @classmethod
-    def freeze_asset_account(cls):
+        return self.txnType(TxnField.freeze_asset)
+
+    def freeze_asset_account(self) -> TxnExpr:
         """Get the 32 byte address of the account whose asset slot is being frozen or un-frozen."""
-        return cls(TxnField.freeze_asset_account)
-    
-    @classmethod
-    def freeze_asset_frozen(cls):
+        return self.txnType(TxnField.freeze_asset_account)
+
+    def freeze_asset_frozen(self) -> TxnExpr:
         """Get the new frozen value for the asset."""
-        return cls(TxnField.freeze_asset_frozen)
-    
-    class ArrayAccessor:
+        return self.txnType(TxnField.freeze_asset_frozen)
 
-        def __init__(self, accessField: TxnField, lengthField: TxnField) -> None:
-            self.accessField = accessField
-            self.lengthField = lengthField
-        
-        def length(self):
-            """Get the length of this array."""
-            return Txn(self.lengthField)
-        
-        def __getitem__(self, index: int):
-            """Get the value at an index in this array.
-            
-            Args:
-                index: Must not be negative.
-            """
-            if not isinstance(index, int) or index < 0:
-                raise TealInputError("Invalid array index: {}".format(index))
-
-            return Txna(self.accessField, index)
-    
-    """Application args array"""
-    application_args = ArrayAccessor(TxnField.application_args, TxnField.num_app_args)
+    @property
+    def application_args(self) -> TxnArray:
+        """Application arguments array."""
+        return TxnArray(self, TxnField.application_args, TxnField.num_app_args)
+
+    @property
+    def accounts(self) -> TxnArray:
+        """Accounts array."""
+        return TxnArray(self, TxnField.accounts, TxnField.num_accounts)
 
-    """Accounts array"""
-    accounts = ArrayAccessor(TxnField.accounts, TxnField.num_accounts)
+Txn: TxnObject = TxnObject(TxnExpr, TxnaExpr)


Test patch:
diff --git a/pyteal/ast/gtxn_test.py b/pyteal/ast/gtxn_test.py
index 47c5667..33cbdf0 100644
--- a/pyteal/ast/gtxn_test.py
+++ b/pyteal/ast/gtxn_test.py
@@ -6,14 +6,14 @@ GTXN_RANGE = range(MAX_GROUP_SIZE)
 
 def test_gtxn_invalid():
     with pytest.raises(TealInputError):
-        Gtxn(-1, TxnField.fee)
+        Gtxn[-1].fee()
 
     with pytest.raises(TealInputError):
-        Gtxn(MAX_GROUP_SIZE+1, TxnField.sender)
+        Gtxn[MAX_GROUP_SIZE+1].sender()
 
 def test_gtxn_sender():
     for i in GTXN_RANGE:
-        expr = Gtxn.sender(i)
+        expr = Gtxn[i].sender()
         assert expr.type_of() == TealType.bytes
         assert expr.__teal__() == [
             TealOp(Op.gtxn, i, "Sender")
@@ -21,7 +21,7 @@ def test_gtxn_sender():
 
 def test_gtxn_fee():
     for i in GTXN_RANGE:
-        expr = Gtxn.fee(i)
+        expr = Gtxn[i].fee()
         assert expr.type_of() == TealType.uint64
         assert expr.__teal__() == [
             TealOp(Op.gtxn, i, "Fee")
@@ -29,7 +29,7 @@ def test_gtxn_fee():
 
 def test_gtxn_first_valid():
     for i in GTXN_RANGE:
-        expr = Gtxn.first_valid(i)
+        expr = Gtxn[i].first_valid()
         assert expr.type_of() == TealType.uint64
         assert expr.__teal__() == [
             TealOp(Op.gtxn, i, "FirstValid")
@@ -37,7 +37,7 @@ def test_gtxn_first_valid():
 
 def test_gtxn_last_valid():
     for i in GTXN_RANGE:
-        expr = Gtxn.last_valid(i)
+        expr = Gtxn[i].last_valid()
         assert expr.type_of() == TealType.uint64
         assert expr.__teal__() == [
             TealOp(Op.gtxn, i, "LastValid")
@@ -45,7 +45,7 @@ def test_gtxn_last_valid():
 
 def test_gtxn_note():
     for i in GTXN_RANGE:
-        expr = Gtxn.note(i)
+        expr = Gtxn[i].note()
         assert expr.type_of() == TealType.bytes
         assert expr.__teal__() == [
             TealOp(Op.gtxn, i, "Note")
@@ -53,7 +53,7 @@ def test_gtxn_note():
 
 def test_gtxn_lease():
     for i in GTXN_RANGE:
-        expr = Gtxn.lease(i)
+        expr = Gtxn[i].lease()
         assert expr.type_of() == TealType.bytes
         assert expr.__teal__() == [
             TealOp(Op.gtxn, i, "Lease")
@@ -61,7 +61,7 @@ def test_gtxn_lease():
 
 def test_gtxn_receiver():
     for i in GTXN_RANGE:
-        expr = Gtxn.receiver(i)
+        expr = Gtxn[i].receiver()
         assert expr.type_of() == TealType.bytes
         assert expr.__teal__() == [
             TealOp(Op.gtxn, i, "Receiver")
@@ -69,7 +69,7 @@ def test_gtxn_receiver():
 
 def test_gtxn_amount():
     for i in GTXN_RANGE:
-        expr = Gtxn.amount(i)
+        expr = Gtxn[i].amount()
         assert expr.type_of() == TealType.uint64
         assert expr.__teal__() == [
             TealOp(Op.gtxn, i, "Amount")
@@ -77,7 +77,7 @@ def test_gtxn_amount():
 
 def test_gtxn_close_remainder_to():
     for i in GTXN_RANGE:
-        expr = Gtxn.close_remainder_to(i)
+        expr = Gtxn[i].close_remainder_to()
         assert expr.type_of() == TealType.bytes
         assert expr.__teal__() == [
             TealOp(Op.gtxn, i, "CloseRemainderTo")
@@ -85,7 +85,7 @@ def test_gtxn_close_remainder_to():
 
 def test_gtxn_vote_pk():
     for i in GTXN_RANGE:
-        expr = Gtxn.vote_pk(i)
+        expr = Gtxn[i].vote_pk()
         assert expr.type_of() == TealType.bytes
         assert expr.__teal__() == [
             TealOp(Op.gtxn, i, "VotePK")
@@ -93,7 +93,7 @@ def test_gtxn_vote_pk():
 
 def test_gtxn_selection_pk():
     for i in GTXN_RANGE:
-        expr = Gtxn.selection_pk(i)
+        expr = Gtxn[i].selection_pk()
         assert expr.type_of() == TealType.bytes
         assert expr.__teal__() == [
             TealOp(Op.gtxn, i, "SelectionPK")
@@ -101,7 +101,7 @@ def test_gtxn_selection_pk():
 
 def test_gtxn_vote_first():
     for i in GTXN_RANGE:
-        expr = Gtxn.vote_first(i)
+        expr = Gtxn[i].vote_first()
         assert expr.type_of() == TealType.uint64
         assert expr.__teal__() == [
             TealOp(Op.gtxn, i, "VoteFirst")
@@ -109,7 +109,7 @@ def test_gtxn_vote_first():
 
 def test_gtxn_vote_last():
     for i in GTXN_RANGE:
-        expr = Gtxn.vote_last(i)
+        expr = Gtxn[i].vote_last()
         assert expr.type_of() == TealType.uint64
         assert expr.__teal__() == [
             TealOp(Op.gtxn, i, "VoteLast")
@@ -117,7 +117,7 @@ def test_gtxn_vote_last():
 
 def test_gtxn_vote_key_dilution():
     for i in GTXN_RANGE:
-        expr = Gtxn.vote_key_dilution(i)
+        expr = Gtxn[i].vote_key_dilution()
         assert expr.type_of() == TealType.uint64
         assert expr.__teal__() == [
             TealOp(Op.gtxn, i, "VoteKeyDilution")
@@ -125,7 +125,7 @@ def test_gtxn_vote_key_dilution():
 
 def test_gtxn_type():
     for i in GTXN_RANGE:
-        expr = Gtxn.type(i)
+        expr = Gtxn[i].type()
         assert expr.type_of() == TealType.bytes
         assert expr.__teal__() == [
             TealOp(Op.gtxn, i, "Type")
@@ -133,7 +133,7 @@ def test_gtxn_type():
 
 def test_gtxn_type_enum():
     for i in GTXN_RANGE:
-        expr = Gtxn.type_enum(i)
+        expr = Gtxn[i].type_enum()
         assert expr.type_of() == TealType.uint64
         assert expr.__teal__() == [
             TealOp(Op.gtxn, i, "TypeEnum")
@@ -141,7 +141,7 @@ def test_gtxn_type_enum():
 
 def test_gtxn_xfer_asset():
     for i in GTXN_RANGE:
-        expr = Gtxn.xfer_asset(i)
+        expr = Gtxn[i].xfer_asset()
         assert expr.type_of() == TealType.uint64
         assert expr.__teal__() == [
             TealOp(Op.gtxn, i, "XferAsset")
@@ -149,7 +149,7 @@ def test_gtxn_xfer_asset():
 
 def test_gtxn_asset_amount():
     for i in GTXN_RANGE:
-        expr = Gtxn.asset_amount(i)
+        expr = Gtxn[i].asset_amount()
         assert expr.type_of() == TealType.uint64
         assert expr.__teal__() == [
             TealOp(Op.gtxn, i, "AssetAmount")
@@ -157,7 +157,7 @@ def test_gtxn_asset_amount():
 
 def test_gtxn_asset_sender():
     for i in GTXN_RANGE:
-        expr = Gtxn.asset_sender(i)
+        expr = Gtxn[i].asset_sender()
         assert expr.type_of() == TealType.bytes
         assert expr.__teal__() == [
             TealOp(Op.gtxn, i, "AssetSender")
@@ -165,7 +165,7 @@ def test_gtxn_asset_sender():
 
 def test_gtxn_asset_receiver():
     for i in GTXN_RANGE:
-        expr = Gtxn.asset_receiver(i)
+        expr = Gtxn[i].asset_receiver()
         assert expr.type_of() == TealType.bytes
         assert expr.__teal__() == [
             TealOp(Op.gtxn, i, "AssetReceiver")
@@ -173,7 +173,7 @@ def test_gtxn_asset_receiver():
 
 def test_gtxn_asset_close_to():
     for i in GTXN_RANGE:
-        expr = Gtxn.asset_close_to(i)
+        expr = Gtxn[i].asset_close_to()
         assert expr.type_of() == TealType.bytes
         assert expr.__teal__() == [
             TealOp(Op.gtxn, i, "AssetCloseTo")
@@ -181,7 +181,7 @@ def test_gtxn_asset_close_to():
 
 def test_gtxn_group_index():
     for i in GTXN_RANGE:
-        expr = Gtxn.group_index(i)
+        expr = Gtxn[i].group_index()
         assert expr.type_of() == TealType.uint64
         assert expr.__teal__() == [
             TealOp(Op.gtxn, i, "GroupIndex")
@@ -189,7 +189,7 @@ def test_gtxn_group_index():
 
 def test_gtxn_id():
     for i in GTXN_RANGE:
-        expr = Gtxn.tx_id(i)
+        expr = Gtxn[i].tx_id()
         assert expr.type_of() == TealType.bytes
         assert expr.__teal__() == [
             TealOp(Op.gtxn, i, "TxID")
@@ -197,7 +197,7 @@ def test_gtxn_id():
 
 def test_txn_application_id():
     for i in GTXN_RANGE:
-        expr = Gtxn.application_id(i)
+        expr = Gtxn[i].application_id()
         assert expr.type_of() == TealType.uint64
         assert expr.__teal__() == [
             TealOp(Op.gtxn, i, "ApplicationID")
@@ -205,7 +205,7 @@ def test_txn_application_id():
 
 def test_txn_on_completion():
     for i in GTXN_RANGE:
-        expr = Gtxn.on_completion(i)
+        expr = Gtxn[i].on_completion()
         assert expr.type_of() == TealType.uint64
         assert expr.__teal__() == [
             TealOp(Op.gtxn, i, "OnCompletion")
@@ -214,7 +214,7 @@ def test_txn_on_completion():
 def test_txn_application_args():
     for i in GTXN_RANGE:
         for j in range(4):
-            expr = Gtxn.application_args(i)[j]
+            expr = Gtxn[i].application_args[j]
             assert expr.type_of() == TealType.bytes
             assert expr.__teal__() == [
                 TealOp(Op.gtxna, i, "ApplicationArgs", j)
@@ -222,7 +222,7 @@ def test_txn_application_args():
 
 def test_txn_application_args_length():
     for i in GTXN_RANGE:
-        expr = Gtxn.application_args(i).length()
+        expr = Gtxn[i].application_args.length()
         assert expr.type_of() == TealType.uint64
         assert expr.__teal__() == [
             TealOp(Op.gtxn, i, "NumAppArgs")
@@ -231,7 +231,7 @@ def test_txn_application_args_length():
 def test_txn_accounts():
     for i in GTXN_RANGE:
         for j in range(4):
-            expr = Gtxn.accounts(i)[j]
+            expr = Gtxn[i].accounts[j]
             assert expr.type_of() == TealType.bytes
             assert expr.__teal__() == [
                 TealOp(Op.gtxna, i, "Accounts", j)
@@ -239,7 +239,7 @@ def test_txn_accounts():
 
 def test_txn_accounts_length():
     for i in GTXN_RANGE:
-        expr = Gtxn.accounts(i).length()
+        expr = Gtxn[i].accounts.length()
         assert expr.type_of() == TealType.uint64
         assert expr.__teal__() == [
             TealOp(Op.gtxn, i, "NumAccounts")
@@ -247,7 +247,7 @@ def test_txn_accounts_length():
 
 def test_txn_approval_program():
     for i in GTXN_RANGE:
-        expr = Gtxn.approval_program(i)
+        expr = Gtxn[i].approval_program()
         assert expr.type_of() == TealType.bytes
         assert expr.__teal__() == [
             TealOp(Op.gtxn, i, "ApprovalProgram")
@@ -255,7 +255,7 @@ def test_txn_approval_program():
 
 def test_txn_clear_state_program():
     for i in GTXN_RANGE:
-        expr = Gtxn.clear_state_program(i)
+        expr = Gtxn[i].clear_state_program()
         assert expr.type_of() == TealType.bytes
         assert expr.__teal__() == [
             TealOp(Op.gtxn, i, "ClearStateProgram")
@@ -263,7 +263,7 @@ def test_txn_clear_state_program():
 
 def test_txn_rekey_to():
     for i in GTXN_RANGE:
-        expr = Gtxn.rekey_to(i)
+        expr = Gtxn[i].rekey_to()
         assert expr.type_of() == TealType.bytes
         assert expr.__teal__() == [
             TealOp(Op.gtxn, i, "RekeyTo")
@@ -271,7 +271,7 @@ def test_txn_rekey_to():
 
 def test_txn_config_asset():
     for i in GTXN_RANGE:
-        expr = Gtxn.config_asset(i)
+        expr = Gtxn[i].config_asset()
         assert expr.type_of() == TealType.uint64
         assert expr.__teal__() == [
             TealOp(Op.gtxn, i, "ConfigAsset")
@@ -279,7 +279,7 @@ def test_txn_config_asset():
 
 def test_txn_config_asset_total():
     for i in GTXN_RANGE:
-        expr = Gtxn.config_asset_total(i)
+        expr = Gtxn[i].config_asset_total()
         assert expr.type_of() == TealType.uint64
         assert expr.__teal__() == [
             TealOp(Op.gtxn, i, "ConfigAssetTotal")
@@ -287,7 +287,7 @@ def test_txn_config_asset_total():
 
 def test_txn_config_asset_decimals():
     for i in GTXN_RANGE:
-        expr = Gtxn.config_asset_decimals(i)
+        expr = Gtxn[i].config_asset_decimals()
         assert expr.type_of() == TealType.uint64
         assert expr.__teal__() == [
             TealOp(Op.gtxn, i, "ConfigAssetDecimals")
@@ -295,7 +295,7 @@ def test_txn_config_asset_decimals():
 
 def test_txn_config_asset_default_frozen():
     for i in GTXN_RANGE:
-        expr = Gtxn.config_asset_default_frozen(i)
+        expr = Gtxn[i].config_asset_default_frozen()
         assert expr.type_of() == TealType.uint64
         assert expr.__teal__() == [
             TealOp(Op.gtxn, i, "ConfigAssetDefaultFrozen")
@@ -303,7 +303,7 @@ def test_txn_config_asset_default_frozen():
 
 def test_txn_config_asset_unit_name():
     for i in GTXN_RANGE:
-        expr = Gtxn.config_asset_unit_name(i)
+        expr = Gtxn[i].config_asset_unit_name()
         assert expr.type_of() == TealType.bytes
         assert expr.__teal__() == [
             TealOp(Op.gtxn, i, "ConfigAssetUnitName")
@@ -311,7 +311,7 @@ def test_txn_config_asset_unit_name():
 
 def test_txn_config_asset_name():
     for i in GTXN_RANGE:
-        expr = Gtxn.config_asset_name(i)
+        expr = Gtxn[i].config_asset_name()
         assert expr.type_of() == TealType.bytes
         assert expr.__teal__() == [
             TealOp(Op.gtxn, i, "ConfigAssetName")
@@ -319,7 +319,7 @@ def test_txn_config_asset_name():
 
 def test_txn_config_asset_url():
     for i in GTXN_RANGE:
-        expr = Gtxn.config_asset_url(i)
+        expr = Gtxn[i].config_asset_url()
         assert expr.type_of() == TealType.bytes
         assert expr.__teal__() == [
             TealOp(Op.gtxn, i, "ConfigAssetURL")
@@ -327,7 +327,7 @@ def test_txn_config_asset_url():
 
 def test_txn_config_asset_metadata_hash():
     for i in GTXN_RANGE:
-        expr = Gtxn.config_asset_metadata_hash(i)
+        expr = Gtxn[i].config_asset_metadata_hash()
         assert expr.type_of() == TealType.bytes
         assert expr.__teal__() == [
             TealOp(Op.gtxn, i, "ConfigAssetMetadataHash")
@@ -335,7 +335,7 @@ def test_txn_config_asset_metadata_hash():
 
 def test_txn_config_asset_manager():
     for i in GTXN_RANGE:
-        expr = Gtxn.config_asset_manager(i)
+        expr = Gtxn[i].config_asset_manager()
         assert expr.type_of() == TealType.bytes
         assert expr.__teal__() == [
             TealOp(Op.gtxn, i, "ConfigAssetManager")
@@ -343,7 +343,7 @@ def test_txn_config_asset_manager():
 
 def test_txn_config_asset_reserve():
     for i in GTXN_RANGE:
-        expr = Gtxn.config_asset_reserve(i)
+        expr = Gtxn[i].config_asset_reserve()
         assert expr.type_of() == TealType.bytes
         assert expr.__teal__() == [
             TealOp(Op.gtxn, i, "ConfigAssetReserve")
@@ -351,7 +351,7 @@ def test_txn_config_asset_reserve():
 
 def test_txn_config_asset_freeze():
     for i in GTXN_RANGE:
-        expr = Gtxn.config_asset_freeze(i)
+        expr = Gtxn[i].config_asset_freeze()
         assert expr.type_of() == TealType.bytes
         assert expr.__teal__() == [
             TealOp(Op.gtxn, i, "ConfigAssetFreeze")
@@ -359,7 +359,7 @@ def test_txn_config_asset_freeze():
 
 def test_txn_config_asset_clawback():
     for i in GTXN_RANGE:
-        expr = Gtxn.config_asset_clawback(i)
+        expr = Gtxn[i].config_asset_clawback()
         assert expr.type_of() == TealType.bytes
         assert expr.__teal__() == [
             TealOp(Op.gtxn, i, "ConfigAssetClawback")
@@ -367,7 +367,7 @@ def test_txn_config_asset_clawback():
 
 def test_txn_freeze_asset():
     for i in GTXN_RANGE:
-        expr = Gtxn.freeze_asset(i)
+        expr = Gtxn[i].freeze_asset()
         assert expr.type_of() == TealType.uint64
         assert expr.__teal__() == [
             TealOp(Op.gtxn, i, "FreezeAsset")
@@ -375,7 +375,7 @@ def test_txn_freeze_asset():
 
 def test_txn_freeze_asset_account():
     for i in GTXN_RANGE:
-        expr = Gtxn.freeze_asset_account(i)
+        expr = Gtxn[i].freeze_asset_account()
         assert expr.type_of() == TealType.bytes
         assert expr.__teal__() == [
             TealOp(Op.gtxn, i, "FreezeAssetAccount")
@@ -383,7 +383,7 @@ def test_txn_freeze_asset_account():
 
 def test_txn_freeze_asset_frozen():
     for i in GTXN_RANGE:
-        expr = Gtxn.freeze_asset_frozen(i)
+        expr = Gtxn[i].freeze_asset_frozen()
         assert expr.type_of() == TealType.uint64
         assert expr.__teal__() == [
             TealOp(Op.gtxn, i, "FreezeAssetFrozen")
diff --git a/pyteal/ast/if_test.py b/pyteal/ast/if_test.py
index 85eb1f5..d7528d2 100644
--- a/pyteal/ast/if_test.py
+++ b/pyteal/ast/if_test.py
@@ -38,9 +38,51 @@ def test_if_bytes():
         labels[1]
     ]
 
+def test_if_none():
+    expr = If(Int(0), Pop(Txn.sender()), Pop(Txn.receiver()))
+    assert expr.type_of() == TealType.none
+    teal = expr.__teal__()
+    assert len(teal) == 9
+    labels = [teal[5], teal[8]]
+    assert all(isinstance(label, TealLabel) for label in labels)
+    assert len(labels) == len(set(labels))
+    assert teal == [
+        TealOp(Op.int, 0),
+        TealOp(Op.bnz, labels[0].label),
+        TealOp(Op.txn, "Receiver"),
+        TealOp(Op.pop),
+        TealOp(Op.b, labels[1].label),
+        labels[0],
+        TealOp(Op.txn, "Sender"),
+        TealOp(Op.pop),
+        labels[1]
+    ]
+
+def test_if_single():
+    expr = If(Int(0), Pop(Int(1)))
+    assert expr.type_of() == TealType.none
+    teal = expr.__teal__()
+    assert len(teal) == 5
+    labels = [teal[4]]
+    assert all(isinstance(label, TealLabel) for label in labels)
+    assert len(labels) == len(set(labels))
+    assert teal == [
+        TealOp(Op.int, 0),
+        TealOp(Op.bz, labels[0].label),
+        TealOp(Op.int, 1),
+        TealOp(Op.pop),
+        labels[0]
+    ]
+
 def test_if_invalid():
     with pytest.raises(TealTypeError):
         If(Int(0), Txn.amount(), Txn.sender())
 
     with pytest.raises(TealTypeError):
         If(Txn.sender(), Int(1), Int(0))
+    
+    with pytest.raises(TealTypeError):
+        If(Int(0), Int(1))
+    
+    with pytest.raises(TealTypeError):
+        If(Int(0), Txn.sender())
diff --git a/tests/compile_test.py b/tests/compile_test.py
index bac973b..7d80db8 100644
--- a/tests/compile_test.py
+++ b/tests/compile_test.py
@@ -152,12 +152,12 @@ def test_split():
     split_core = And(Txn.type_enum() == Int(1),
                      Txn.fee() < tmpl_fee)
 
-    split_transfer = And(Gtxn.sender(0) == Gtxn.sender(1),
+    split_transfer = And(Gtxn[0].sender() == Gtxn[1].sender(),
                          Txn.close_remainder_to() == Global.zero_address(),
-                         Gtxn.receiver(0) == tmpl_rcv1,
-                         Gtxn.receiver(1) == tmpl_rcv2,
-                         Gtxn.amount(1) == ((Gtxn.amount(0) + Gtxn.amount(1)) * tmpl_ratn) / tmpl_ratd,
-                         Gtxn.amount(0) == tmpl_minpay)
+                         Gtxn[0].receiver() == tmpl_rcv1,
+                         Gtxn[1].receiver() == tmpl_rcv2,
+                         Gtxn[1].amount() == ((Gtxn[0].amount() + Gtxn[1].amount()) * tmpl_ratn) / tmpl_ratd,
+                         Gtxn[0].amount() == tmpl_minpay)
 
     split_close = And(Txn.close_remainder_to() == tmpl_own,
                       Txn.receiver() == Global.zero_address(),