Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support inner transactions #115

Merged
merged 12 commits into from
Sep 16, 2021
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,10 @@ Active venv:
Pip install PyTeal in editable state
* `pip install -e .`

Install dependencies :
Install dependencies:
* `pip3 install -r requirements.txt`

Type checking using mypy
Type checking using mypy:
* `mypy pyteal`

Run tests:
Expand Down
136 changes: 79 additions & 57 deletions docs/accessing_transaction_field.rst

Large diffs are not rendered by default.

9 changes: 7 additions & 2 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,14 @@ PyTeal Package
.. data:: Txn
:annotation: = <pyteal.TxnObject object>

The current transaction being evaluated.
The current transaction being evaluated. This is an instance of :any:`TxnObject`.

.. data:: Gtxn
:annotation: = <pyteal.TxnGroup object>

The current group of transactions being evaluated.
The current group of transactions being evaluated. This is an instance of :any:`TxnGroup`.

.. data:: InnerTxn
:annotation: = <pyteal.TxnObject object>

The most recently submitted inner transaction. This is an instance of :any:`TxnObject`.
5 changes: 5 additions & 0 deletions pyteal/ast/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
from .app import App, AppField, OnComplete, AppParam
from .asset import AssetHolding, AssetParam

# inner txns
from .itxn import InnerTxnBuilder, InnerTxn

