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 */ @arc4.abimethod({ readonly: true }) 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 */ @arc4.abimethod({ readonly: true }) 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 */ @arc4.abimethod({ readonly: true }) 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 */ @arc4.abimethod({ readonly: true }) public forLoop(): uint64[] { // Create an array with values [0, 1, 2, 3] let numbers: uint64[] = []
// Use for-of loop with urange to fill the array for (const item of urange(4)) { numbers = [...numbers, item] }
// Create a reversed array [3, 2, 1, 0] let reversed: uint64[] = []
// Reverse the array by prepending each element // This demonstrates another way to use for-of loops for (const num of numbers) { // Add each new element to the beginning of the array reversed = [num, ...reversed] }
// Sum all values in the reversed array let sum: uint64 = 0 for (const num of reversed) { sum += num }
// The sum should be 0 + 1 + 2 + 3 = 6 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): # urange: reversed items, forward index @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 */ @arc4.abimethod({ readonly: true }) 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. |