Intro to Control Flow in Solidity
Introducing the basic control flow structures, like loops and conditionals, in Solidity.
July 22, 2022 | 5 minute readIn programming, control flow refers to the flow of execution of your code. Practically speaking, this means conditionals and loops.
Solidity has a set of control structures, just like other programming languages. And this article covers what they are and how to use them. Specifically, this article covers conditionals (if
and else
) and loops (for
, while
, and do...while
).
Conditionals
If you’re coming from JavaScript, Go, Java, or any other C-like language, then Solidity’s version of conditionals will look very familiar. Solidity uses if
and else
in combination to test any set of conditions.
Here’s a basic example:
contract FavoriteNumber {
uint favoriteNumber;
constructor(uint _number) {
favoriteNumber = _number;
}
function checkFavoriteNumber() public view returns (uint) {
if (favoriteNumber < 50) {
// do something
} else if (favoriteNumber < 100) {
// do something else
} else {
// do something else
}
return favoriteNumber;
}
}
Just like in JavaScript, if
and else
can be combined to test any set of conditions.
Loops
Solidity provides a couple of structures for looping. Namely, for
, while
, and do...while
, which I’ll explain below.
Before discussing specific loops, it’s important to remember that each additional line of code adds to the cost of running a function. Because of this, you won’t see many loops in production smart contracts. Additionally, you should use loop control keywords like break
, continue
, and return
to avoid unnecessary execution. Every line of code counts!
for
loops
for
loops in Solidity look and act just like they do in JavaScript:
uint counter = 10;
event Number(uint _number);
for (uint i = 0; i < counter; i++) {
emit Number( i );
}
Here we have a counter that we will count up to. Our loop declares an increment variable (uint i = 0;
), then our breaking condition (i < counter;
), and finally our incrementor (i++
). With each iteration of the loop, we emit an event, called Number
.
That is a trivial example of a loop. We can look at a more complex example of one in the wild:
// Payout each royalty
for (uint256 i = 0; i < numRecipients; ) {
// Cache the recipient and amount
recipient = recipients[i];
amount = amounts[i];
// Ensure that we aren't somehow paying out more than we have
require(amountRemaining >= amount, "insolvent");
// Transfer to the recipient
_handleOutgoingTransfer(recipient, amount, _payoutCurrency, 50000);
emit RoyaltyPayout(_tokenContract, _tokenId, recipient, amount);
// Cannot underflow as remaining amount is ensured to be greater than or equal to royalty amount
unchecked {
amountRemaining -= amount;
++i;
}
}
This code is taken from Zora v3 FeePayoutSupport contract, which is used for paying out protocol fees and royalties. This loop goes through two lists, one of royalty recipients and one of amounts. For each recipient, amount combination, it transfers that amount owed to the recipient.
while
loops
Just like in other languages, we have to be careful with while
loops to ensure we create one that actually ends. You’ll rarely see while
loops in production Solidity code.
A while
loop in Solidity looks like this:
uint counter = 10;
event Number(uint _number);
while (i < counter) {
emit Number(i);
i = i + 1;
}
We have a counter (counter
) and an incrementor (i
). With each iteration of the loop, we emit an event for the value of i
and then increment i
by 1. Once i
reaches 10, the loop will stop.
Here’s an example of such a while
loop in a production contract:
uint256 temp = value;
uint256 digits;
while (temp != 0) {
digits++;
temp /= 10;
}
This loop is from OpenZeppelin’s Strings.sol utility contract, which has a method for turning an uint256
into a string
.
do...while
loops
The last looping structure available in Solidity is the do...while
loop. This is similar to a while
loop, but the structure is inverted, so the code block runs at least once. The basic syntax looks like this:
uint counter = 10;
uint i = 0;
do {
emit uintNumber( blockNumber[i] );
i = i + 1;
} while (i < counter);
The above block emits at least one event for the first iteration of the loop. That’s because the do
block executes before the condition is checked for the first time.
Here’s an example of this in the wild:
function _mintNFTs(address to, uint256 quantity) internal {
do {
uint256 toMint = quantity > MAX_MINT_BATCH_SIZE
? MAX_MINT_BATCH_SIZE
: quantity;
_mint({to: to, quantity: toMint});
quantity -= toMint;
} while (quantity > 0);
}
This function is from the Zora Drops contract, which powers
http://create.zora.co. It’s looping down from the quantity
provided by the caller and minting some number of NFTs to the to
address.
Conclusion
This article has covered the syntax for basic control flow in Solidity. The language was inspired by Java, Go, and JavaScript, so these constructs should look familiar to a programmer coming from these languages.
if
and else
can be combed to check a complex set of conditions. Solidity does also provide the ternary operator-style condition checking for tersely checking a single condition.
Solidity’s looping structures are for
, while
, and do...while
. All three are powerful, but should be used intentionally and sparingly, since each line of code increases the transaction fee for running a function. You should always avoid running a loop over a list that doesn’t have a fixed size.