Skip to content

Commit 2a68233

Browse files
Add opcode support for itxnas and gitxnas (#193)
* Add opcode support for itxnas and gitxnas * Update stale reference to inner transaction limit * Fix allowed types for GitxnaExpr txnIndex * Remove obsolete logic for handling GitxnaExpr.teal construction * Remove unnecessary cast and fix gitxna runtime type checking * Move type validation to constructors for gtxn and gitxn variants * Add missed tests from prior commit * Fix duplicate test case * Move index validation from subclasses to TxnaExpr * Inline validation functions per PR feedback * Remove unused imports * Refactor to isinstance tupled check * Remove TEAL v1 min version test per PR feedback * Fix constructor type checking for GtxnExpr * Refactor to remove duplicate type check function
1 parent 74c34b3 commit 2a68233

File tree

7 files changed

+175
-49
lines changed

7 files changed

+175
-49
lines changed

pyteal/ast/gitxn.py

Lines changed: 36 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1-
from typing import TYPE_CHECKING, cast
1+
from typing import TYPE_CHECKING, cast, Union
22

33
from pyteal.config import MAX_GROUP_SIZE
44

55
from ..errors import TealInputError, verifyFieldVersion, verifyTealVersion
66
from ..ir import TealOp, Op, TealBlock
7+
from .expr import Expr
78
from .txn import TxnExpr, TxnField, TxnObject, TxnaExpr
9+
from ..types import require_type, TealType
810

911
if TYPE_CHECKING:
1012
from ..compiler import CompileOptions
@@ -15,6 +17,15 @@ class GitxnExpr(TxnExpr):
1517

1618
def __init__(self, txnIndex: int, field: TxnField) -> None:
1719
super().__init__(Op.gitxn, "Gitxn", field)
20+
21+
# Currently we do not have gitxns. Only gitxn with immediate transaction index supported.
22+
if type(txnIndex) is not int:
23+
raise TealInputError(
24+
"Invalid gitxn syntax with immediate transaction field {} and transaction index {}".format(
25+
field, txnIndex
26+
)
27+
)
28+
1829
self.txnIndex = txnIndex
1930

2031
def __str__(self):
@@ -23,14 +34,6 @@ def __str__(self):
2334
def __teal__(self, options: "CompileOptions"):
2435
verifyFieldVersion(self.field.arg_name, self.field.min_version, options.version)
2536

26-
# currently we do not have gitxns, only gitxn with immediate transaction index supported
27-
if type(self.txnIndex) is not int:
28-
raise TealInputError(
29-
"Invalid gitxn syntax with immediate transaction field {} and transaction index {}".format(
30-
self.field, self.txnIndex
31-
)
32-
)
33-
3437
verifyTealVersion(
3538
Op.gitxn.min_version,
3639
options.version,
@@ -46,8 +49,14 @@ def __teal__(self, options: "CompileOptions"):
4649
class GitxnaExpr(TxnaExpr):
4750
"""An expression that accesses an inner transaction array field from an inner transaction in the last inner group."""
4851

49-
def __init__(self, txnIndex: int, field: TxnField, index: int) -> None:
50-
super().__init__(Op.gitxna, None, "Gitxna", field, index)
52+
def __init__(self, txnIndex: int, field: TxnField, index: Union[int, Expr]) -> None:
53+
super().__init__(Op.gitxna, Op.gitxnas, "Gitxna", field, index)
54+
55+
if type(txnIndex) is not int:
56+
raise TealInputError(
57+
f"Invalid txnIndex type: Expected int, but received {txnIndex}."
58+
)
59+
5160
self.txnIndex = txnIndex
5261

5362
def __str__(self):
@@ -57,18 +66,23 @@ def __str__(self):
5766

5867
def __teal__(self, options: "CompileOptions"):
5968
verifyFieldVersion(self.field.arg_name, self.field.min_version, options.version)
60-
if type(self.txnIndex) is not int or type(self.index) is not int:
61-
raise TealInputError(
62-
"Invalid gitxna syntax with immediate transaction index {}, transaction field {}, array index {}".format(
63-
self.txnIndex, self.field, self.index
64-
)
65-
)
69+
70+
if type(self.index) is int:
71+
opToUse = Op.gitxna
72+
else:
73+
opToUse = Op.gitxnas
6674

6775
verifyTealVersion(
68-
Op.gitxna.min_version, options.version, "TEAL version too low to use gitxna"
76+
opToUse.min_version,
77+
options.version,
78+
"TEAL version too low to use op {}".format(opToUse),
6979
)
70-
op = TealOp(self, Op.gitxna, self.txnIndex, self.field.arg_name, self.index)
71-
return TealBlock.FromOp(options, op)
80+
81+
if type(self.index) is int:
82+
op = TealOp(self, opToUse, self.txnIndex, self.field.arg_name, self.index)
83+
return TealBlock.FromOp(options, op)
84+
op = TealOp(self, opToUse, self.txnIndex, self.field.arg_name)
85+
return TealBlock.FromOp(options, op, cast(Expr, self.index))
7286

7387

7488
GitxnaExpr.__module__ = "pyteal"
@@ -85,14 +99,14 @@ def __getitem__(self, txnIndex: int) -> TxnObject:
8599

86100
if txnIndex < 0 or txnIndex >= MAX_GROUP_SIZE:
87101
raise TealInputError(
88-
"Invalid Gtxn index {}, shoud be in [0, {})".format(
102+
"Invalid Gtxn index {}, should be in [0, {})".format(
89103
txnIndex, MAX_GROUP_SIZE
90104
)
91105
)
92106

93107
return TxnObject(
94108
lambda field: GitxnExpr(txnIndex, field),
95-
lambda field, index: GitxnaExpr(txnIndex, field, cast(int, index)),
109+
lambda field, index: GitxnaExpr(txnIndex, field, index),
96110
)
97111

98112

pyteal/ast/gitxn_test.py

Lines changed: 63 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,75 @@
77

88

99
def test_gitxn_invalid():
10-
with pytest.raises(TealInputError):
11-
GitxnExpr(0, TxnField.sender).__teal__(teal5Options)
10+
for ctor, e in [
11+
(
12+
lambda: Gitxn[MAX_GROUP_SIZE],
13+
TealInputError,
14+
),
15+
(
16+
lambda: Gitxn[-1],
17+
TealInputError,
18+
),
19+
]:
20+
with pytest.raises(e):
21+
ctor()
1222

13-
with pytest.raises(TealInputError):
14-
Gitxn[MAX_GROUP_SIZE].sender()
1523

16-
with pytest.raises(TealInputError):
17-
Gitxn[-1].asset_sender()
24+
def test_gitxn_valid():
25+
for i in range(MAX_GROUP_SIZE):
26+
Gitxn[i].sender()
1827

19-
with pytest.raises(TealInputError):
20-
Gitxn[Bytes("first")].sender()
2128

29+
def test_gitxn_expr_invalid():
30+
for f, e in [
31+
(
32+
lambda: GitxnExpr(Int(1), TxnField.sender),
33+
TealInputError,
34+
),
35+
(
36+
lambda: GitxnExpr(1, TxnField.sender).__teal__(teal5Options),
37+
TealInputError,
38+
),
39+
]:
40+
with pytest.raises(e):
41+
f()
2242

23-
def test_gitxn_valid():
24-
GitxnaExpr(0, TxnField.application_args, 1).__teal__(teal6Options)
2543

26-
for i in range(MAX_GROUP_SIZE):
27-
Gitxn[i].sender()
44+
def test_gitxn_expr_valid():
45+
GitxnExpr(1, TxnField.sender).__teal__(teal6Options)
46+
47+
48+
def test_gitxna_expr_invalid():
49+
for f, e in [
50+
(
51+
lambda: GitxnaExpr("Invalid_type", TxnField.application_args, 1),
52+
TealInputError,
53+
),
54+
(
55+
lambda: GitxnaExpr(1, TxnField.application_args, "Invalid_type"),
56+
TealInputError,
57+
),
58+
(
59+
lambda: GitxnaExpr(0, TxnField.application_args, Assert(Int(1))),
60+
TealTypeError,
61+
),
62+
(
63+
lambda: GitxnaExpr(0, TxnField.application_args, 0).__teal__(teal5Options),
64+
TealInputError,
65+
),
66+
]:
67+
with pytest.raises(e):
68+
f()
69+
70+
71+
def test_gitxna_valid():
72+
[
73+
e.__teal__(teal6Options)
74+
for e in [
75+
GitxnaExpr(0, TxnField.application_args, 1),
76+
GitxnaExpr(0, TxnField.application_args, Int(1)),
77+
]
78+
]
2879

2980

3081
# txn_test.py performs additional testing

pyteal/ast/gtxn.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,21 @@
1111
from ..compiler import CompileOptions
1212

1313

14+
def validate_txn_index_or_throw(txnIndex: Union[int, Expr]):
15+
if not isinstance(txnIndex, (int, Expr)):
16+
raise TealInputError(
17+
f"Invalid txnIndex type: Expected int or Expr, but received {txnIndex}"
18+
)
19+
if isinstance(txnIndex, Expr):
20+
require_type(txnIndex, TealType.uint64)
21+
22+
1423
class GtxnExpr(TxnExpr):
1524
"""An expression that accesses a transaction field from a transaction in the current group."""
1625

1726
def __init__(self, txnIndex: Union[int, Expr], field: TxnField) -> None:
1827
super().__init__(Op.gtxn, "Gtxn", field)
28+
validate_txn_index_or_throw(txnIndex)
1929
self.txnIndex = txnIndex
2030

2131
def __str__(self):
@@ -51,6 +61,7 @@ def __init__(
5161
self, txnIndex: Union[int, Expr], field: TxnField, index: Union[int, Expr]
5262
) -> None:
5363
super().__init__(Op.gtxna, Op.gtxnas, "Gtxna", field, index)
64+
validate_txn_index_or_throw(txnIndex)
5465
self.txnIndex = txnIndex
5566

5667
def __str__(self):

pyteal/ast/gtxn_test.py

Lines changed: 49 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,57 @@
22

33
from .. import *
44

5+
teal6Options = CompileOptions(version=6)
56

6-
def test_gtxn_invalid():
7-
with pytest.raises(TealInputError):
8-
Gtxn[-1].fee()
9-
10-
with pytest.raises(TealInputError):
11-
Gtxn[MAX_GROUP_SIZE + 1].sender()
127

13-
with pytest.raises(TealTypeError):
14-
Gtxn[Pop(Int(0))].sender()
15-
16-
with pytest.raises(TealTypeError):
17-
Gtxn[Bytes("index")].sender()
8+
def test_gtxn_invalid():
9+
for f, e in [
10+
(lambda: Gtxn[-1], TealInputError),
11+
(lambda: Gtxn[MAX_GROUP_SIZE + 1], TealInputError),
12+
(lambda: Gtxn[Pop(Int(0))], TealTypeError),
13+
(lambda: Gtxn[Bytes("index")], TealTypeError),
14+
]:
15+
with pytest.raises(e):
16+
f()
17+
18+
19+
def test_gtxn_expr_invalid():
20+
for f, e in [
21+
(lambda: GtxnExpr(Assert(Int(1)), TxnField.sender), TealTypeError),
22+
]:
23+
with pytest.raises(e):
24+
f()
25+
26+
27+
def test_gtxn_expr_valid():
28+
[
29+
e.__teal__(teal6Options)
30+
for e in [
31+
GtxnExpr(1, TxnField.sender),
32+
GtxnExpr(Int(1), TxnField.sender),
33+
]
34+
]
35+
36+
37+
def test_gtxna_expr_invalid():
38+
for f, e in [
39+
(lambda: GtxnaExpr("Invalid_type", TxnField.assets, 1), TealInputError),
40+
(lambda: GtxnaExpr(1, TxnField.assets, "Invalid_type"), TealInputError),
41+
(lambda: GtxnaExpr(Assert(Int(1)), TxnField.assets, 1), TealTypeError),
42+
(lambda: GtxnaExpr(1, TxnField.assets, Assert(Int(1))), TealTypeError),
43+
]:
44+
with pytest.raises(e):
45+
f()
46+
47+
48+
def test_gtxna_expr_valid():
49+
[
50+
e.__teal__(teal6Options)
51+
for e in [
52+
GtxnaExpr(1, TxnField.assets, 1),
53+
GtxnaExpr(Int(1), TxnField.assets, Int(1)),
54+
]
55+
]
1856

1957

2058
# txn_test.py performs additional testing

pyteal/ast/itxn.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ def Submit(cls) -> Expr:
120120
:any:`InnerTxnBuilder.Begin` and :any:`InnerTxnBuilder.SetField` must be called before
121121
submitting an inner transaction.
122122
123-
This will fail fail if 16 inner transactions have already been executed, or if the
123+
This will fail if 256 inner transactions have already been executed, or if the
124124
inner transaction itself fails. Upon failure, the current program will immediately exit and
125125
fail as well.
126126
@@ -204,7 +204,8 @@ def SetFields(cls, fields: Dict[TxnField, Union[Expr, List[Expr]]]) -> Expr:
204204
InnerTxnBuilder.__module__ = "pyteal"
205205

206206
InnerTxn: TxnObject = TxnObject(
207-
TxnExprBuilder(Op.itxn, "InnerTxn"), TxnaExprBuilder(Op.itxna, None, "InnerTxna")
207+
TxnExprBuilder(Op.itxn, "InnerTxn"),
208+
TxnaExprBuilder(Op.itxna, Op.itxnas, "InnerTxna"),
208209
)
209210

210211
InnerTxn.__module__ = "pyteal"

pyteal/ast/txn.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,15 @@ def type_of(self):
161161
class TxnaExpr(LeafExpr):
162162
"""An expression that accesses a transaction array field from the current transaction."""
163163

164+
@staticmethod
165+
def __validate_index_or_throw(index: Union[int, Expr]):
166+
if not isinstance(index, (int, Expr)):
167+
raise TealInputError(
168+
f"Invalid index type: Expected int or Expr, but received {index}."
169+
)
170+
if isinstance(index, Expr):
171+
require_type(index, TealType.uint64)
172+
164173
def __init__(
165174
self,
166175
staticOp: Op,
@@ -172,6 +181,8 @@ def __init__(
172181
super().__init__()
173182
if not field.is_array:
174183
raise TealInputError("Unexpected non-array field: {}".format(field))
184+
self.__validate_index_or_throw(index)
185+
175186
self.staticOp = staticOp
176187
self.dynamicOp = dynamicOp
177188
self.name = name

pyteal/ast/txn_test.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,9 +96,9 @@ def test_txn_fields():
9696
[],
9797
[TealOp(dynamicGtxnArg, Op.int, 0)],
9898
),
99-
(InnerTxn, Op.itxn, Op.itxna, None, [], []),
99+
(InnerTxn, Op.itxn, Op.itxna, Op.itxnas, [], []),
100100
*[
101-
(Gitxn[i], Op.gitxn, Op.gitxna, None, [i], [])
101+
(Gitxn[i], Op.gitxn, Op.gitxna, Op.gitxnas, [i], [])
102102
for i in range(MAX_GROUP_SIZE)
103103
],
104104
]

0 commit comments

Comments
 (0)