# meta
from .array import Array
from .tmpl import Tmpl
Expand Down Expand Up @@ -139,6 +142,8 @@
"AppParam",
"AssetHolding",
"AssetParam",
"InnerTxnBuilder",
"InnerTxn",
"Array",
"Tmpl",
"Nonce",
Expand Down
2 changes: 1 addition & 1 deletion pyteal/ast/binaryexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def __init__(
) -> None:
super().__init__()
if type(inputType) is tuple:
leftType, rightType = cast(Tuple[TealType, TealType], inputType)
leftType, rightType = inputType
else:
leftType = cast(TealType, inputType)
rightType = leftType
Expand Down
6 changes: 3 additions & 3 deletions pyteal/ast/gaid.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def __init__(self, txnIndex: Union[int, Expr]) -> None:
must be less than the index of the current transaction.
"""
super().__init__()
if type(txnIndex) == int:
if type(txnIndex) is int:
if txnIndex < 0 or txnIndex >= MAX_GROUP_SIZE:
raise TealInputError(
"Invalid transaction index {}, shoud be in [0, {})".format(
Expand All @@ -47,8 +47,8 @@ def __teal__(self, options: "CompileOptions"):
"TEAL version too low to use Gaid expression",
)

if type(self.txnIndex) == int:
op = TealOp(self, Op.gaid, cast(int, self.txnIndex))
if type(self.txnIndex) is int:
op = TealOp(self, Op.gaid, self.txnIndex)
return TealBlock.FromOp(options, op)

op = TealOp(self, Op.gaids)
Expand Down
8 changes: 3 additions & 5 deletions pyteal/ast/gload.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def __init__(self, txnIndex: Union[int, Expr], slotId: int) -> None:
in the range [0-256).
"""
super().__init__()
if type(txnIndex) == int:
if type(txnIndex) is int:
if txnIndex < 0 or txnIndex >= MAX_GROUP_SIZE:
raise TealInputError(
"Invalid transaction index {}, shoud be in [0, {})".format(
Expand All @@ -56,10 +56,8 @@ def __teal__(self, options: "CompileOptions"):
"TEAL version too low to use Gload expression",
)

if type(self.txnIndex) == int:
op = TealOp(
self, Op.gload, cast(int, self.txnIndex), cast(int, self.slotId)
)
if type(self.txnIndex) is int:
op = TealOp(self, Op.gload, self.txnIndex, self.slotId)
return TealBlock.FromOp(options, op)

op = TealOp(self, Op.gloads, self.slotId)
Expand Down
20 changes: 19 additions & 1 deletion pyteal/ast/global_.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ class GlobalField(Enum):
latest_timestamp = (7, "LatestTimestamp", TealType.uint64, 2)
current_app_id = (8, "CurrentApplicationID", TealType.uint64, 2)
creator_address = (9, "CreatorAddress", TealType.bytes, 3)
current_app_address = (10, "CurrentApplicationAddress", TealType.bytes, 5)
group_id = (11, "GroupID", TealType.bytes, 5)

def __init__(self, id: int, name: str, type: TealType, min_version: int) -> None:
self.id = id
Expand Down Expand Up @@ -110,8 +112,24 @@ def current_application_id(cls) -> "Global":
def creator_address(cls) -> "Global":
"""Address of the creator of the current application.

Fails if no such application is executing. Requires TEAL version 3 or higher."""
Fails during Signature mode. Requires TEAL version 3 or higher."""
return cls(GlobalField.creator_address)

@classmethod
def current_application_address(cls) -> "Global":
"""Get the address of that the current application controls.

Fails during Signature mode. Requires TEAL version 5 or higher."""
return cls(GlobalField.current_app_address)

@classmethod
def group_id(cls) -> "Global":
"""Get the ID of the current transaction group.

If the current transaction is not part of a group, this will return 32 zero bytes.

Requires TEAL version 5 or higher."""
return cls(GlobalField.group_id)


Global.__module__ = "pyteal"
29 changes: 29 additions & 0 deletions pyteal/ast/global_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

teal2Options = CompileOptions(version=2)
teal3Options = CompileOptions(version=3)
teal5Options = CompileOptions(version=5)


def test_global_min_txn_fee():
Expand Down Expand Up @@ -120,3 +121,31 @@ def test_global_creator_address():

with pytest.raises(TealInputError):
expr.__teal__(teal2Options)


def test_global_current_application_address():
expr = Global.current_application_address()
assert expr.type_of() == TealType.bytes

expected = TealSimpleBlock([TealOp(expr, Op.global_, "CurrentApplicationAddress")])

actual, _ = expr.__teal__(teal5Options)

assert actual == expected

with pytest.raises(TealInputError):
expr.__teal__(teal3Options)


def test_global_group_id():
expr = Global.group_id()
assert expr.type_of() == TealType.bytes

expected = TealSimpleBlock([TealOp(expr, Op.global_, "GroupID")])

actual, _ = expr.__teal__(teal5Options)

assert actual == expected

with pytest.raises(TealInputError):
expr.__teal__(teal3Options)
62 changes: 41 additions & 21 deletions pyteal/ast/gtxn.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,17 @@ class GtxnExpr(TxnExpr):
"""An expression that accesses a transaction field from a transaction in the current group."""

def __init__(self, txnIndex: Union[int, Expr], field: TxnField) -> None:
super().__init__(field)
super().__init__(Op.gtxn, "Gtxn", field)
self.txnIndex = txnIndex

def __str__(self):
return "(Gtxn {} {})".format(self.txnIndex, self.field.arg_name)
return "({} {} {})".format(self.name, self.txnIndex, self.field.arg_name)

def __teal__(self, options: "CompileOptions"):
verifyFieldVersion(self.field.arg_name, self.field.min_version, options.version)

if type(self.txnIndex) == int:
op = TealOp(self, Op.gtxn, cast(int, self.txnIndex), self.field.arg_name)
if type(self.txnIndex) is int:
op = TealOp(self, Op.gtxn, self.txnIndex, self.field.arg_name)
return TealBlock.FromOp(options, op)

verifyTealVersion(
Expand All @@ -45,34 +45,54 @@ def __teal__(self, options: "CompileOptions"):
class GtxnaExpr(TxnaExpr):
"""An expression that accesses a transaction array field from a transaction in the current group."""

def __init__(self, txnIndex: Union[int, Expr], field: TxnField, index: int) -> None:
super().__init__(field, index)
def __init__(
self, txnIndex: Union[int, Expr], field: TxnField, index: Union[int, Expr]
) -> None:
super().__init__(Op.gtxna, Op.gtxnas, "Gtxna", field, index)
self.txnIndex = txnIndex

def __str__(self):
return "(Gtxna {} {} {})".format(self.txnIndex, self.field.arg_name, self.index)
return "({} {} {} {})".format(
self.name, self.txnIndex, self.field.arg_name, self.index
)

def __teal__(self, options: "CompileOptions"):
verifyFieldVersion(self.field.arg_name, self.field.min_version, options.version)

if type(self.txnIndex) == int:
op = TealOp(
self,
Op.gtxna,
cast(int, self.txnIndex),
self.field.arg_name,
self.index,
)
return TealBlock.FromOp(options, op)
if type(self.txnIndex) is int:
if type(self.index) is int:
opToUse = Op.gtxna
else:
opToUse = Op.gtxnas
else:
if type(self.index) is int:
opToUse = Op.gtxnsa
else:
opToUse = Op.gtxnsas

verifyTealVersion(
Op.gtxnsa.min_version,
opToUse.min_version,
options.version,
"TEAL version too low to index Gtxn with dynamic values",
"TEAL version too low to use op {}".format(opToUse),
)

op = TealOp(self, Op.gtxnsa, self.field.arg_name, self.index)
return TealBlock.FromOp(options, op, cast(Expr, self.txnIndex))
if type(self.txnIndex) is int:
if type(self.index) is int:
op = TealOp(
self, opToUse, self.txnIndex, self.field.arg_name, self.index
)
return TealBlock.FromOp(options, op)
op = TealOp(self, opToUse, self.txnIndex, self.field.arg_name)
return TealBlock.FromOp(options, op, cast(Expr, self.index))

if type(self.index) is int:
op = TealOp(self, opToUse, self.field.arg_name, self.index)
return TealBlock.FromOp(options, op, cast(Expr, self.txnIndex))

op = TealOp(self, opToUse, self.field.arg_name)
return TealBlock.FromOp(
options, op, cast(Expr, self.txnIndex), cast(Expr, self.index)
)


GtxnaExpr.__module__ = "pyteal"
Expand All @@ -82,7 +102,7 @@ class TxnGroup:
"""Represents a group of transactions."""

def __getitem__(self, txnIndex: Union[int, Expr]) -> TxnObject:
if type(txnIndex) == int:
if type(txnIndex) is int:
if txnIndex < 0 or txnIndex >= MAX_GROUP_SIZE:
raise TealInputError(
"Invalid Gtxn index {}, shoud be in [0, {})".format(
Expand Down
Loading