Control Flow¶
PyTeal provides several control flow expressions to create programs.
Exiting the Program: Approve and Reject¶
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 Approve and 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 subroutines.
Chaining Expressions: Seq¶
The 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 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 Pop expression to discard it. For example,
Seq(
Pop(Txn.sender()),
Return(Int(1))
)
Simple Branching: If¶
In an 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 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 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)
Checking Conditions: Assert¶
The 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.
Chaining Tests: Cond¶
A 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.
Looping: While¶
Note
This expression is only available in TEAL version 4 or higher.
The 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 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))
]))
])
Looping: For¶
Note
This expression is only available in TEAL version 4 or higher.
Similar to While, the 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 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())
)
])
Exiting Loops: Continue and Break¶
The expressions Continue and 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())
])
Subroutines¶
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.
Creating Subroutines¶
To create a subroutine, apply the Subroutine function decorator to a Python function which
implements the subroutine. This decorator takes one argument, which is the return type of the subroutine.
TealType.none indicates that the subroutine does not return a value, and any other type
(e.g. TealType.uint64 or 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)
Calling Subroutines¶
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¶
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)))
)
Exiting Subroutines¶
The 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 Approve, and a false value is equivalent to Reject.