From 48f6084b2dae6a3d14493f2cd6c19944683bc677 Mon Sep 17 00:00:00 2001 From: Jacob Daitzman Date: Thu, 6 Oct 2022 15:13:10 -0400 Subject: [PATCH 01/17] Transfer existing `AccountParam` methods to use fields and expressions --- pyteal/ast/acct.py | 101 +++++++++++++++++++++++++++++----------- pyteal/ast/acct_test.py | 2 - 2 files changed, 73 insertions(+), 30 deletions(-) diff --git a/pyteal/ast/acct.py b/pyteal/ast/acct.py index 7f61e10ee..922cad80a 100644 --- a/pyteal/ast/acct.py +++ b/pyteal/ast/acct.py @@ -1,59 +1,104 @@ -from typing import Final +from enum import Enum +from typing import Final, TYPE_CHECKING +from pyteal.errors import verifyFieldVersion from pyteal.types import TealType, require_type from pyteal.ir import Op +from pyteal.ast.leafexpr import LeafExpr from pyteal.ast.expr import Expr from pyteal.ast.maybe import MaybeValue +if TYPE_CHECKING: + from pyteal.compiler import CompileOptions + + +class AccountParamField(Enum): + # fmt: off + # id | name | type | min version + balance = (0, "AcctBalance", TealType.uint64, 6) + min_balance = (1, "AcctMinBalance", TealType.uint64, 6) + auth_addr = (2, "AcctAuthAddr", TealType.bytes, 6) + total_num_uint = (3, "AcctTotalNumUint", TealType.uint64, 8) + total_num_byte_slice = (4, "AcctTotalNumByteSlice", TealType.uint64, 8) + total_extra_app_pages = (5, "AcctTotalExtraAppPages", TealType.uint64, 8) + total_apps_created = (6, "AcctTotalAppsCreated", TealType.uint64, 8) + total_apps_opted_in = (7, "AcctTotalAppsOptedIn", TealType.uint64, 8) + total_box_bytes = (8, "AcctTotalBoxBytes", TealType.uint64, 8) + total_assets_created = (9, "AcctTotalAssetsCreated", TealType.uint64, 8) + total_assets = (10, "AcctTotalAssets", TealType.uint64, 8) + total_boxes = (11, "AcctTotalBoxes", TealType.uint64, 8) + # fmt: on + + def __init__(self, id: int, name: str, type: TealType, min_version: int) -> None: + self.id = id + self.arg_name = name + self.type = type + self.min_version = min_version + + def type_of(self) -> TealType: + return self.type + + +AccountParamField.__module__ = "pyteal" + + +class AccountParamExpr(MaybeValue): + """A maybe value expression that accesses an account parameter field from a given account.""" + + def __init__(self, field: AccountParamField, acct: Expr) -> None: + super().__init__( + Op.acct_params_get, + field.type_of(), + immediate_args=[field.arg_name], + args=[acct] + ) + require_type(acct, TealType.anytype) + + self.field = field + self.acct = acct + + def __str__(self): + return "(AccountParam {} {})".format(self.field.arg_name, self.acct) + + def __teal__(self, options: "CompileOptions"): + verifyFieldVersion(self.field.arg_name, self.field.min_version, options.version) + + return super().__teal__(options) + + +AccountParamExpr.__module__ = "pyteal" + class AccountParam: @classmethod - def balance(cls, acct: Expr) -> MaybeValue: + def balance(cls, acct: Expr) -> AccountParamExpr: """Get the current balance in microalgos an account. Args: acct: An index into Txn.accounts that corresponds to the application to check or an address available at runtime. May evaluate to uint64 or an address. """ - require_type(acct, TealType.anytype) - return MaybeValue( - Op.acct_params_get, - TealType.uint64, - immediate_args=["AcctBalance"], - args=[acct], - ) + return AccountParamExpr(AccountParamField.balance, acct) @classmethod - def minBalance(cls, acct: Expr) -> MaybeValue: + def minBalance(cls, acct: Expr) -> AccountParamExpr: """Get the minimum balance in microalgos for an account. Args: acct: An index into Txn.accounts that corresponds to the application to check or an address available at runtime. May evaluate to uint64 or an address. """ - require_type(acct, TealType.anytype) - return MaybeValue( - Op.acct_params_get, - TealType.uint64, - immediate_args=["AcctMinBalance"], - args=[acct], - ) + return AccountParamExpr(AccountParamField.min_balance, acct) @classmethod - def authAddr(cls, acct: Expr) -> MaybeValue: + def authAddr(cls, acct: Expr) -> AccountParamExpr: """Get the authorizing address for an account. If the account is not rekeyed, the empty addresss is returned. Args: acct: An index into Txn.accounts that corresponds to the application to check or an address available at runtime. May evaluate to uint64 or an address. """ - require_type(acct, TealType.anytype) - return MaybeValue( - Op.acct_params_get, - TealType.bytes, - immediate_args=["AcctAuthAddr"], - args=[acct], - ) + return AccountParamExpr(AccountParamField.auth_addr, acct) AccountParam.__module__ = "pyteal" @@ -71,15 +116,15 @@ def __init__(self, account: Expr) -> None: """ self._account: Final = account - def balance(self) -> MaybeValue: + def balance(self) -> AccountParamExpr: """Get the current balance in microAlgos for the account""" return AccountParam.balance(self._account) - def min_balance(self) -> MaybeValue: + def min_balance(self) -> AccountParamExpr: """Get the minimum balance in microAlgos for the account.""" return AccountParam.minBalance(self._account) - def auth_address(self) -> MaybeValue: + def auth_address(self) -> AccountParamExpr: """Get the authorizing address for the account. If the account is not rekeyed, the empty address is returned.""" diff --git a/pyteal/ast/acct_test.py b/pyteal/ast/acct_test.py index e4f12f958..b6ca995d7 100644 --- a/pyteal/ast/acct_test.py +++ b/pyteal/ast/acct_test.py @@ -2,8 +2,6 @@ from pyteal.ast.maybe_test import assert_MaybeValue_equality options = pt.CompileOptions() -avm4Options = pt.CompileOptions(version=4) -avm5Options = pt.CompileOptions(version=5) avm6Options = pt.CompileOptions(version=6) From 161547f7acbe76225f58b24fa7f8725b00b1d24b Mon Sep 17 00:00:00 2001 From: Jacob Daitzman Date: Thu, 6 Oct 2022 15:19:43 -0400 Subject: [PATCH 02/17] Add version checks to existing `AccountParam` tests --- pyteal/ast/acct_test.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/pyteal/ast/acct_test.py b/pyteal/ast/acct_test.py index b6ca995d7..f50c23e9b 100644 --- a/pyteal/ast/acct_test.py +++ b/pyteal/ast/acct_test.py @@ -1,7 +1,10 @@ +import pytest + import pyteal as pt from pyteal.ast.maybe_test import assert_MaybeValue_equality options = pt.CompileOptions() +avm5Options = pt.CompileOptions(version=5) avm6Options = pt.CompileOptions(version=6) @@ -27,6 +30,9 @@ def test_acct_param_balance_valid(): with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected + with pytest.raises(pt.TealInputError): + expr.__teal__(avm5Options) + def test_acct_param_min_balance_valid(): arg = pt.Int(0) @@ -50,6 +56,9 @@ def test_acct_param_min_balance_valid(): with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected + with pytest.raises(pt.TealInputError): + expr.__teal__(avm5Options) + def test_acct_param_auth_addr_valid(): arg = pt.Int(1) @@ -73,6 +82,9 @@ def test_acct_param_auth_addr_valid(): with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected + with pytest.raises(pt.TealInputError): + expr.__teal__(avm5Options) + def test_AccountParamObject(): for account in ( From 1db7a6fb4665538007b4bd58e58004798701d677 Mon Sep 17 00:00:00 2001 From: Jacob Daitzman Date: Thu, 6 Oct 2022 15:22:04 -0400 Subject: [PATCH 03/17] Add string representation test --- pyteal/ast/acct_test.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pyteal/ast/acct_test.py b/pyteal/ast/acct_test.py index f50c23e9b..25f167bf7 100644 --- a/pyteal/ast/acct_test.py +++ b/pyteal/ast/acct_test.py @@ -86,6 +86,12 @@ def test_acct_param_auth_addr_valid(): expr.__teal__(avm5Options) +def test_acct_param_string(): + assert str(pt.AccountParam.balance(pt.Int(1))) == "(AccountParam AcctBalance (Int 1))" + assert str(pt.AccountParam.minBalance(pt.Int(1))) == "(AccountParam AcctMinBalance (Int 1))" + assert str(pt.AccountParam.authAddr(pt.Int(1))) == "(AccountParam AcctAuthAddr (Int 1))" + + def test_AccountParamObject(): for account in ( pt.Int(7), From 2e76c333fee0a612ebdf42cd276a691672978027 Mon Sep 17 00:00:00 2001 From: Jacob Daitzman Date: Thu, 6 Oct 2022 16:06:27 -0400 Subject: [PATCH 04/17] `AccountParam` new class methods --- pyteal/ast/acct.py | 98 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 94 insertions(+), 4 deletions(-) diff --git a/pyteal/ast/acct.py b/pyteal/ast/acct.py index 922cad80a..38feca5c2 100644 --- a/pyteal/ast/acct.py +++ b/pyteal/ast/acct.py @@ -23,10 +23,10 @@ class AccountParamField(Enum): total_extra_app_pages = (5, "AcctTotalExtraAppPages", TealType.uint64, 8) total_apps_created = (6, "AcctTotalAppsCreated", TealType.uint64, 8) total_apps_opted_in = (7, "AcctTotalAppsOptedIn", TealType.uint64, 8) - total_box_bytes = (8, "AcctTotalBoxBytes", TealType.uint64, 8) - total_assets_created = (9, "AcctTotalAssetsCreated", TealType.uint64, 8) - total_assets = (10, "AcctTotalAssets", TealType.uint64, 8) - total_boxes = (11, "AcctTotalBoxes", TealType.uint64, 8) + total_assets_created = (8, "AcctTotalAssetsCreated", TealType.uint64, 8) + total_assets = (9, "AcctTotalAssets", TealType.uint64, 8) + total_boxes = (10, "AcctTotalBoxes", TealType.uint64, 8) + total_box_bytes = (11, "AcctTotalBoxBytes", TealType.uint64, 8) # fmt: on def __init__(self, id: int, name: str, type: TealType, min_version: int) -> None: @@ -100,6 +100,96 @@ def authAddr(cls, acct: Expr) -> AccountParamExpr: """ return AccountParamExpr(AccountParamField.auth_addr, acct) + @classmethod + def totalNumUint(cls, acct: Expr) -> AccountParamExpr: + """Get the total number of uint64 values allocated by the account in Global and Local States. + + Args: + acct: An index into Txn.accounts that corresponds to the application to check or an address available at runtime. + May evaluate to uint64 or an address. + """ + return AccountParamExpr(AccountParamField.total_num_uint, acct) + + @classmethod + def totalNumByteSlice(cls, acct: Expr) -> AccountParamExpr: + """Get the total number of byte array values allocated by the account in Global and Local States. + + Args: + acct: An index into Txn.accounts that corresponds to the application to check or an address available at runtime. + May evaluate to uint64 or an address. + """ + return AccountParamExpr(AccountParamField.total_num_byte_slice, acct) + + @classmethod + def totalExtraAppPages(cls, acct: Expr) -> AccountParamExpr: + """Get the number of extra app code pages used by the account. + + Args: + acct: An index into Txn.accounts that corresponds to the application to check or an address available at runtime. + May evaluate to uint64 or an address. + """ + return AccountParamExpr(AccountParamField.total_extra_app_pages, acct) + + @classmethod + def totalAppsCreated(cls, acct: Expr) -> AccountParamExpr: + """Get the number of existing apps created by the account. + + Args: + acct: An index into Txn.accounts that corresponds to the application to check or an address available at runtime. + May evaluate to uint64 or an address. + """ + return AccountParamExpr(AccountParamField.total_apps_created, acct) + + @classmethod + def totalAppsOptedIn(cls, acct: Expr) -> AccountParamExpr: + """Get the number of apps the account is opted into. + + Args: + acct: An index into Txn.accounts that corresponds to the application to check or an address available at runtime. + May evaluate to uint64 or an address. + """ + return AccountParamExpr(AccountParamField.total_apps_opted_in, acct) + + @classmethod + def totalAssetsCreated(cls, acct: Expr) -> AccountParamExpr: + """Get the number of existing ASAs created by the account. + + Args: + acct: An index into Txn.accounts that corresponds to the application to check or an address available at runtime. + May evaluate to uint64 or an address. + """ + return AccountParamExpr(AccountParamField.total_assets_created, acct) + + @classmethod + def totalAssets(cls, acct: Expr) -> AccountParamExpr: + """Get the number of ASAs held by the account (including ASAs the account created). + + Args: + acct: An index into Txn.accounts that corresponds to the application to check or an address available at runtime. + May evaluate to uint64 or an address. + """ + return AccountParamExpr(AccountParamField.total_assets, acct) + + + @classmethod + def totalBoxes(cls, acct: Expr) -> AccountParamExpr: + """Get the number of existing boxes created by the account's app. + + Args: + acct: An index into Txn.accounts that corresponds to the application to check or an address available at runtime. + May evaluate to uint64 or an address. + """ + return AccountParamExpr(AccountParamField.total_boxes, acct) + + @classmethod + def totalBoxBytes(cls, acct: Expr) -> AccountParamExpr: + """Get the total number of bytes used by the account's app's box keys and values. + + Args: + acct: An index into Txn.accounts that corresponds to the application to check or an address available at runtime. + May evaluate to uint64 or an address. + """ + return AccountParamExpr(AccountParamField.total_box_bytes, acct) AccountParam.__module__ = "pyteal" From 92dc2325d3ab97c0e709ac18500113cb19af5efc Mon Sep 17 00:00:00 2001 From: Jacob Daitzman Date: Thu, 6 Oct 2022 16:07:38 -0400 Subject: [PATCH 05/17] Switch to parametrized `AccountParam` tests --- pyteal/ast/acct_test.py | 80 ++++++++++------------------------------- 1 file changed, 19 insertions(+), 61 deletions(-) diff --git a/pyteal/ast/acct_test.py b/pyteal/ast/acct_test.py index 25f167bf7..55428b4e8 100644 --- a/pyteal/ast/acct_test.py +++ b/pyteal/ast/acct_test.py @@ -1,81 +1,39 @@ import pytest import pyteal as pt +from pyteal.ast.acct import AccountParamField from pyteal.ast.maybe_test import assert_MaybeValue_equality -options = pt.CompileOptions() -avm5Options = pt.CompileOptions(version=5) avm6Options = pt.CompileOptions(version=6) - -def test_acct_param_balance_valid(): +@pytest.mark.parametrize( + "method_name,field_name", + [ + ("balance", "balance"), + ("minBalance", "min_balance"), + ("authAddr", "auth_addr"), + ] +) +def test_acct_param_fields_valid(method_name, field_name): arg = pt.Int(1) - expr = pt.AccountParam.balance(arg) + account_param_method = getattr(pt.AccountParam, method_name) + expr = account_param_method(arg) assert expr.type_of() == pt.TealType.none - assert expr.value().type_of() == pt.TealType.uint64 - - expected = pt.TealSimpleBlock( - [ - pt.TealOp(arg, pt.Op.int, 1), - pt.TealOp(expr, pt.Op.acct_params_get, "AcctBalance"), - pt.TealOp(None, pt.Op.store, expr.slotOk), - pt.TealOp(None, pt.Op.store, expr.slotValue), - ] - ) - - actual, _ = expr.__teal__(avm6Options) - actual.addIncoming() - actual = pt.TealBlock.NormalizeBlocks(actual) - - with pt.TealComponent.Context.ignoreExprEquality(): - assert actual == expected - - with pytest.raises(pt.TealInputError): - expr.__teal__(avm5Options) - -def test_acct_param_min_balance_valid(): - arg = pt.Int(0) - expr = pt.AccountParam.minBalance(arg) - assert expr.type_of() == pt.TealType.none - assert expr.value().type_of() == pt.TealType.uint64 - - expected = pt.TealSimpleBlock( - [ - pt.TealOp(arg, pt.Op.int, 0), - pt.TealOp(expr, pt.Op.acct_params_get, "AcctMinBalance"), - pt.TealOp(None, pt.Op.store, expr.slotOk), - pt.TealOp(None, pt.Op.store, expr.slotValue), - ] - ) - - actual, _ = expr.__teal__(avm6Options) - actual.addIncoming() - actual = pt.TealBlock.NormalizeBlocks(actual) - - with pt.TealComponent.Context.ignoreExprEquality(): - assert actual == expected - - with pytest.raises(pt.TealInputError): - expr.__teal__(avm5Options) - - -def test_acct_param_auth_addr_valid(): - arg = pt.Int(1) - expr = pt.AccountParam.authAddr(arg) - assert expr.type_of() == pt.TealType.none - assert expr.value().type_of() == pt.TealType.bytes + account_param_field = AccountParamField[field_name] + assert expr.value().type_of() == account_param_field.type_of() expected = pt.TealSimpleBlock( [ pt.TealOp(arg, pt.Op.int, 1), - pt.TealOp(expr, pt.Op.acct_params_get, "AcctAuthAddr"), + pt.TealOp(expr, pt.Op.acct_params_get, account_param_field.arg_name), pt.TealOp(None, pt.Op.store, expr.slotOk), pt.TealOp(None, pt.Op.store, expr.slotValue), ] ) - actual, _ = expr.__teal__(avm6Options) + supported_options_version = pt.CompileOptions(version=account_param_field.min_version) + actual, _ = expr.__teal__(supported_options_version) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -83,8 +41,8 @@ def test_acct_param_auth_addr_valid(): assert actual == expected with pytest.raises(pt.TealInputError): - expr.__teal__(avm5Options) - + unsupported_options_version = pt.CompileOptions(version=account_param_field.min_version - 1) + expr.__teal__(unsupported_options_version) def test_acct_param_string(): assert str(pt.AccountParam.balance(pt.Int(1))) == "(AccountParam AcctBalance (Int 1))" From ad92dbf49b1026fc693abc61a29c47d553cff4e9 Mon Sep 17 00:00:00 2001 From: Jacob Daitzman Date: Thu, 6 Oct 2022 16:09:24 -0400 Subject: [PATCH 06/17] Test new `AssetParam` methods --- pyteal/ast/acct_test.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pyteal/ast/acct_test.py b/pyteal/ast/acct_test.py index 55428b4e8..1b9ccdda8 100644 --- a/pyteal/ast/acct_test.py +++ b/pyteal/ast/acct_test.py @@ -12,6 +12,15 @@ ("balance", "balance"), ("minBalance", "min_balance"), ("authAddr", "auth_addr"), + ("totalNumUint", "total_num_uint"), + ("totalNumByteSlice", "total_num_byte_slice"), + ("totalExtraAppPages", "total_extra_app_pages"), + ("totalAppsCreated", "total_apps_created"), + ("totalAppsOptedIn", "total_apps_opted_in"), + ("totalAssetsCreated", "total_assets_created"), + ("totalAssets", "total_assets"), + ("totalBoxes", "total_boxes"), + ("totalBoxBytes", "total_box_bytes"), ] ) def test_acct_param_fields_valid(method_name, field_name): From bf08b920b16df95db9e594ee8500bf0979747c74 Mon Sep 17 00:00:00 2001 From: Jacob Daitzman Date: Thu, 6 Oct 2022 16:15:35 -0400 Subject: [PATCH 07/17] Add `AccountParamObject` methods --- pyteal/ast/acct.py | 36 ++++++++++++++++++++++++++++++++++++ pyteal/ast/acct_test.py | 29 +++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/pyteal/ast/acct.py b/pyteal/ast/acct.py index 38feca5c2..f808005d0 100644 --- a/pyteal/ast/acct.py +++ b/pyteal/ast/acct.py @@ -220,5 +220,41 @@ def auth_address(self) -> AccountParamExpr: If the account is not rekeyed, the empty address is returned.""" return AccountParam.authAddr(self._account) + def total_num_uint(self) -> AccountParamExpr: + """Get the total number of uint64 values allocated by the account in Global and Local States.""" + return AccountParam.totalNumUint(self._account) + + def total_num_byte_slice(self) -> AccountParamExpr: + """Get the total number of byte array values allocated by the account in Global and Local States.""" + return AccountParam.totalNumByteSlice(self._account) + + def total_extra_app_pages(self) -> AccountParamExpr: + """Get the number of extra app code pages used by the account.""" + return AccountParam.totalExtraAppPages(self._account) + + def total_apps_created(self) -> AccountParamExpr: + """Get the number of existing apps created by the account.""" + return AccountParam.totalAppsCreated(self._account) + + def total_apps_opted_in(self) -> AccountParamExpr: + """Get the number of apps the account is opted into.""" + return AccountParam.totalAppsOptedIn(self._account) + + def total_assets_created(self) -> AccountParamExpr: + """Get the number of existing ASAs created by the account.""" + return AccountParam.totalAssetsCreated(self._account) + + def total_assets(self) -> AccountParamExpr: + """Get the number of ASAs held by the account (including ASAs the account created).""" + return AccountParam.totalAssets(self._account) + + def total_boxes(self) -> AccountParamExpr: + """Get the number of existing boxes created by the account's app.""" + return AccountParam.totalBoxes(self._account) + + def total_box_bytes(self) -> AccountParamExpr: + """Get the total number of bytes used by the account's app's box keys and values.""" + return AccountParam.totalBoxBytes(self._account) + AccountParamObject.__module__ = "pyteal" diff --git a/pyteal/ast/acct_test.py b/pyteal/ast/acct_test.py index 1b9ccdda8..9e0b37f78 100644 --- a/pyteal/ast/acct_test.py +++ b/pyteal/ast/acct_test.py @@ -5,6 +5,7 @@ from pyteal.ast.maybe_test import assert_MaybeValue_equality avm6Options = pt.CompileOptions(version=6) +avm8Options = pt.CompileOptions(version=8) @pytest.mark.parametrize( "method_name,field_name", @@ -77,3 +78,31 @@ def test_AccountParamObject(): assert_MaybeValue_equality( obj.auth_address(), pt.AccountParam.authAddr(account), avm6Options ) + + assert_MaybeValue_equality( + obj.total_num_uint(), pt.AccountParam.totalNumUint(account), avm8Options + ) + assert_MaybeValue_equality( + obj.total_num_byte_slice(), pt.AccountParam.totalNumByteSlice(account), avm8Options + ) + assert_MaybeValue_equality( + obj.total_extra_app_pages(), pt.AccountParam.totalExtraAppPages(account), avm8Options + ) + assert_MaybeValue_equality( + obj.total_apps_created(), pt.AccountParam.totalAppsCreated(account), avm8Options + ) + assert_MaybeValue_equality( + obj.total_apps_opted_in(), pt.AccountParam.totalAppsOptedIn(account), avm8Options + ) + assert_MaybeValue_equality( + obj.total_assets_created(), pt.AccountParam.totalAssetsCreated(account), avm8Options + ) + assert_MaybeValue_equality( + obj.total_assets(), pt.AccountParam.totalAssets(account), avm8Options + ) + assert_MaybeValue_equality( + obj.total_boxes(), pt.AccountParam.totalBoxes(account), avm8Options + ) + assert_MaybeValue_equality( + obj.total_box_bytes(), pt.AccountParam.totalBoxBytes(account), avm8Options + ) From a90dc82700b4069a6d39d65471b483ff93c763b6 Mon Sep 17 00:00:00 2001 From: Jacob Daitzman Date: Thu, 6 Oct 2022 16:30:03 -0400 Subject: [PATCH 08/17] Format with black --- pyteal/ast/acct.py | 22 ++++++++++---------- pyteal/ast/acct_test.py | 46 +++++++++++++++++++++++++++++++---------- 2 files changed, 46 insertions(+), 22 deletions(-) diff --git a/pyteal/ast/acct.py b/pyteal/ast/acct.py index f808005d0..e08be93c1 100644 --- a/pyteal/ast/acct.py +++ b/pyteal/ast/acct.py @@ -50,7 +50,7 @@ def __init__(self, field: AccountParamField, acct: Expr) -> None: Op.acct_params_get, field.type_of(), immediate_args=[field.arg_name], - args=[acct] + args=[acct], ) require_type(acct, TealType.anytype) @@ -103,7 +103,7 @@ def authAddr(cls, acct: Expr) -> AccountParamExpr: @classmethod def totalNumUint(cls, acct: Expr) -> AccountParamExpr: """Get the total number of uint64 values allocated by the account in Global and Local States. - + Args: acct: An index into Txn.accounts that corresponds to the application to check or an address available at runtime. May evaluate to uint64 or an address. @@ -113,7 +113,7 @@ def totalNumUint(cls, acct: Expr) -> AccountParamExpr: @classmethod def totalNumByteSlice(cls, acct: Expr) -> AccountParamExpr: """Get the total number of byte array values allocated by the account in Global and Local States. - + Args: acct: An index into Txn.accounts that corresponds to the application to check or an address available at runtime. May evaluate to uint64 or an address. @@ -123,7 +123,7 @@ def totalNumByteSlice(cls, acct: Expr) -> AccountParamExpr: @classmethod def totalExtraAppPages(cls, acct: Expr) -> AccountParamExpr: """Get the number of extra app code pages used by the account. - + Args: acct: An index into Txn.accounts that corresponds to the application to check or an address available at runtime. May evaluate to uint64 or an address. @@ -143,17 +143,17 @@ def totalAppsCreated(cls, acct: Expr) -> AccountParamExpr: @classmethod def totalAppsOptedIn(cls, acct: Expr) -> AccountParamExpr: """Get the number of apps the account is opted into. - + Args: acct: An index into Txn.accounts that corresponds to the application to check or an address available at runtime. May evaluate to uint64 or an address. """ return AccountParamExpr(AccountParamField.total_apps_opted_in, acct) - + @classmethod def totalAssetsCreated(cls, acct: Expr) -> AccountParamExpr: """Get the number of existing ASAs created by the account. - + Args: acct: An index into Txn.accounts that corresponds to the application to check or an address available at runtime. May evaluate to uint64 or an address. @@ -163,18 +163,17 @@ def totalAssetsCreated(cls, acct: Expr) -> AccountParamExpr: @classmethod def totalAssets(cls, acct: Expr) -> AccountParamExpr: """Get the number of ASAs held by the account (including ASAs the account created). - + Args: acct: An index into Txn.accounts that corresponds to the application to check or an address available at runtime. May evaluate to uint64 or an address. """ return AccountParamExpr(AccountParamField.total_assets, acct) - @classmethod def totalBoxes(cls, acct: Expr) -> AccountParamExpr: """Get the number of existing boxes created by the account's app. - + Args: acct: An index into Txn.accounts that corresponds to the application to check or an address available at runtime. May evaluate to uint64 or an address. @@ -184,13 +183,14 @@ def totalBoxes(cls, acct: Expr) -> AccountParamExpr: @classmethod def totalBoxBytes(cls, acct: Expr) -> AccountParamExpr: """Get the total number of bytes used by the account's app's box keys and values. - + Args: acct: An index into Txn.accounts that corresponds to the application to check or an address available at runtime. May evaluate to uint64 or an address. """ return AccountParamExpr(AccountParamField.total_box_bytes, acct) + AccountParam.__module__ = "pyteal" diff --git a/pyteal/ast/acct_test.py b/pyteal/ast/acct_test.py index 9e0b37f78..57b7d4765 100644 --- a/pyteal/ast/acct_test.py +++ b/pyteal/ast/acct_test.py @@ -7,6 +7,7 @@ avm6Options = pt.CompileOptions(version=6) avm8Options = pt.CompileOptions(version=8) + @pytest.mark.parametrize( "method_name,field_name", [ @@ -22,7 +23,7 @@ ("totalAssets", "total_assets"), ("totalBoxes", "total_boxes"), ("totalBoxBytes", "total_box_bytes"), - ] + ], ) def test_acct_param_fields_valid(method_name, field_name): arg = pt.Int(1) @@ -42,7 +43,9 @@ def test_acct_param_fields_valid(method_name, field_name): ] ) - supported_options_version = pt.CompileOptions(version=account_param_field.min_version) + supported_options_version = pt.CompileOptions( + version=account_param_field.min_version + ) actual, _ = expr.__teal__(supported_options_version) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -51,13 +54,24 @@ def test_acct_param_fields_valid(method_name, field_name): assert actual == expected with pytest.raises(pt.TealInputError): - unsupported_options_version = pt.CompileOptions(version=account_param_field.min_version - 1) + unsupported_options_version = pt.CompileOptions( + version=account_param_field.min_version - 1 + ) expr.__teal__(unsupported_options_version) + def test_acct_param_string(): - assert str(pt.AccountParam.balance(pt.Int(1))) == "(AccountParam AcctBalance (Int 1))" - assert str(pt.AccountParam.minBalance(pt.Int(1))) == "(AccountParam AcctMinBalance (Int 1))" - assert str(pt.AccountParam.authAddr(pt.Int(1))) == "(AccountParam AcctAuthAddr (Int 1))" + assert ( + str(pt.AccountParam.balance(pt.Int(1))) == "(AccountParam AcctBalance (Int 1))" + ) + assert ( + str(pt.AccountParam.minBalance(pt.Int(1))) + == "(AccountParam AcctMinBalance (Int 1))" + ) + assert ( + str(pt.AccountParam.authAddr(pt.Int(1))) + == "(AccountParam AcctAuthAddr (Int 1))" + ) def test_AccountParamObject(): @@ -83,19 +97,29 @@ def test_AccountParamObject(): obj.total_num_uint(), pt.AccountParam.totalNumUint(account), avm8Options ) assert_MaybeValue_equality( - obj.total_num_byte_slice(), pt.AccountParam.totalNumByteSlice(account), avm8Options + obj.total_num_byte_slice(), + pt.AccountParam.totalNumByteSlice(account), + avm8Options, ) assert_MaybeValue_equality( - obj.total_extra_app_pages(), pt.AccountParam.totalExtraAppPages(account), avm8Options + obj.total_extra_app_pages(), + pt.AccountParam.totalExtraAppPages(account), + avm8Options, ) assert_MaybeValue_equality( - obj.total_apps_created(), pt.AccountParam.totalAppsCreated(account), avm8Options + obj.total_apps_created(), + pt.AccountParam.totalAppsCreated(account), + avm8Options, ) assert_MaybeValue_equality( - obj.total_apps_opted_in(), pt.AccountParam.totalAppsOptedIn(account), avm8Options + obj.total_apps_opted_in(), + pt.AccountParam.totalAppsOptedIn(account), + avm8Options, ) assert_MaybeValue_equality( - obj.total_assets_created(), pt.AccountParam.totalAssetsCreated(account), avm8Options + obj.total_assets_created(), + pt.AccountParam.totalAssetsCreated(account), + avm8Options, ) assert_MaybeValue_equality( obj.total_assets(), pt.AccountParam.totalAssets(account), avm8Options From 0c16f4b6303b21adaf5e4f888546a4a7c1da69cd Mon Sep 17 00:00:00 2001 From: Jacob Daitzman Date: Thu, 6 Oct 2022 16:35:21 -0400 Subject: [PATCH 09/17] Fix `flake8` errors --- pyteal/ast/acct.py | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/pyteal/ast/acct.py b/pyteal/ast/acct.py index e08be93c1..13790604f 100644 --- a/pyteal/ast/acct.py +++ b/pyteal/ast/acct.py @@ -4,7 +4,6 @@ from pyteal.types import TealType, require_type from pyteal.ir import Op -from pyteal.ast.leafexpr import LeafExpr from pyteal.ast.expr import Expr from pyteal.ast.maybe import MaybeValue @@ -15,18 +14,18 @@ class AccountParamField(Enum): # fmt: off # id | name | type | min version - balance = (0, "AcctBalance", TealType.uint64, 6) - min_balance = (1, "AcctMinBalance", TealType.uint64, 6) - auth_addr = (2, "AcctAuthAddr", TealType.bytes, 6) - total_num_uint = (3, "AcctTotalNumUint", TealType.uint64, 8) - total_num_byte_slice = (4, "AcctTotalNumByteSlice", TealType.uint64, 8) - total_extra_app_pages = (5, "AcctTotalExtraAppPages", TealType.uint64, 8) - total_apps_created = (6, "AcctTotalAppsCreated", TealType.uint64, 8) - total_apps_opted_in = (7, "AcctTotalAppsOptedIn", TealType.uint64, 8) - total_assets_created = (8, "AcctTotalAssetsCreated", TealType.uint64, 8) - total_assets = (9, "AcctTotalAssets", TealType.uint64, 8) - total_boxes = (10, "AcctTotalBoxes", TealType.uint64, 8) - total_box_bytes = (11, "AcctTotalBoxBytes", TealType.uint64, 8) + balance = (0, "AcctBalance", TealType.uint64, 6) # noqa: E221 + min_balance = (1, "AcctMinBalance", TealType.uint64, 6) # noqa: E221 + auth_addr = (2, "AcctAuthAddr", TealType.bytes, 6) # noqa: E221 + total_num_uint = (3, "AcctTotalNumUint", TealType.uint64, 8) # noqa: E221 + total_num_byte_slice = (4, "AcctTotalNumByteSlice", TealType.uint64, 8) # noqa: E221 + total_extra_app_pages = (5, "AcctTotalExtraAppPages", TealType.uint64, 8) # noqa: E221 + total_apps_created = (6, "AcctTotalAppsCreated", TealType.uint64, 8) # noqa: E221 + total_apps_opted_in = (7, "AcctTotalAppsOptedIn", TealType.uint64, 8) # noqa: E221 + total_assets_created = (8, "AcctTotalAssetsCreated", TealType.uint64, 8) # noqa: E221 + total_assets = (9, "AcctTotalAssets", TealType.uint64, 8) # noqa: E221 + total_boxes = (10, "AcctTotalBoxes", TealType.uint64, 8) # noqa: E221 + total_box_bytes = (11, "AcctTotalBoxBytes", TealType.uint64, 8) # noqa: E221 # fmt: on def __init__(self, id: int, name: str, type: TealType, min_version: int) -> None: From a4c4c3fe3570ffbdb76817427efd65a8feff0eac Mon Sep 17 00:00:00 2001 From: Jacob Daitzman Date: Thu, 13 Oct 2022 15:44:45 -0400 Subject: [PATCH 10/17] Fix docstring_parser version From b3d5393f822f02a2b7dffd42077a0083fc650d97 Mon Sep 17 00:00:00 2001 From: Jacob Daitzman Date: Thu, 13 Oct 2022 16:17:44 -0400 Subject: [PATCH 11/17] Add optional `compile_check` to `MaybeValue` --- pyteal/ast/maybe.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pyteal/ast/maybe.py b/pyteal/ast/maybe.py index 4c6091953..7e52cc19f 100644 --- a/pyteal/ast/maybe.py +++ b/pyteal/ast/maybe.py @@ -1,4 +1,4 @@ -from typing import List, Union, TYPE_CHECKING +from typing import Callable, List, Union, TYPE_CHECKING from pyteal.errors import verifyProgramVersion from pyteal.types import TealType @@ -22,6 +22,7 @@ def __init__( *, immediate_args: List[Union[int, str]] = None, args: List[Expr] = None, + compile_check: Callable[["CompileOptions"], None] = lambda _: None, ): """Create a new MaybeValue. @@ -32,12 +33,13 @@ def __init__( args (optional): Stack arguments for the op. Defaults to None. """ - def local_version_check(option: "CompileOptions"): + def local_version_check(options: "CompileOptions"): verifyProgramVersion( minVersion=op.min_version, - version=option.version, + version=options.version, msg=f"{op.value} unavailable", ) + compile_check(options) types = [type, TealType.uint64] super().__init__( From 43397556b9cd92689106a78a15447dedd803d7d3 Mon Sep 17 00:00:00 2001 From: Jacob Daitzman Date: Thu, 13 Oct 2022 16:26:43 -0400 Subject: [PATCH 12/17] Use `MaybeValue` compile check instead of `AccountParamExpr` for field version checks --- pyteal/ast/acct.py | 102 ++++++++++++++++++---------------------- pyteal/ast/acct_test.py | 7 +-- 2 files changed, 49 insertions(+), 60 deletions(-) diff --git a/pyteal/ast/acct.py b/pyteal/ast/acct.py index 13790604f..1d3180328 100644 --- a/pyteal/ast/acct.py +++ b/pyteal/ast/acct.py @@ -41,153 +41,141 @@ def type_of(self) -> TealType: AccountParamField.__module__ = "pyteal" -class AccountParamExpr(MaybeValue): - """A maybe value expression that accesses an account parameter field from a given account.""" +class AccountParam: + @staticmethod + def __makeAccountParamExpr(field: AccountParamField, acct: Expr) -> MaybeValue: + require_type(acct, TealType.anytype) + + def field_version_check(options: "CompileOptions"): + verifyFieldVersion(field.arg_name, field.min_version, options.version) - def __init__(self, field: AccountParamField, acct: Expr) -> None: - super().__init__( + return MaybeValue( Op.acct_params_get, field.type_of(), immediate_args=[field.arg_name], args=[acct], + compile_check=field_version_check, ) - require_type(acct, TealType.anytype) - - self.field = field - self.acct = acct - - def __str__(self): - return "(AccountParam {} {})".format(self.field.arg_name, self.acct) - def __teal__(self, options: "CompileOptions"): - verifyFieldVersion(self.field.arg_name, self.field.min_version, options.version) - - return super().__teal__(options) - - -AccountParamExpr.__module__ = "pyteal" - - -class AccountParam: @classmethod - def balance(cls, acct: Expr) -> AccountParamExpr: + def balance(cls, acct: Expr) -> MaybeValue: """Get the current balance in microalgos an account. Args: acct: An index into Txn.accounts that corresponds to the application to check or an address available at runtime. May evaluate to uint64 or an address. """ - return AccountParamExpr(AccountParamField.balance, acct) + return cls.__makeAccountParamExpr(AccountParamField.balance, acct) @classmethod - def minBalance(cls, acct: Expr) -> AccountParamExpr: + def minBalance(cls, acct: Expr) -> MaybeValue: """Get the minimum balance in microalgos for an account. Args: acct: An index into Txn.accounts that corresponds to the application to check or an address available at runtime. May evaluate to uint64 or an address. """ - return AccountParamExpr(AccountParamField.min_balance, acct) + return cls.__makeAccountParamExpr(AccountParamField.min_balance, acct) @classmethod - def authAddr(cls, acct: Expr) -> AccountParamExpr: + def authAddr(cls, acct: Expr) -> MaybeValue: """Get the authorizing address for an account. If the account is not rekeyed, the empty addresss is returned. Args: acct: An index into Txn.accounts that corresponds to the application to check or an address available at runtime. May evaluate to uint64 or an address. """ - return AccountParamExpr(AccountParamField.auth_addr, acct) + return cls.__makeAccountParamExpr(AccountParamField.auth_addr, acct) @classmethod - def totalNumUint(cls, acct: Expr) -> AccountParamExpr: + def totalNumUint(cls, acct: Expr) -> MaybeValue: """Get the total number of uint64 values allocated by the account in Global and Local States. Args: acct: An index into Txn.accounts that corresponds to the application to check or an address available at runtime. May evaluate to uint64 or an address. """ - return AccountParamExpr(AccountParamField.total_num_uint, acct) + return cls.__makeAccountParamExpr(AccountParamField.total_num_uint, acct) @classmethod - def totalNumByteSlice(cls, acct: Expr) -> AccountParamExpr: + def totalNumByteSlice(cls, acct: Expr) -> MaybeValue: """Get the total number of byte array values allocated by the account in Global and Local States. Args: acct: An index into Txn.accounts that corresponds to the application to check or an address available at runtime. May evaluate to uint64 or an address. """ - return AccountParamExpr(AccountParamField.total_num_byte_slice, acct) + return cls.__makeAccountParamExpr(AccountParamField.total_num_byte_slice, acct) @classmethod - def totalExtraAppPages(cls, acct: Expr) -> AccountParamExpr: + def totalExtraAppPages(cls, acct: Expr) -> MaybeValue: """Get the number of extra app code pages used by the account. Args: acct: An index into Txn.accounts that corresponds to the application to check or an address available at runtime. May evaluate to uint64 or an address. """ - return AccountParamExpr(AccountParamField.total_extra_app_pages, acct) + return cls.__makeAccountParamExpr(AccountParamField.total_extra_app_pages, acct) @classmethod - def totalAppsCreated(cls, acct: Expr) -> AccountParamExpr: + def totalAppsCreated(cls, acct: Expr) -> MaybeValue: """Get the number of existing apps created by the account. Args: acct: An index into Txn.accounts that corresponds to the application to check or an address available at runtime. May evaluate to uint64 or an address. """ - return AccountParamExpr(AccountParamField.total_apps_created, acct) + return cls.__makeAccountParamExpr(AccountParamField.total_apps_created, acct) @classmethod - def totalAppsOptedIn(cls, acct: Expr) -> AccountParamExpr: + def totalAppsOptedIn(cls, acct: Expr) -> MaybeValue: """Get the number of apps the account is opted into. Args: acct: An index into Txn.accounts that corresponds to the application to check or an address available at runtime. May evaluate to uint64 or an address. """ - return AccountParamExpr(AccountParamField.total_apps_opted_in, acct) + return cls.__makeAccountParamExpr(AccountParamField.total_apps_opted_in, acct) @classmethod - def totalAssetsCreated(cls, acct: Expr) -> AccountParamExpr: + def totalAssetsCreated(cls, acct: Expr) -> MaybeValue: """Get the number of existing ASAs created by the account. Args: acct: An index into Txn.accounts that corresponds to the application to check or an address available at runtime. May evaluate to uint64 or an address. """ - return AccountParamExpr(AccountParamField.total_assets_created, acct) + return cls.__makeAccountParamExpr(AccountParamField.total_assets_created, acct) @classmethod - def totalAssets(cls, acct: Expr) -> AccountParamExpr: + def totalAssets(cls, acct: Expr) -> MaybeValue: """Get the number of ASAs held by the account (including ASAs the account created). Args: acct: An index into Txn.accounts that corresponds to the application to check or an address available at runtime. May evaluate to uint64 or an address. """ - return AccountParamExpr(AccountParamField.total_assets, acct) + return cls.__makeAccountParamExpr(AccountParamField.total_assets, acct) @classmethod - def totalBoxes(cls, acct: Expr) -> AccountParamExpr: + def totalBoxes(cls, acct: Expr) -> MaybeValue: """Get the number of existing boxes created by the account's app. Args: acct: An index into Txn.accounts that corresponds to the application to check or an address available at runtime. May evaluate to uint64 or an address. """ - return AccountParamExpr(AccountParamField.total_boxes, acct) + return cls.__makeAccountParamExpr(AccountParamField.total_boxes, acct) @classmethod - def totalBoxBytes(cls, acct: Expr) -> AccountParamExpr: + def totalBoxBytes(cls, acct: Expr) -> MaybeValue: """Get the total number of bytes used by the account's app's box keys and values. Args: acct: An index into Txn.accounts that corresponds to the application to check or an address available at runtime. May evaluate to uint64 or an address. """ - return AccountParamExpr(AccountParamField.total_box_bytes, acct) + return cls.__makeAccountParamExpr(AccountParamField.total_box_bytes, acct) AccountParam.__module__ = "pyteal" @@ -205,53 +193,53 @@ def __init__(self, account: Expr) -> None: """ self._account: Final = account - def balance(self) -> AccountParamExpr: + def balance(self) -> MaybeValue: """Get the current balance in microAlgos for the account""" return AccountParam.balance(self._account) - def min_balance(self) -> AccountParamExpr: + def min_balance(self) -> MaybeValue: """Get the minimum balance in microAlgos for the account.""" return AccountParam.minBalance(self._account) - def auth_address(self) -> AccountParamExpr: + 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) - def total_num_uint(self) -> AccountParamExpr: + def total_num_uint(self) -> MaybeValue: """Get the total number of uint64 values allocated by the account in Global and Local States.""" return AccountParam.totalNumUint(self._account) - def total_num_byte_slice(self) -> AccountParamExpr: + def total_num_byte_slice(self) -> MaybeValue: """Get the total number of byte array values allocated by the account in Global and Local States.""" return AccountParam.totalNumByteSlice(self._account) - def total_extra_app_pages(self) -> AccountParamExpr: + def total_extra_app_pages(self) -> MaybeValue: """Get the number of extra app code pages used by the account.""" return AccountParam.totalExtraAppPages(self._account) - def total_apps_created(self) -> AccountParamExpr: + def total_apps_created(self) -> MaybeValue: """Get the number of existing apps created by the account.""" return AccountParam.totalAppsCreated(self._account) - def total_apps_opted_in(self) -> AccountParamExpr: + def total_apps_opted_in(self) -> MaybeValue: """Get the number of apps the account is opted into.""" return AccountParam.totalAppsOptedIn(self._account) - def total_assets_created(self) -> AccountParamExpr: + def total_assets_created(self) -> MaybeValue: """Get the number of existing ASAs created by the account.""" return AccountParam.totalAssetsCreated(self._account) - def total_assets(self) -> AccountParamExpr: + def total_assets(self) -> MaybeValue: """Get the number of ASAs held by the account (including ASAs the account created).""" return AccountParam.totalAssets(self._account) - def total_boxes(self) -> AccountParamExpr: + def total_boxes(self) -> MaybeValue: """Get the number of existing boxes created by the account's app.""" return AccountParam.totalBoxes(self._account) - def total_box_bytes(self) -> AccountParamExpr: + def total_box_bytes(self) -> MaybeValue: """Get the total number of bytes used by the account's app's box keys and values.""" return AccountParam.totalBoxBytes(self._account) diff --git a/pyteal/ast/acct_test.py b/pyteal/ast/acct_test.py index 57b7d4765..87de4906e 100644 --- a/pyteal/ast/acct_test.py +++ b/pyteal/ast/acct_test.py @@ -62,15 +62,16 @@ def test_acct_param_fields_valid(method_name, field_name): def test_acct_param_string(): assert ( - str(pt.AccountParam.balance(pt.Int(1))) == "(AccountParam AcctBalance (Int 1))" + str(pt.AccountParam.balance(pt.Int(1))) + == "((acct_params_get AcctBalance (Int 1)) (StackStore slot#280) (StackStore slot#281))" ) assert ( str(pt.AccountParam.minBalance(pt.Int(1))) - == "(AccountParam AcctMinBalance (Int 1))" + == "((acct_params_get AcctMinBalance (Int 1)) (StackStore slot#282) (StackStore slot#283))" ) assert ( str(pt.AccountParam.authAddr(pt.Int(1))) - == "(AccountParam AcctAuthAddr (Int 1))" + == "((acct_params_get AcctAuthAddr (Int 1)) (StackStore slot#284) (StackStore slot#285))" ) From 1b21bf27567a281dddcf40e8df3bb690c14ba301 Mon Sep 17 00:00:00 2001 From: Jacob Daitzman Date: Thu, 13 Oct 2022 16:34:20 -0400 Subject: [PATCH 13/17] Remove AccountParam string test --- pyteal/ast/acct_test.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/pyteal/ast/acct_test.py b/pyteal/ast/acct_test.py index 87de4906e..79788bbe8 100644 --- a/pyteal/ast/acct_test.py +++ b/pyteal/ast/acct_test.py @@ -60,21 +60,6 @@ def test_acct_param_fields_valid(method_name, field_name): expr.__teal__(unsupported_options_version) -def test_acct_param_string(): - assert ( - str(pt.AccountParam.balance(pt.Int(1))) - == "((acct_params_get AcctBalance (Int 1)) (StackStore slot#280) (StackStore slot#281))" - ) - assert ( - str(pt.AccountParam.minBalance(pt.Int(1))) - == "((acct_params_get AcctMinBalance (Int 1)) (StackStore slot#282) (StackStore slot#283))" - ) - assert ( - str(pt.AccountParam.authAddr(pt.Int(1))) - == "((acct_params_get AcctAuthAddr (Int 1)) (StackStore slot#284) (StackStore slot#285))" - ) - - def test_AccountParamObject(): for account in ( pt.Int(7), From 445bf02a52345b059247d940b1e612acb94dc310 Mon Sep 17 00:00:00 2001 From: Jacob Daitzman Date: Tue, 18 Oct 2022 14:46:10 -0400 Subject: [PATCH 14/17] Change `compile_check` parameter to overwrite program check --- pyteal/ast/acct.py | 11 ++++++++--- pyteal/ast/maybe.py | 9 ++++++--- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/pyteal/ast/acct.py b/pyteal/ast/acct.py index 1d3180328..595649d1b 100644 --- a/pyteal/ast/acct.py +++ b/pyteal/ast/acct.py @@ -1,6 +1,6 @@ from enum import Enum from typing import Final, TYPE_CHECKING -from pyteal.errors import verifyFieldVersion +from pyteal.errors import verifyFieldVersion, verifyProgramVersion from pyteal.types import TealType, require_type from pyteal.ir import Op @@ -46,7 +46,12 @@ class AccountParam: def __makeAccountParamExpr(field: AccountParamField, acct: Expr) -> MaybeValue: require_type(acct, TealType.anytype) - def field_version_check(options: "CompileOptions"): + def field_and_program_version_check(options: "CompileOptions"): + verifyProgramVersion( + minVersion=Op.acct_params_get.min_version, + version=options.version, + msg=f"{Op.acct_params_get.value} unavailable", + ) verifyFieldVersion(field.arg_name, field.min_version, options.version) return MaybeValue( @@ -54,7 +59,7 @@ def field_version_check(options: "CompileOptions"): field.type_of(), immediate_args=[field.arg_name], args=[acct], - compile_check=field_version_check, + compile_check=field_and_program_version_check, ) @classmethod diff --git a/pyteal/ast/maybe.py b/pyteal/ast/maybe.py index 7e52cc19f..0c73516ce 100644 --- a/pyteal/ast/maybe.py +++ b/pyteal/ast/maybe.py @@ -22,7 +22,7 @@ def __init__( *, immediate_args: List[Union[int, str]] = None, args: List[Expr] = None, - compile_check: Callable[["CompileOptions"], None] = lambda _: None, + compile_check: Callable[["CompileOptions"], None] = None, ): """Create a new MaybeValue. @@ -31,15 +31,16 @@ def __init__( type: The type of the returned value. immediate_args (optional): Immediate arguments for the op. Defaults to None. args (optional): Stack arguments for the op. Defaults to None. + compile_check (optional): Callable compile check. Defaults to program version check. """ + # Default compile check if one is not given def local_version_check(options: "CompileOptions"): verifyProgramVersion( minVersion=op.min_version, version=options.version, msg=f"{op.value} unavailable", ) - compile_check(options) types = [type, TealType.uint64] super().__init__( @@ -47,7 +48,9 @@ def local_version_check(options: "CompileOptions"): types, immediate_args=immediate_args, args=args, - compile_check=local_version_check, + compile_check=( + local_version_check if compile_check is None else compile_check + ), ) def hasValue(self) -> ScratchLoad: From e4e506f616aa0a96272e0d0c11c0be3e654f1e21 Mon Sep 17 00:00:00 2001 From: Jacob Daitzman Date: Tue, 18 Oct 2022 17:41:18 -0400 Subject: [PATCH 15/17] Explicitly test version checks for program and field versions --- pyteal/ast/acct_test.py | 90 ++++++++++++++++++++++++++--------------- 1 file changed, 58 insertions(+), 32 deletions(-) diff --git a/pyteal/ast/acct_test.py b/pyteal/ast/acct_test.py index 79788bbe8..8259f8ac6 100644 --- a/pyteal/ast/acct_test.py +++ b/pyteal/ast/acct_test.py @@ -25,39 +25,65 @@ ("totalBoxBytes", "total_box_bytes"), ], ) -def test_acct_param_fields_valid(method_name, field_name): - arg = pt.Int(1) - account_param_method = getattr(pt.AccountParam, method_name) - expr = account_param_method(arg) - assert expr.type_of() == pt.TealType.none - - account_param_field = AccountParamField[field_name] - assert expr.value().type_of() == account_param_field.type_of() - - expected = pt.TealSimpleBlock( - [ - pt.TealOp(arg, pt.Op.int, 1), - pt.TealOp(expr, pt.Op.acct_params_get, account_param_field.arg_name), - pt.TealOp(None, pt.Op.store, expr.slotOk), - pt.TealOp(None, pt.Op.store, expr.slotValue), - ] - ) - - supported_options_version = pt.CompileOptions( - version=account_param_field.min_version - ) - actual, _ = expr.__teal__(supported_options_version) - actual.addIncoming() - actual = pt.TealBlock.NormalizeBlocks(actual) - - with pt.TealComponent.Context.ignoreExprEquality(): - assert actual == expected - - with pytest.raises(pt.TealInputError): - unsupported_options_version = pt.CompileOptions( - version=account_param_field.min_version - 1 +class TestAcctParam: + @staticmethod + def test_acct_param_fields_valid(method_name, field_name): + arg = pt.Int(1) + account_param_method = getattr(pt.AccountParam, method_name) + expr = account_param_method(arg) + assert expr.type_of() == pt.TealType.none + + account_param_field = AccountParamField[field_name] + assert expr.value().type_of() == account_param_field.type_of() + + expected = pt.TealSimpleBlock( + [ + pt.TealOp(arg, pt.Op.int, 1), + pt.TealOp(expr, pt.Op.acct_params_get, account_param_field.arg_name), + pt.TealOp(None, pt.Op.store, expr.slotOk), + pt.TealOp(None, pt.Op.store, expr.slotValue), + ] + ) + + supported_options_version = pt.CompileOptions( + version=account_param_field.min_version + ) + actual, _ = expr.__teal__(supported_options_version) + actual.addIncoming() + actual = pt.TealBlock.NormalizeBlocks(actual) + + with pt.TealComponent.Context.ignoreExprEquality(): + assert actual == expected + + @staticmethod + def test_acct_param_version_checks(method_name, field_name): + arg = pt.Int(1) + account_param_method = getattr(pt.AccountParam, method_name) + expr = account_param_method(arg) + + account_param_field = AccountParamField[field_name] + + def test_unsupported_version(version: int, match: str = None): + with pytest.raises(pt.TealInputError, match=match): + unsupported_options_version = pt.CompileOptions(version=version) + expr.__teal__(unsupported_options_version) + + # Test program and field version checks + program_unsupported_version = pt.ir.Op.acct_params_get.min_version - 1 + program_error_match = "unavailable" + test_unsupported_version(program_unsupported_version, program_error_match) + + field_unsupported_version = account_param_field.min_version - 1 + + # Since program version dominates, we conditionally check field error message or program error message + # depending on whether the unsupported field version is less than or equal to the program unsupported + # version. + field_error_match = ( + "Program version too low to use field" + if field_unsupported_version > program_unsupported_version + else program_error_match ) - expr.__teal__(unsupported_options_version) + test_unsupported_version(field_unsupported_version, field_error_match) def test_AccountParamObject(): From c68baac32490e1b2d132c6fc319ff753f419403f Mon Sep 17 00:00:00 2001 From: Jacob Daitzman Date: Tue, 18 Oct 2022 17:52:28 -0400 Subject: [PATCH 16/17] Include note about compile check overwrite in documentation --- pyteal/ast/maybe.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyteal/ast/maybe.py b/pyteal/ast/maybe.py index 0c73516ce..9ae1f0245 100644 --- a/pyteal/ast/maybe.py +++ b/pyteal/ast/maybe.py @@ -32,6 +32,7 @@ def __init__( immediate_args (optional): Immediate arguments for the op. Defaults to None. args (optional): Stack arguments for the op. Defaults to None. compile_check (optional): Callable compile check. Defaults to program version check. + This parameter overwrites the default program version check. """ # Default compile check if one is not given From 3d46083dbfe43a5a15d5477231e769d293fb1b3a Mon Sep 17 00:00:00 2001 From: Jacob Daitzman Date: Tue, 25 Oct 2022 20:08:41 -0400 Subject: [PATCH 17/17] Document version requirement in new `AccountParam` methods --- pyteal/ast/acct.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/pyteal/ast/acct.py b/pyteal/ast/acct.py index 595649d1b..7bc19ec78 100644 --- a/pyteal/ast/acct.py +++ b/pyteal/ast/acct.py @@ -96,6 +96,8 @@ def authAddr(cls, acct: Expr) -> MaybeValue: def totalNumUint(cls, acct: Expr) -> MaybeValue: """Get the total number of uint64 values allocated by the account in Global and Local States. + Requires program version 8 or higher. + Args: acct: An index into Txn.accounts that corresponds to the application to check or an address available at runtime. May evaluate to uint64 or an address. @@ -106,6 +108,8 @@ def totalNumUint(cls, acct: Expr) -> MaybeValue: def totalNumByteSlice(cls, acct: Expr) -> MaybeValue: """Get the total number of byte array values allocated by the account in Global and Local States. + Requires program version 8 or higher. + Args: acct: An index into Txn.accounts that corresponds to the application to check or an address available at runtime. May evaluate to uint64 or an address. @@ -116,6 +120,8 @@ def totalNumByteSlice(cls, acct: Expr) -> MaybeValue: def totalExtraAppPages(cls, acct: Expr) -> MaybeValue: """Get the number of extra app code pages used by the account. + Requires program version 8 or higher. + Args: acct: An index into Txn.accounts that corresponds to the application to check or an address available at runtime. May evaluate to uint64 or an address. @@ -126,6 +132,8 @@ def totalExtraAppPages(cls, acct: Expr) -> MaybeValue: def totalAppsCreated(cls, acct: Expr) -> MaybeValue: """Get the number of existing apps created by the account. + Requires program version 8 or higher. + Args: acct: An index into Txn.accounts that corresponds to the application to check or an address available at runtime. May evaluate to uint64 or an address. @@ -136,6 +144,8 @@ def totalAppsCreated(cls, acct: Expr) -> MaybeValue: def totalAppsOptedIn(cls, acct: Expr) -> MaybeValue: """Get the number of apps the account is opted into. + Requires program version 8 or higher. + Args: acct: An index into Txn.accounts that corresponds to the application to check or an address available at runtime. May evaluate to uint64 or an address. @@ -146,6 +156,8 @@ def totalAppsOptedIn(cls, acct: Expr) -> MaybeValue: def totalAssetsCreated(cls, acct: Expr) -> MaybeValue: """Get the number of existing ASAs created by the account. + Requires program version 8 or higher. + Args: acct: An index into Txn.accounts that corresponds to the application to check or an address available at runtime. May evaluate to uint64 or an address. @@ -156,6 +168,8 @@ def totalAssetsCreated(cls, acct: Expr) -> MaybeValue: def totalAssets(cls, acct: Expr) -> MaybeValue: """Get the number of ASAs held by the account (including ASAs the account created). + Requires program version 8 or higher. + Args: acct: An index into Txn.accounts that corresponds to the application to check or an address available at runtime. May evaluate to uint64 or an address. @@ -166,6 +180,8 @@ def totalAssets(cls, acct: Expr) -> MaybeValue: def totalBoxes(cls, acct: Expr) -> MaybeValue: """Get the number of existing boxes created by the account's app. + Requires program version 8 or higher. + Args: acct: An index into Txn.accounts that corresponds to the application to check or an address available at runtime. May evaluate to uint64 or an address. @@ -176,6 +192,8 @@ def totalBoxes(cls, acct: Expr) -> MaybeValue: def totalBoxBytes(cls, acct: Expr) -> MaybeValue: """Get the total number of bytes used by the account's app's box keys and values. + Requires program version 8 or higher. + Args: acct: An index into Txn.accounts that corresponds to the application to check or an address available at runtime. May evaluate to uint64 or an address.