Repository: algorand/pyteal (language: python)

Issue:
define reference type spec
Define ReferenceTypeSpec and ReferenceType

Each ReferenceType implementation provides params and a way to access the underlying identifier (asset id, app id, address) as well as reference index (thanks @jasonpaulos !)


Discussion / hints:

Fix patch:
diff --git a/pyteal/__init__.pyi b/pyteal/__init__.pyi
index 5e49e918e..7a3c85ba9 100644
--- a/pyteal/__init__.pyi
+++ b/pyteal/__init__.pyi
@@ -30,18 +30,22 @@ from pyteal.config import (
 __all__ = [
     "ABIReturnSubroutine",
     "AccountParam",
+    "AccountParamObject",
     "Add",
     "Addr",
     "And",
     "App",
     "AppField",
     "AppParam",
+    "AppParamObject",
     "Approve",
     "Arg",
     "Array",
     "Assert",
     "AssetHolding",
+    "AssetHoldingObject",
     "AssetParam",
+    "AssetParamObject",
     "Balance",
     "BareCallActions",
     "BinaryExpr",
diff --git a/pyteal/ast/__init__.py b/pyteal/ast/__init__.py
index 68c6fd149..693ee4a8d 100644
--- a/pyteal/ast/__init__.py
+++ b/pyteal/ast/__init__.py
@@ -24,9 +24,14 @@
 from pyteal.ast.gitxn import Gitxn, GitxnExpr, GitxnaExpr, InnerTxnGroup
 from pyteal.ast.gload import ImportScratchValue
 from pyteal.ast.global_ import Global, GlobalField
-from pyteal.ast.app import App, AppField, OnComplete, AppParam
-from pyteal.ast.asset import AssetHolding, AssetParam
-from pyteal.ast.acct import AccountParam
+from pyteal.ast.app import App, AppField, OnComplete, AppParam, AppParamObject
+from pyteal.ast.asset import (
+    AssetHolding,
+    AssetHoldingObject,
+    AssetParam,
+    AssetParamObject,
+)
+from pyteal.ast.acct import AccountParam, AccountParamObject
 
 # inner txns
 from pyteal.ast.itxn import InnerTxnBuilder, InnerTxn, InnerTxnAction
@@ -177,9 +182,13 @@
     "AppField",
     "OnComplete",
     "AppParam",
+    "AppParamObject",
     "AssetHolding",
+    "AssetHoldingObject",
     "AssetParam",
+    "AssetParamObject",
     "AccountParam",
+    "AccountParamObject",
     "InnerTxnBuilder",
     "InnerTxn",
     "InnerTxnAction",
diff --git a/pyteal/ast/abi/reference_type.py b/pyteal/ast/abi/reference_type.py
index 3c6e4e363..269fe57dc 100644
--- a/pyteal/ast/abi/reference_type.py
+++ b/pyteal/ast/abi/reference_type.py
@@ -1,13 +1,13 @@
-from typing import List, Final, TypeVar, Union, cast
+from typing import List, Final, TypeVar, cast
 from abc import abstractmethod
-from pyteal.ast.abi.type import BaseType, ComputedValue, TypeSpec
+from pyteal.ast.abi.type import BaseType, TypeSpec
+from pyteal.ast.abi.uint import NUM_BITS_IN_BYTE, uint_decode
 
-from pyteal.ast.binaryexpr import GetByte
-from pyteal.ast.bytes import Bytes
-from pyteal.ast.int import Int
 from pyteal.ast.expr import Expr
-from pyteal.ast.ternaryexpr import SetByte
 from pyteal.ast.txn import Txn
+from pyteal.ast.acct import AccountParamObject
+from pyteal.ast.asset import AssetHoldingObject, AssetParamObject
+from pyteal.ast.app import AppParamObject
 from pyteal.errors import TealInputError
 from pyteal.types import TealType
 
@@ -16,10 +16,6 @@
 
 
 class ReferenceTypeSpec(TypeSpec):
-    def __init__(self) -> None:
-        super().__init__()
-        self.size: Final = 8
-
     @abstractmethod
     def new_instance(self) -> "ReferenceType":
         pass
@@ -29,8 +25,8 @@ def annotation_type(self) -> "type[ReferenceType]":
         pass
 
     def bit_size(self) -> int:
-        """Get the bit size of this uint type"""
-        return self.size
+        """Get the bit size of the index this reference type holds"""
+        return NUM_BITS_IN_BYTE
 
     def is_dynamic(self) -> bool:
         return False
@@ -41,13 +37,6 @@ def byte_length_static(self) -> int:
     def storage_type(self) -> TealType:
         return TealType.uint64
 
-    def __eq__(self, other: object) -> bool:
-        return type(self) is type(other)
-
-    @abstractmethod
-    def __str__(self) -> str:
-        pass
-
 
 class ReferenceType(BaseType):
     @abstractmethod
@@ -57,37 +46,16 @@ def __init__(self, spec: ReferenceTypeSpec) -> None:
     def type_spec(self) -> ReferenceTypeSpec:
         return cast(ReferenceTypeSpec, super().type_spec())
 
-    def get(self) -> Expr:
-        return self.stored_value.load()
+    def referenced_index(self) -> Expr:
+        """Get the reference index for this value.
 
-    def set(
-        self: T, value: Union[int, Expr, "ReferenceType", ComputedValue[T]]
-    ) -> Expr:
-        if isinstance(value, ComputedValue):
-            return self._set_with_computed_type(value)
-
-        if isinstance(value, BaseType) and not (
-            isinstance(value.type_spec(), ReferenceTypeSpec)
-        ):
-            raise TealInputError(
-                "Type {} is not assignable to type {}".format(
-                    value.type_spec(), self.type_spec()
-                )
-            )
-
-        match value:
-            case int():
-                return self.stored_value.store(Int(value))
-            case Expr():
-                return self.stored_value.store(value)
-            case ReferenceType():
-                return self.stored_value.store(value.get())
-            case _:
-                raise TealInputError(
-                    "Expected int, Expr, ReferenceType or ComputedValue, got {}".format(
-                        type(value)
-                    )
-                )
+        The three reference types (account, application, asset) contain indexes into a foreign array
+        of the transaction. This method returns that index.
+
+        If this reference type is an application or asset, note that this DOES NOT return the
+        application or asset ID. See :code:`application_id()` or :code:`asset_id()` for that.
+        """
+        return self.stored_value.load()
 
     def decode(
         self,
@@ -97,12 +65,17 @@ def decode(
         endIndex: Expr = None,
         length: Expr = None,
     ) -> Expr:
-        if startIndex is None:
-            startIndex = Int(0)
-        return self.stored_value.store(GetByte(encoded, startIndex))
+        return uint_decode(
+            self.type_spec().bit_size(),
+            self.stored_value,
+            encoded,
+            startIndex,
+            endIndex,
+            length,
+        )
 
     def encode(self) -> Expr:
-        return SetByte(Bytes(b"\x00"), Int(0), self.stored_value.load())
+        raise TealInputError("A ReferenceType cannot be encoded")
 
 
 class AccountTypeSpec(ReferenceTypeSpec):
@@ -126,9 +99,30 @@ class Account(ReferenceType):
     def __init__(self) -> None:
         super().__init__(AccountTypeSpec())
 
-    def deref(self) -> Expr:
+    def address(self) -> Expr:
+        """Get the address of the account."""
         return Txn.accounts[self.stored_value.load()]
 
+    def params(self) -> AccountParamObject:
+        """Get information about the account."""
+        return AccountParamObject(self.referenced_index())
+
+    def asset_holding(self, asset: "Expr | Asset") -> AssetHoldingObject:
+        """Get information about an asset held by this account.
+
+        Args:
+            asset: An identifier for the asset. It must be one of the following: an abi.Asset
+                reference object, an expression holding an index into Txn.ForeignAssets that
+                corresponds to the asset (in which case it must evaluate to uint64), or since v4, an
+                expression holding an asset ID that appears in Txn.ForeignAssets (in which case it
+                must evaluate to uint64).
+        """
+        if isinstance(asset, Asset):
+            asset_ref = asset.referenced_index()
+        else:
+            asset_ref = asset
+        return AssetHoldingObject(asset_ref, self.referenced_index())
+
 
 Account.__module__ = "pyteal"
 
@@ -154,8 +148,29 @@ class Asset(ReferenceType):
     def __init__(self) -> None:
         super().__init__(AssetTypeSpec())
 
-    def deref(self) -> Expr:
-        return Txn.assets[self.stored_value.load()]
+    def asset_id(self) -> Expr:
+        """Get the ID of the asset."""
+        return Txn.assets[self.referenced_index()]
+
+    def holding(self, account: Expr | Account) -> AssetHoldingObject:
+        """Get information about this asset held by an account.
+
+        Args:
+            account: An identifier for the account. It must be one of the following: an abi.Account
+                reference object, an expression holding an index into Txn.Accounts that corresponds
+                to the account (in which case it must evaluate to uint64), or since v4, an
+                expression holding an account address that appears in Txn.Accounts or is Txn.Sender
+                (in which case it must evaluate to bytes).
+        """
+        if isinstance(account, Account):
+            account_ref = account.referenced_index()
+        else:
+            account_ref = account
+        return AssetHoldingObject(self.referenced_index(), account_ref)
+
+    def params(self) -> AssetParamObject:
+        """Get information about the asset's parameters."""
+        return AssetParamObject(self.referenced_index())
 
 
 Asset.__module__ = "pyteal"
@@ -182,9 +197,14 @@ class Application(ReferenceType):
     def __init__(self) -> None:
         super().__init__(ApplicationTypeSpec())
 
-    def deref(self) -> Expr:
+    def application_id(self) -> Expr:
+        """Get the ID of the application."""
         return Txn.applications[self.stored_value.load()]
 
+    def params(self) -> AppParamObject:
+        """Get information about the application's parameters."""
+        return AppParamObject(self.referenced_index())
+
 
 Application.__module__ = "pyteal"
 
diff --git a/pyteal/ast/acct.py b/pyteal/ast/acct.py
index c08f2760b..97eee199b 100644
--- a/pyteal/ast/acct.py
+++ b/pyteal/ast/acct.py
@@ -1,3 +1,5 @@
+from typing import Final
+
 from pyteal.types import TealType, require_type
 from pyteal.ir import Op
 from pyteal.ast.expr import Expr
@@ -55,3 +57,33 @@ def authAddr(cls, acct: Expr) -> MaybeValue:
 
 
 AccountParam.__module__ = "pyteal"
+
+
+class AccountParamObject:
+    """Represents information about an account"""
+
+    def __init__(self, account: Expr) -> None:
+        """Create a new AccountParamObject for the given account.
+
+        Args:
+            app: An index into Txn.accounts that corresponds to the application to check or an
+                address available at runtime. May evaluate to uint64 or bytes, respectively.
+        """
+        self._account: Final = account
+
+    def balance(self) -> MaybeValue:
+        """Get the current balance in microAlgos for the account"""
+        return AccountParam.balance(self._account)
+
+    def min_balance(self) -> MaybeValue:
+        """Get the minimum balance in microAlgos for the account."""
+        return AccountParam.minBalance(self._account)
+
+    def auth_address(self) -> MaybeValue:
+        """Get the authorizing address for the account.
+
+        If the account is not rekeyed, the empty address is returned."""
+        return AccountParam.authAddr(self._account)
+
+
+AccountParamObject.__module__ = "pyteal"
diff --git a/pyteal/ast/app.py b/pyteal/ast/app.py
index adf18f6a7..6a36b31e1 100644
--- a/pyteal/ast/app.py
+++ b/pyteal/ast/app.py
@@ -1,4 +1,4 @@
-from typing import TYPE_CHECKING
+from typing import TYPE_CHECKING, Final
 from enum import Enum
 
 from pyteal.types import TealType, require_type
@@ -356,3 +356,56 @@ def address(cls, app: Expr) -> MaybeValue:
 
 
 AppParam.__module__ = "pyteal"
+
+
+class AppParamObject:
+    """Represents information about an application's parameters"""
+
+    def __init__(self, app: Expr) -> None:
+        """Create a new AppParamObject for the given application.
+
+        Args:
+            app: An identifier for the app. It must be an index into Txn.ForeignApps that
+                corresponds to the app to check, or since v4, an application ID that appears in
+                Txn.ForeignApps or is the CurrentApplicationID. In either case, it must evaluate to
+                uint64.
+        """
+        self._app: Final = app
+
+    def approval_program(self) -> MaybeValue:
+        """Get the bytecode of Approval Program for the application."""
+        return AppParam.approvalProgram(self._app)
+
+    def clear_state_program(self) -> MaybeValue:
+        return AppParam.clearStateProgram(self._app)
+
+    def global_num_unit(self) -> MaybeValue:
+        """Get the number of uint64 values allowed in Global State for the application."""
+        return AppParam.globalNumUnit(self._app)
+
+    def global_num_byte_slice(self) -> MaybeValue:
+        """Get the number of byte array values allowed in Global State for the application."""
+        return AppParam.globalNumByteSlice(self._app)
+
+    def local_num_unit(self) -> MaybeValue:
+        """Get the number of uint64 values allowed in Local State for the application."""
+        return AppParam.localNumUnit(self._app)
+
+    def local_num_byte_slice(self) -> MaybeValue:
+        """Get the number of byte array values allowed in Local State for the application."""
+        return AppParam.localNumByteSlice(self._app)
+
+    def extra_program_pages(self) -> MaybeValue:
+        """Get the number of Extra Program Pages of code space for the application."""
+        return AppParam.extraProgramPages(self._app)
+
+    def creator_address(self) -> MaybeValue:
+        """Get the creator address for the application."""
+        return AppParam.creator(self._app)
+
+    def address(self) -> MaybeValue:
+        """Get the escrow address for the application."""
+        return AppParam.address(self._app)
+
+
+AppParamObject.__module__ = "pyteal"
diff --git a/pyteal/ast/asset.py b/pyteal/ast/asset.py
index a39879be2..41c440615 100644
--- a/pyteal/ast/asset.py
+++ b/pyteal/ast/asset.py
@@ -1,3 +1,5 @@
+from typing import Final
+
 from pyteal.types import TealType, require_type
 from pyteal.ir import Op
 from pyteal.ast.expr import Expr
@@ -51,6 +53,39 @@ def frozen(cls, account: Expr, asset: Expr) -> MaybeValue:
 AssetHolding.__module__ = "pyteal"
 
 
+class AssetHoldingObject:
+    """Represents information about an account's holding of an asset"""
+
+    def __init__(self, asset: Expr, account: Expr) -> None:
+        """Create a new AssetParamObject for the given asset.
+
+        Args:
+            asset: An identifier for the asset. It must be an index into Txn.ForeignAssets that
+                corresponds to the asset to check, or since v4, an asset ID that appears in
+                Txn.ForeignAssets. In either case, it must evaluate to uint64.
+            account: An identifier for the account. It must be an index into Txn.Accounts that
+                corresponds to the account to check (in which case it must evaluate to uint64), or
+                since v4, an account address that appears in Txn.Accounts or is Txn.Sender (in which
+                case it must evaluate to bytes).
+        """
+        self._asset: Final = asset
+        self._account: Final = account
+
+    def balance(self) -> MaybeValue:
+        """Get the amount of the asset held by the account."""
+        return AssetHolding.balance(self._account, self._asset)
+
+    def frozen(self) -> MaybeValue:
+        """Check if the asset is frozen for the account.
+
+        A value of 1 indicates frozen and 0 indicates not frozen.
+        """
+        return AssetHolding.frozen(self._account, self._asset)
+
+
+AssetHoldingObject.__module__ = "pyteal"
+
+
 class AssetParam:
     @classmethod
     def total(cls, asset: Expr) -> MaybeValue:
@@ -259,3 +294,70 @@ def creator(cls, asset: Expr) -> MaybeValue:
 
 
 AssetParam.__module__ = "pyteal"
+
+
+class AssetParamObject:
+    """Represents information about an asset's parameters"""
+
+    def __init__(self, asset: Expr) -> None:
+        """Create a new AssetParamObject for the given asset.
+
+        Args:
+            asset: An identifier for the asset. It must be an index into Txn.ForeignAssets that
+                corresponds to the asset to check, or since v4, an asset ID that appears in
+                Txn.ForeignAssets. In either case, it must evaluate to uint64.
+        """
+        self._asset: Final = asset
+
+    def total(self) -> MaybeValue:
+        """Get the total number of units of the asset."""
+        return AssetParam.total(self._asset)
+
+    def decimals(self) -> MaybeValue:
+        """Get the number of decimals for the asset."""
+        return AssetParam.decimals(self._asset)
+
+    def default_frozen(self) -> MaybeValue:
+        """Check if the asset is frozen by default."""
+        return AssetParam.defaultFrozen(self._asset)
+
+    def unit_name(self) -> MaybeValue:
+        """Get the unit name of the asset."""
+        return AssetParam.unitName(self._asset)
+
+    def name(self) -> MaybeValue:
+        """Get the name of the asset."""
+        return AssetParam.name(self._asset)
+
+    def url(self) -> MaybeValue:
+        """Get the URL of the asset."""
+        return AssetParam.url(self._asset)
+
+    def metadata_hash(self) -> MaybeValue:
+        """Get the arbitrary commitment for the asset.
+
+        If set, this will be 32 bytes long."""
+        return AssetParam.metadataHash(self._asset)
+
+    def manager_address(self) -> MaybeValue:
+        """Get the manager address for the asset."""
+        return AssetParam.manager(self._asset)
+
+    def reserve_address(self) -> MaybeValue:
+        """Get the reserve address for the asset."""
+        return AssetParam.reserve(self._asset)
+
+    def freeze_address(self) -> MaybeValue:
+        """Get the freeze address for the asset."""
+        return AssetParam.freeze(self._asset)
+
+    def clawback_address(self) -> MaybeValue:
+        """Get the clawback address for the asset."""
+        return AssetParam.clawback(self._asset)
+
+    def creator_address(self) -> MaybeValue:
+        """Get the creator address for the asset."""
+        return AssetParam.creator(self._asset)
+
+
+AssetParamObject.__module__ = "pyteal"


Test patch:
diff --git a/pyteal/ast/abi/reference_type_test.py b/pyteal/ast/abi/reference_type_test.py
index f799d4e08..0bf0df9a3 100644
--- a/pyteal/ast/abi/reference_type_test.py
+++ b/pyteal/ast/abi/reference_type_test.py
@@ -1,9 +1,75 @@
+import pytest
+
 import pyteal as pt
 from pyteal import abi
 
 options = pt.CompileOptions(version=5)
 
 
+def test_ReferenceTypeSpecs_list():
+    assert abi.ReferenceTypeSpecs == [
+        abi.AccountTypeSpec(),
+        abi.AssetTypeSpec(),
+        abi.ApplicationTypeSpec(),
+    ]
+
+
+def test_ReferenceType_referenced_index():
+    for value in (abi.Account(), abi.Asset(), abi.Application()):
+        expr = value.referenced_index()
+        assert expr.type_of() == pt.TealType.uint64
+        assert expr.has_return() is False
+
+        expected = pt.TealSimpleBlock(
+            [
+                pt.TealOp(expr, pt.Op.load, value.stored_value.slot),
+            ]
+        )
+        actual, _ = expr.__teal__(options)
+        actual.addIncoming()
+        actual = pt.TealBlock.NormalizeBlocks(actual)
+
+        with pt.TealComponent.Context.ignoreExprEquality():
+            assert actual == expected
+
+
+def test_ReferenceType_encode():
+    for value in (abi.Account(), abi.Asset(), abi.Application()):
+        with pytest.raises(
+            pt.TealInputError, match=r"A ReferenceType cannot be encoded$"
+        ):
+            value.encode()
+
+
+def test_ReferenceType_decode():
+    encoded = pt.Bytes("encoded")
+    for value in (abi.Account(), abi.Asset(), abi.Application()):
+        for startIndex in (None, pt.Int(1)):
+            for endIndex in (None, pt.Int(2)):
+                for length in (None, pt.Int(3)):
+                    expr = value.decode(
+                        encoded, startIndex=startIndex, endIndex=endIndex, length=length
+                    )
+                    assert expr.type_of() == pt.TealType.none
+                    assert expr.has_return() is False
+
+                    expected_decoding = value.stored_value.store(
+                        pt.GetByte(
+                            encoded, startIndex if startIndex is not None else pt.Int(0)
+                        )
+                    )
+                    expected, _ = expected_decoding.__teal__(options)
+                    expected.addIncoming()
+                    expected = pt.TealBlock.NormalizeBlocks(expected)
+
+                    actual, _ = expr.__teal__(options)
+                    actual.addIncoming()
+                    actual = pt.TealBlock.NormalizeBlocks(actual)
+
+                    with pt.TealComponent.Context.ignoreExprEquality():
+                        assert actual == expected
+
+
 def test_Account_str():
     assert str(abi.AccountTypeSpec()) == "account"
 
@@ -31,34 +97,16 @@ def test_Account_typespec():
     assert abi.Account().type_spec() == abi.AccountTypeSpec()
 
 
-def test_Account_encode():
+def test_Account_address():
     value = abi.Account()
-    expr = value.encode()
+    expr = value.address()
     assert expr.type_of() == pt.TealType.bytes
     assert expr.has_return() is False
 
-    expectEncoding = pt.SetByte(pt.Bytes(b"\x00"), pt.Int(0), value.stored_value.load())
-
-    expected, _ = expectEncoding.__teal__(options)
-    expected.addIncoming()
-    expected = pt.TealBlock.NormalizeBlocks(expected)
-
-    actual, _ = expr.__teal__(options)
-    actual.addIncoming()
-    actual = pt.TealBlock.NormalizeBlocks(actual)
-    with pt.TealComponent.Context.ignoreExprEquality():
-        assert actual == expected
-
-
-def test_Account_get():
-    value = abi.Account()
-    expr = value.get()
-    assert expr.type_of() == pt.TealType.uint64
-    assert expr.has_return() is False
-
     expected = pt.TealSimpleBlock(
         [
-            pt.TealOp(expr, pt.Op.load, value.stored_value.slot),
+            pt.TealOp(None, pt.Op.load, value.stored_value.slot),
+            pt.TealOp(None, pt.Op.txnas, "Accounts"),
         ]
     )
     actual, _ = expr.__teal__(options)
@@ -69,47 +117,40 @@ def test_Account_get():
         assert actual == expected
 
 
-def test_Account_set():
-    val_to_set = 2
+def test_Account_params():
     value = abi.Account()
-    expr = value.set(val_to_set)
-    assert expr.type_of() == pt.TealType.none
-    assert expr.has_return() is False
 
-    expected = pt.TealSimpleBlock(
-        [
-            pt.TealOp(expr, pt.Op.int, val_to_set),
-            pt.TealOp(None, pt.Op.store, value.stored_value.slot),
-        ]
-    )
-    actual, _ = expr.__teal__(options)
-    actual.addIncoming()
-    actual = pt.TealBlock.NormalizeBlocks(actual)
+    params = value.params()
+
+    assert type(params) is pt.AccountParamObject
+
+    expected = value.referenced_index()
+    actual = params._account
 
     with pt.TealComponent.Context.ignoreExprEquality():
-        assert actual == expected
+        assert actual.__teal__(options) == expected.__teal__(options)
 
 
-def test_Account_deref():
-    val_to_set = 2
+def test_Account_asset_holding():
     value = abi.Account()
-    value.set(val_to_set)
-    expr = value.deref()
-    assert expr.type_of() == pt.TealType.bytes
-    assert expr.has_return() is False
 
-    expected = pt.TealSimpleBlock(
-        [
-            pt.TealOp(None, pt.Op.load, value.stored_value.slot),
-            pt.TealOp(None, pt.Op.txnas, "Accounts"),
-        ]
-    )
-    actual, _ = expr.__teal__(options)
-    actual.addIncoming()
-    actual = pt.TealBlock.NormalizeBlocks(actual)
+    assets = ((pt.Int(6), pt.Int(6)), (a := abi.Asset(), a.referenced_index()))
 
-    with pt.TealComponent.Context.ignoreExprEquality():
-        assert actual == expected
+    for asset, expected_asset in assets:
+        holding = value.asset_holding(asset)
+
+        assert type(holding) is pt.AssetHoldingObject
+
+        expected_account = value.referenced_index()
+        actual_account = holding._account
+
+        actual_asset = holding._asset
+
+        with pt.TealComponent.Context.ignoreExprEquality():
+            assert actual_account.__teal__(options) == expected_account.__teal__(
+                options
+            )
+            assert actual_asset.__teal__(options) == expected_asset.__teal__(options)
 
 
 def test_Asset_str():
@@ -139,34 +180,16 @@ def test_Asset_typespec():
     assert abi.Asset().type_spec() == abi.AssetTypeSpec()
 
 
-def test_Asset_encode():
-    value = abi.Asset()
-    expr = value.encode()
-    assert expr.type_of() == pt.TealType.bytes
-    assert expr.has_return() is False
-
-    expectEncoding = pt.SetByte(pt.Bytes(b"\x00"), pt.Int(0), value.stored_value.load())
-
-    expected, _ = expectEncoding.__teal__(options)
-    expected.addIncoming()
-    expected = pt.TealBlock.NormalizeBlocks(expected)
-
-    actual, _ = expr.__teal__(options)
-    actual.addIncoming()
-    actual = pt.TealBlock.NormalizeBlocks(actual)
-    with pt.TealComponent.Context.ignoreExprEquality():
-        assert actual == expected
-
-
-def test_Asset_get():
+def test_Asset_asset_id():
     value = abi.Asset()
-    expr = value.get()
+    expr = value.asset_id()
     assert expr.type_of() == pt.TealType.uint64
     assert expr.has_return() is False
 
     expected = pt.TealSimpleBlock(
         [
-            pt.TealOp(expr, pt.Op.load, value.stored_value.slot),
+            pt.TealOp(None, pt.Op.load, value.stored_value.slot),
+            pt.TealOp(None, pt.Op.txnas, "Assets"),
         ]
     )
     actual, _ = expr.__teal__(options)
@@ -177,47 +200,47 @@ def test_Asset_get():
         assert actual == expected
 
 
-def test_Asset_set():
-    val_to_set = 2
+def test_Asset_holding():
     value = abi.Asset()
-    expr = value.set(val_to_set)
-    assert expr.type_of() == pt.TealType.none
-    assert expr.has_return() is False
 
-    expected = pt.TealSimpleBlock(
-        [
-            pt.TealOp(expr, pt.Op.int, val_to_set),
-            pt.TealOp(None, pt.Op.store, value.stored_value.slot),
-        ]
+    accounts = (
+        (pt.Int(6), pt.Int(6)),
+        (
+            pt.Addr("QSA6K5MNJPEGO5SDSWXBM3K4UEI3Q2NCPS2OUXVJI5QPCHMVI27MFRSHKI"),
+            pt.Addr("QSA6K5MNJPEGO5SDSWXBM3K4UEI3Q2NCPS2OUXVJI5QPCHMVI27MFRSHKI"),
+        ),
+        (a := abi.Account(), a.referenced_index()),
     )
-    actual, _ = expr.__teal__(options)
-    actual.addIncoming()
-    actual = pt.TealBlock.NormalizeBlocks(actual)
 
-    with pt.TealComponent.Context.ignoreExprEquality():
-        assert actual == expected
+    for account, expected_account in accounts:
+        holding = value.holding(account)
+
+        assert type(holding) is pt.AssetHoldingObject
+
+        expected_asset = value.referenced_index()
+        actual_asset = holding._asset
 
+        actual_account = holding._account
 
-def test_Asset_deref():
-    val_to_set = 2
+        with pt.TealComponent.Context.ignoreExprEquality():
+            assert actual_asset.__teal__(options) == expected_asset.__teal__(options)
+            assert actual_account.__teal__(options) == expected_account.__teal__(
+                options
+            )
+
+
+def test_Asset_params():
     value = abi.Asset()
-    value.set(val_to_set)
-    expr = value.deref()
-    assert expr.type_of() == pt.TealType.uint64
-    assert expr.has_return() is False
 
-    expected = pt.TealSimpleBlock(
-        [
-            pt.TealOp(None, pt.Op.load, value.stored_value.slot),
-            pt.TealOp(None, pt.Op.txnas, "Assets"),
-        ]
-    )
-    actual, _ = expr.__teal__(options)
-    actual.addIncoming()
-    actual = pt.TealBlock.NormalizeBlocks(actual)
+    params = value.params()
+
+    assert type(params) is pt.AssetParamObject
+
+    expected = value.referenced_index()
+    actual = params._asset
 
     with pt.TealComponent.Context.ignoreExprEquality():
-        assert actual == expected
+        assert actual.__teal__(options) == expected.__teal__(options)
 
 
 def test_Application_str():
@@ -247,34 +270,16 @@ def test_Application_typespec():
     assert abi.Application().type_spec() == abi.ApplicationTypeSpec()
 
 
-def test_Application_encode():
-    value = abi.Application()
-    expr = value.encode()
-    assert expr.type_of() == pt.TealType.bytes
-    assert expr.has_return() is False
-
-    expectEncoding = pt.SetByte(pt.Bytes(b"\x00"), pt.Int(0), value.stored_value.load())
-
-    expected, _ = expectEncoding.__teal__(options)
-    expected.addIncoming()
-    expected = pt.TealBlock.NormalizeBlocks(expected)
-
-    actual, _ = expr.__teal__(options)
-    actual.addIncoming()
-    actual = pt.TealBlock.NormalizeBlocks(actual)
-    with pt.TealComponent.Context.ignoreExprEquality():
-        assert actual == expected
-
-
-def test_Application_get():
+def test_Application_application_id():
     value = abi.Application()
-    expr = value.get()
+    expr = value.application_id()
     assert expr.type_of() == pt.TealType.uint64
     assert expr.has_return() is False
 
     expected = pt.TealSimpleBlock(
         [
-            pt.TealOp(expr, pt.Op.load, value.stored_value.slot),
+            pt.TealOp(None, pt.Op.load, value.stored_value.slot),
+            pt.TealOp(None, pt.Op.txnas, "Applications"),
         ]
     )
     actual, _ = expr.__teal__(options)
@@ -285,44 +290,15 @@ def test_Application_get():
         assert actual == expected
 
 
-def test_Application_set():
-    val_to_set = 2
+def test_Application_params():
     value = abi.Application()
-    expr = value.set(val_to_set)
-    assert expr.type_of() == pt.TealType.none
-    assert expr.has_return() is False
-
-    expected = pt.TealSimpleBlock(
-        [
-            pt.TealOp(expr, pt.Op.int, val_to_set),
-            pt.TealOp(None, pt.Op.store, value.stored_value.slot),
-        ]
-    )
-    actual, _ = expr.__teal__(options)
-    actual.addIncoming()
-    actual = pt.TealBlock.NormalizeBlocks(actual)
 
-    with pt.TealComponent.Context.ignoreExprEquality():
-        assert actual == expected
+    params = value.params()
 
+    assert type(params) is pt.AppParamObject
 
-def test_Application_deref():
-    val_to_set = 2
-    value = abi.Application()
-    value.set(val_to_set)
-    expr = value.deref()
-    assert expr.type_of() == pt.TealType.uint64
-    assert expr.has_return() is False
-
-    expected = pt.TealSimpleBlock(
-        [
-            pt.TealOp(None, pt.Op.load, value.stored_value.slot),
-            pt.TealOp(None, pt.Op.txnas, "Applications"),
-        ]
-    )
-    actual, _ = expr.__teal__(options)
-    actual.addIncoming()
-    actual = pt.TealBlock.NormalizeBlocks(actual)
+    expected = value.referenced_index()
+    actual = params._app
 
     with pt.TealComponent.Context.ignoreExprEquality():
-        assert actual == expected
+        assert actual.__teal__(options) == expected.__teal__(options)
diff --git a/pyteal/ast/acct_test.py b/pyteal/ast/acct_test.py
index 52fc89132..66958b6ae 100644
--- a/pyteal/ast/acct_test.py
+++ b/pyteal/ast/acct_test.py
@@ -1,4 +1,5 @@
 import pyteal as pt
+from pyteal.ast.maybe_test import assert_MaybeValue_equality
 
 options = pt.CompileOptions()
 teal4Options = pt.CompileOptions(version=4)
@@ -73,3 +74,23 @@ def test_acct_param_auth_addr_valid():
 
     with pt.TealComponent.Context.ignoreExprEquality():
         assert actual == expected
+
+
+def test_AccountParamObject():
+    for account in (
+        pt.Int(7),
+        pt.Addr("QSA6K5MNJPEGO5SDSWXBM3K4UEI3Q2NCPS2OUXVJI5QPCHMVI27MFRSHKI"),
+    ):
+        obj = pt.AccountParamObject(account)
+
+        assert obj._account is account
+
+        assert_MaybeValue_equality(
+            obj.balance(), pt.AccountParam.balance(account), teal6Options
+        )
+        assert_MaybeValue_equality(
+            obj.min_balance(), pt.AccountParam.minBalance(account), teal6Options
+        )
+        assert_MaybeValue_equality(
+            obj.auth_address(), pt.AccountParam.authAddr(account), teal6Options
+        )
diff --git a/pyteal/ast/app_test.py b/pyteal/ast/app_test.py
index 5d16ab876..52241ae20 100644
--- a/pyteal/ast/app_test.py
+++ b/pyteal/ast/app_test.py
@@ -1,6 +1,7 @@
 import pytest
 
 import pyteal as pt
+from pyteal.ast.maybe_test import assert_MaybeValue_equality
 
 options = pt.CompileOptions()
 teal4Options = pt.CompileOptions(version=4)
@@ -674,3 +675,40 @@ def test_app_param_address_valid():
 def test_app_param_address_invalid():
     with pytest.raises(pt.TealTypeError):
         pt.AppParam.address(pt.Txn.sender())
+
+
+def test_AppParamObject():
+    for app in (pt.Int(1), pt.Int(100)):
+        obj = pt.AppParamObject(app)
+
+        assert obj._app is app
+
+        assert_MaybeValue_equality(
+            obj.approval_program(), pt.AppParam.approvalProgram(app), teal5Options
+        )
+        assert_MaybeValue_equality(
+            obj.clear_state_program(), pt.AppParam.clearStateProgram(app), teal5Options
+        )
+        assert_MaybeValue_equality(
+            obj.global_num_unit(), pt.AppParam.globalNumUnit(app), teal5Options
+        )
+        assert_MaybeValue_equality(
+            obj.global_num_byte_slice(),
+            pt.AppParam.globalNumByteSlice(app),
+            teal5Options,
+        )
+        assert_MaybeValue_equality(
+            obj.local_num_unit(), pt.AppParam.localNumUnit(app), teal5Options
+        )
+        assert_MaybeValue_equality(
+            obj.local_num_byte_slice(), pt.AppParam.localNumByteSlice(app), teal5Options
+        )
+        assert_MaybeValue_equality(
+            obj.extra_program_pages(), pt.AppParam.extraProgramPages(app), teal5Options
+        )
+        assert_MaybeValue_equality(
+            obj.creator_address(), pt.AppParam.creator(app), teal5Options
+        )
+        assert_MaybeValue_equality(
+            obj.address(), pt.AppParam.address(app), teal5Options
+        )
diff --git a/pyteal/ast/asset_test.py b/pyteal/ast/asset_test.py
index 6ac9cf0e1..642afd076 100644
--- a/pyteal/ast/asset_test.py
+++ b/pyteal/ast/asset_test.py
@@ -1,6 +1,7 @@
 import pytest
 
 import pyteal as pt
+from pyteal.ast.maybe_test import assert_MaybeValue_equality
 
 teal2Options = pt.CompileOptions()
 teal4Options = pt.CompileOptions(version=4)
@@ -706,3 +707,62 @@ def test_asset_param_creator_valid():
 def test_asset_param_creator_invalid():
     with pytest.raises(pt.TealTypeError):
         pt.AssetParam.creator(pt.Txn.sender())
+
+
+def test_AssetHoldingObject():
+    for asset in (pt.Int(1), pt.Int(100)):
+        for account in (
+            pt.Int(7),
+            pt.Addr("QSA6K5MNJPEGO5SDSWXBM3K4UEI3Q2NCPS2OUXVJI5QPCHMVI27MFRSHKI"),
+        ):
+            obj = pt.AssetHoldingObject(asset, account)
+
+            assert obj._asset is asset
+            assert obj._account is account
+
+            assert_MaybeValue_equality(
+                obj.balance(), pt.AssetHolding.balance(account, asset), teal5Options
+            )
+            assert_MaybeValue_equality(
+                obj.frozen(), pt.AssetHolding.frozen(account, asset), teal5Options
+            )
+
+
+def test_AssetParamObject():
+    for asset in (pt.Int(1), pt.Int(100)):
+        obj = pt.AssetParamObject(asset)
+
+        assert obj._asset is asset
+
+        assert_MaybeValue_equality(
+            obj.total(), pt.AssetParam.total(asset), teal5Options
+        )
+        assert_MaybeValue_equality(
+            obj.decimals(), pt.AssetParam.decimals(asset), teal5Options
+        )
+        assert_MaybeValue_equality(
+            obj.default_frozen(), pt.AssetParam.defaultFrozen(asset), teal5Options
+        )
+        assert_MaybeValue_equality(
+            obj.unit_name(), pt.AssetParam.unitName(asset), teal5Options
+        )
+        assert_MaybeValue_equality(obj.name(), pt.AssetParam.name(asset), teal5Options)
+        assert_MaybeValue_equality(obj.url(), pt.AssetParam.url(asset), teal5Options)
+        assert_MaybeValue_equality(
+            obj.metadata_hash(), pt.AssetParam.metadataHash(asset), teal5Options
+        )
+        assert_MaybeValue_equality(
+            obj.manager_address(), pt.AssetParam.manager(asset), teal5Options
+        )
+        assert_MaybeValue_equality(
+            obj.reserve_address(), pt.AssetParam.reserve(asset), teal5Options
+        )
+        assert_MaybeValue_equality(
+            obj.freeze_address(), pt.AssetParam.freeze(asset), teal5Options
+        )
+        assert_MaybeValue_equality(
+            obj.clawback_address(), pt.AssetParam.clawback(asset), teal5Options
+        )
+        assert_MaybeValue_equality(
+            obj.creator_address(), pt.AssetParam.creator(asset), teal5Options
+        )
diff --git a/pyteal/ast/maybe_test.py b/pyteal/ast/maybe_test.py
index fa57ba5ca..0b4da7471 100644
--- a/pyteal/ast/maybe_test.py
+++ b/pyteal/ast/maybe_test.py
@@ -3,6 +3,26 @@
 options = pt.CompileOptions()
 
 
+def assert_MaybeValue_equality(
+    actual: pt.MaybeValue, expected: pt.MaybeValue, options: pt.CompileOptions
+):
+    actual_block, _ = actual.__teal__(options)
+    actual_block.addIncoming()
+    actual_block = pt.TealBlock.NormalizeBlocks(actual_block)
+
+    expected_block, _ = expected.__teal__(options)
+    expected_block.addIncoming()
+    expected_block = pt.TealBlock.NormalizeBlocks(expected_block)
+
+    with pt.TealComponent.Context.ignoreExprEquality(), pt.TealComponent.Context.ignoreScratchSlotEquality():
+        assert actual_block == expected_block
+
+    assert pt.TealBlock.MatchScratchSlotReferences(
+        pt.TealBlock.GetReferencedScratchSlots(actual_block),
+        pt.TealBlock.GetReferencedScratchSlots(expected_block),
+    )
+
+
 def test_maybe_value():
     ops = (
         pt.Op.app_global_get_ex,
diff --git a/pyteal/ast/subroutine_test.py b/pyteal/ast/subroutine_test.py
index a58df675f..2e9df0ac2 100644
--- a/pyteal/ast/subroutine_test.py
+++ b/pyteal/ast/subroutine_test.py
@@ -240,7 +240,7 @@ def fn_w_tuple1arg(
 def test_subroutine_return_reference():
     @ABIReturnSubroutine
     def invalid_ret_type(*, output: pt.abi.Account):
-        return output.set(0)
+        return output.decode(pt.Bytes(b"\x00"))
 
     with pytest.raises(pt.TealInputError):
         invalid_ret_type.method_signature()