PyTeal provides several control flow expressions to create programs.
Note
The Approve
and Reject
expressions are only available in TEAL version 4 or higher.
Prior to this, Return(Int(1))
is equivalent to Approve()
and Return(Int(0))
is equivalent to Reject()
.
The :any:`Approve` and :any:`Reject` expressions cause the program to immediately exit. If Approve
is used, then the execution is marked as successful, and if Reject
is used, then the execution
is marked as unsuccessful.
These expressions also work inside :ref:`subroutines <subroutine_expr>`.
The :any:`Seq` expression can be used to create a sequence of multiple expressions. It's arguments are the expressions to include in the sequence, either as a variable number of arguments, or as a single list
For example:
Seq(
App.globalPut(Bytes("creator"), Txn.sender()),
Return(Int(1))
)
A Seq
expression will take on the value of its last expression. Additionally, all
expressions in a Seq
expression, except the last one, must not return anything (e.g.
evaluate to :any:`TealType.none`). This restriction is in place because intermediate values must not
add things to the TEAL stack. As a result, the following is an invalid sequence:
Seq(
Txn.sender(),
Return(Int(1))
)
If you must include an operation that returns a value in the earlier part of a sequence, you can wrap the value in a :any:`Pop` expression to discard it. For example,
Seq(
Pop(Txn.sender()),
Return(Int(1))
)
In an :any:`If` expression,
If(test-expr, then-expr, else-expr)
the test-expr
is always evaluated and needs to be typed TealType.uint64
.
If it results in a value greater than 0, then the then-expr
is evaluated.
Otherwise, else-expr
is evaluated. Note that then-expr
and else-expr
must
evaluate to the same type (e.g. both TealType.uint64
).
You may also invoke an :any:`If` expression without an else-expr
:
If(test-expr, then-expr)
In this case, then-expr
must be typed TealType.none
.
There is also an alternate way to write an :any:`If` expression that makes reading complex statements easier to read.
If(test-expr)
.Then(then-expr)
.ElseIf(test-expr)
.Then(then-expr)
.Else(else-expr)
The :any:`Assert` expression can be used to ensure that conditions are met before continuing the
program. The syntax for Assert
is:
Assert(test-expr)
If test-expr
is always evaluated and must be typed TealType.uint64
. If
test-expr
results in a value greater than 0, the program continues. Otherwise, the program
immediately exits and indicates that it encountered an error.
Example:
Assert(Txn.type_enum() == TxnType.Payment)
The above example will cause the program to immediately fail with an error if the transaction type is not a payment.
A :any:`Cond` expression chains a series of tests to select a result expression. The syntax of Cond is:
Cond([test-expr-1, body-1],
[test-expr-2, body-2],
. . . )
Each test-expr
is evaluated in order. If it produces 0, the paired body
is ignored, and evaluation proceeds to the next test-expr
.
As soon as a test-expr
produces a true value (> 0),
its body
is evaluated to produce the value for this Cond
expression.
If none of test-expr
s evaluates to a true value, the Cond
expression will
be evaluated to err
, a TEAL opcode that causes the runtime panic.
In a Cond
expression, each test-expr
needs to be typed TealType.uint64
.
A body
could be typed either TealType.uint64
or TealType.bytes
. However, all
body
s must have the same data type. Otherwise, a TealTypeError
is triggered.
Example:
Cond([Global.group_size() == Int(5), bid],
[Global.group_size() == Int(4), redeem],
[Global.group_size() == Int(1), wrapup])
This PyTeal code branches on the size of the atomic transaction group.
Note
This expression is only available in TEAL version 4 or higher.
The :any:`While` expression can be used to create simple loops in PyTeal. The syntax of While
is:
While(loop-condition).Do(loop-body)
The loop-condition
expression must evaluate to TealType.uint64
, and the loop-body
expression must evaluate to TealType.none
.
The loop-body
expression will continue to execute as long as loop-condition
produces
a true value (> 0).
For example, the following code uses :any:`ScratchVar` to iterate through every transaction in the current group and sum up all of their fees.
totalFees = ScratchVar(TealType.uint64)
i = ScratchVar(TealType.uint64)
Seq([
i.store(Int(0)),
totalFees.store(Int(0)),
While(i.load() < Global.group_size()).Do(Seq([
totalFees.store(totalFees.load() + Gtxn[i.load()].fee()),
i.store(i.load() + Int(1))
]))
])
Note
This expression is only available in TEAL version 4 or higher.
Similar to While
, the :any:`For` expression can also be used to create loops in PyTeal. The
syntax of For
is:
For(loop-start, loop-condition, loop-step).Do(loop-body)
The loop-start
, loop-step
, and loop-body
expressions must evaluate to
TealType.none
, and the the loop-condition
expression must evaluate to TealType.uint64
.
When a For
expression is executed, loop-start
is executed first. Then the
expressions loop-condition
, loop-body
, and loop-step
will continue to
execute in order as long as loop-condition
produces a true value (> 0).
For example, the following code uses :any:`ScratchVar` to iterate through every transaction in the
current group and sum up all of their fees. The code here is functionally equivalent to the
While
loop example above.
totalFees = ScratchVar(TealType.uint64)
i = ScratchVar(TealType.uint64)
Seq([
totalFees.store(Int(0)),
For(i.store(Int(0)), i.load() < Global.group_size(), i.store(i.load() + Int(1))).Do(
totalFees.store(totalFees.load() + Gtxn[i.load()].fee())
)
])
The expressions :any:`Continue` and :any:`Break` can be used to exit While
and For
loops in different ways.
When Continue
is present in the loop body, it instructs the program to skip the remainder
of the loop body. The loop may continue to execute as long as its condition remains true.
For example, the code below iterates though every transaction in the current group and counts how
many are payments, using the Continue
expression.
numPayments = ScratchVar(TealType.uint64)
i = ScratchVar(TealType.uint64)
Seq([
numPayments.store(Int(0)),
For(i.store(Int(0)), i.load() < Global.group_size(), i.store(i.load() + Int(1))).Do(Seq([
If(Gtxn[i.load()].type_enum() != TxnType.Payment)
.Then(Continue()),
numPayments.store(numPayments.load() + Int(1))
]))
])
When Break
is present in the loop body, it instructs the program to completely exit the
current loop. The loop will not continue to execute, even if its condition remains true.
For example, the code below finds the index of the first payment transaction in the current group,
using the Break
expression.
firstPaymentIndex = ScratchVar(TealType.uint64)
i = ScratchVar(TealType.uint64)
Seq([
# store a default value in case no payment transactions are found
firstPaymentIndex.store(Global.group_size()),
For(i.store(Int(0)), i.load() < Global.group_size(), i.store(i.load() + Int(1))).Do(
If(Gtxn[i.load()].type_enum() == TxnType.Payment)
.Then(Seq([
firstPaymentIndex.store(i.load()),
Break()
]))
),
# assert that a payment was found
Assert(firstPaymentIndex.load() < Global.group_size())
])
Note
Subroutines are only available in TEAL version 4 or higher.
A subroutine is section of code that can be called multiple times from within a program. Subroutines are PyTeal's equivalent to functions. Subroutines can accept any number of arguments, and these arguments must be PyTeal expressions. Additionally, a subroutine may return a single value, or no value.
To create a subroutine, apply the :any:`Subroutine` function decorator to a Python function which implements the subroutine. This decorator takes one argument, which is the return type of the subroutine. :any:`TealType.none` indicates that the subroutine does not return a value, and any other type (e.g. :any:`TealType.uint64` or :any:`TealType.bytes`) indicates the return type of the single value the subroutine returns.
For example,
@Subroutine(TealType.uint64)
def isEven(i):
return i % Int(2) == Int(0)
To call a subroutine, simply call it like a normal Python function and pass in its arguments. For example,
App.globalPut(Bytes("value_is_even"), isEven(Int(10)))
Recursion with subroutines is also possible. For example, the subroutine below also checks if its argument is even, but uses recursion to do so.
@Subroutine(TealType.uint64)
def recursiveIsEven(i):
return (
If(i == Int(0))
.Then(Int(1))
.ElseIf(i == Int(1))
.Then(Int(0))
.Else(recursiveIsEven(i - Int(2)))
)
The :any:`Return` expression can be used to explicitly return from a subroutine.
If the subroutine does not return a value, Return
should be called with no arguments. For
example, the subroutine below asserts that the first payment transaction in the current group has a
fee of 0:
@Subroutine(TealType.none)
def assertFirstPaymentHasZeroFee():
i = ScratchVar(TealType.uint64)
return Seq([
For(i.store(Int(0)), i.load() < Global.group_size(), i.store(i.load() + Int(1))).Do(
If(Gtxn[i.load()].type_enum() == TxnType.Payment)
.Then(Seq([
Assert(Gtxn[i.load()].fee() == Int(0)),
Return()
]))
),
# no payments found
Err()
])
Otherwise if the subroutine does return a value, that value should be the argument to the Return
expression. For example, the subroutine below checks whether the current group contains a payment
transaction:
@Subroutine(TealType.uint64)
def hasPayment():
i = ScratchVar(TealType.uint64)
return Seq([
For(i.store(Int(0)), i.load() < Global.group_size(), i.store(i.load() + Int(1))).Do(
If(Gtxn[i.load()].type_enum() == TxnType.Payment)
.Then(Return(Int(1)))
),
Return(Int(0))
])
Return
can also be called from the main program. In this case, a single integer argument
should be provided, which is the success value for the current execution. A true value (> 0)
is equivalent to :any:`Approve`, and a false value is equivalent to :any:`Reject`.