Control Flow | Algorand Developer Portal
Control Flow
Control flow in Algorand smart contracts follows common programming paradigms, with support for if statements, while loops, for loops, and switch/match statements. Both Algorand Python and Algorand TypeScript provide familiar syntax for these constructs.
If statements
If statements work as you would expect in any programming language. The conditions must be an expression that evaluates to a boolean.
/**
* Determines if an account is rich based on its balance
* @param accountBalance The account balance to check
* @returns A string describing the account's wealth status
*/
@readonly
public isRich(accountBalance: uint64): string {
if (accountBalance > 1000) {
return 'This account is rich!'
} else if (accountBalance > 100) {
return 'This account is doing well.'
} else {
return 'This account is poor :('
}
}
@arc4.abimethod
def is_rich(self, account_balance: UInt64) -> String:
if account_balance > 1000:
return String("This account is rich!")
elif account_balance > 100:
return String("This account is doing well.")
else:
return String("This account is poor :(")
Ternary conditions
Ternary conditions allow for compact conditional expressions. The condition must be an expression that evaluates to a boolean.
/**
* Determines if a number is even or odd
* @param number The number to check
* @returns "Even" if the number is even, "Odd" otherwise
*/
@readonly
public isEven(number: uint64): string {
return number % 2 === 0 ? 'Even' : 'Odd'
}
@arc4.abimethod
def is_even(self, number: UInt64) -> String:
return String("Even") if number % 2 == 0 else String("Odd")
While loops
While loops iterate as long as the specified condition is true. The condition must be an expression that evaluates to a boolean.
You can use break and continue statements to control loop execution.
/**
* Demonstrates while loop with continue and break statements
* @returns The number of iterations performed
*/
@readonly
public loop(): uint64 {
let num: uint64 = 10
let loopCount: uint64 = 0
while (num > 0) {
if (num > 5) {
num -= 1
loopCount += 1
continue
}
num -= 2
loopCount += 1
if (num === 1) {
break
}
}
return loopCount
}
class WhileLoopExample(ARC4Contract):
@arc4.abimethod
def loop(self) -> UInt64:
num = UInt64(10)
loop_count = UInt64(0)
while num > 0:
if num > 5:
num -= 1
loop_count += 1
continue
num -= 2
loop_count += 1
if num == 1:
break
return loop_count
For Loops
For loops are used to iterate over sequences, ranges and ARC-4 arrays.
In Algorand Python, utility functions like uenumerate and urange facilitate creating sequences and ranges of UInt64 numbers, and the built-in reversed method works with these. In Algorand TypeScript, standard iteration constructs are available.
Here is an example of how you can use For loops in smart contracts:
/**
* Demonstrates different types of for loops
* @returns An array of uint64 values in reversed order
*/
@readonly
public forLoop(): uint64[] {
let numbers: uint64[] = []
for (const item of urange(4)) {
numbers = [...numbers, item]
}
let reversed: uint64[] = []
for (const num of numbers) {
reversed = [num, ...reversed]
}
let sum: uint64 = 0
for (const num of reversed) {
sum += num
}
assert(sum === 6, 'Sum of reversed array should be 6')
return reversed
}
FourArray: t.TypeAlias = arc4.StaticArray[arc4.UInt8, t.Literal[4]]
class ForLoopsExample(ARC4Contract):
@arc4.abimethod
def for_loop(self) -> FourArray:
array = FourArray(arc4.UInt8(0), arc4.UInt8(0), arc4.UInt8(0), arc4.UInt8(0))
for index, item in uenumerate(reversed(urange(4))): # [3, 2, 1, 0]
array[index] = arc4.UInt8(item)
x = UInt64(0)
for item in urange(1, 5): # [1, 2, 3, 4]
x += item
assert x == 10
return array
Switch or Match Statements
switch for TypeScript and match for Python provide a clean way to handle multiple conditions. They follow the standard syntax of their respective languages.
/**
* Returns the day of the week based on a numeric input
* @param date A number from 0-6 representing a day of the week
* @returns The name of the day, or "Invalid day" if out of range
*/
@readonly
public getDay(date: uint64): string {
switch (Uint64(date)) {
case Uint64(1):
return 'Monday'
case Uint64(2):
return 'Tuesday'
case Uint64(3):
return 'Wednesday'
case Uint64(4):
return 'Thursday'
case Uint64(5):
return 'Friday'
case Uint64(6):
return 'Saturday'
case Uint64(7):
return 'Sunday'
default:
return 'Invalid day'
}
}
class MatchStatements(ARC4Contract):
@arc4.abimethod
def get_day(self, date: UInt64) -> String:
match date:
case UInt64(0):
return String("Monday")
case UInt64(1):
return String("Tuesday")
case UInt64(2):
return String("Wednesday")
case UInt64(3):
return String("Thursday")
case UInt64(4):
return String("Friday")
case UInt64(5):
return String("Saturday")
case UInt64(6):
return String("Sunday")
case _:
return String("Invalid day")
Note: Captures and patterns are not supported. Currently, there is only support for basic case/switch functionality; pattern matching and guard clauses are not currently supported.
TEAL Flow Control Opcode
Algorand Python and TypeScript are high-level smart contract languages that allow developers to express control flows in more accessible languages. However, the Algorand Virtual Machine (AVM) executes the Transaction Execution Approval Language (TEAL) flow control opcodes after compilation. TEAL is a low-level assembly language that the AVM understands directly. While developers will write smart contracts in higher-level languages, understanding the underlying TEAL opcodes can be beneficial to comprehend what’s happening line by line. The following chart contains all of the control flow opcodes available in TEAL.
| Opcode | Description |
|---|---|
| err | Fail immediately. |
| bnz target | branch to TARGET if value A is not zero |
| bz target | branch to TARGET if value A is zero |
| b target | branch unconditionally to TARGET |
| return | use A as success value; end |
| pop | discard A |
| popn n | remove N values from the top of the stack |
| dup | duplicate A |
| dup2 | duplicate A and B |
| dupn n | duplicate A, N times |
| dig n | Nth value from the top of the stack. dig 0 is equivalent to dup |
| bury n | replace the Nth value from the top of the stack with A. bury 0 fails. |
| cover n | remove top of stack, and place it deeper in the stack such that N elements are above it. Fails if stack depth <= N. |
| uncover n | remove the value at depth N in the stack and shift above items down so the Nth deep value is on top of the stack. Fails if stack depth <= N. |
| frame_dig i | Nth (signed) value from the frame pointer. |
| frame_bury i | replace the Nth (signed) value from the frame pointer in the stack with A |
| swap | swaps A and B on stack |
| select | selects one of two values based on top-of-stack: B if C != 0, else A |
| assert | immediately fail unless A is a non-zero number |
| callsub target | branch unconditionally to TARGET, saving the next instruction on the call stack |
| proto a r | Prepare top call frame for a retsub that will assume A args and R return values. |
| retsub | pop the top instruction from the call stack and branch to it |
| switch target … | branch to the Ath label. Continue at following instruction if index A exceeds the number of labels. |
| match target … | given match cases from A[1] to A[N], branch to the Ith label where A[I] = B. Continue to the following instruction if no matches are found. |