Control Flow¶
SolScript provides standard control flow statements.
Conditionals¶
If Statement¶
function checkValue(uint256 value) public pure returns (string memory) {
if (value > 100) {
return "High";
}
return "Low";
}
If-Else¶
function checkValue(uint256 value) public pure returns (string memory) {
if (value > 100) {
return "High";
} else {
return "Low";
}
}
If-Else If-Else¶
function classify(uint256 value) public pure returns (string memory) {
if (value == 0) {
return "Zero";
} else if (value < 10) {
return "Small";
} else if (value < 100) {
return "Medium";
} else {
return "Large";
}
}
Ternary Operator¶
function max(uint256 a, uint256 b) public pure returns (uint256) {
return a > b ? a : b;
}
function abs(int256 value) public pure returns (uint256) {
return value >= 0 ? uint256(value) : uint256(-value);
}
Loops¶
For Loop¶
function sum(uint256[] memory values) public pure returns (uint256) {
uint256 total = 0;
for (uint256 i = 0; i < values.length; i++) {
total += values[i];
}
return total;
}
While Loop¶
function factorial(uint256 n) public pure returns (uint256) {
uint256 result = 1;
uint256 i = n;
while (i > 1) {
result *= i;
i--;
}
return result;
}
Do-While Loop¶
function countDigits(uint256 n) public pure returns (uint256) {
uint256 count = 0;
do {
count++;
n /= 10;
} while (n > 0);
return count;
}
Loop Control¶
Break¶
Exit the loop early:
function findIndex(uint256[] memory arr, uint256 target) public pure returns (int256) {
for (uint256 i = 0; i < arr.length; i++) {
if (arr[i] == target) {
return int256(i); // Found, exit loop
}
}
return -1; // Not found
}
Continue¶
Skip to next iteration:
function sumEven(uint256[] memory values) public pure returns (uint256) {
uint256 total = 0;
for (uint256 i = 0; i < values.length; i++) {
if (values[i] % 2 != 0) {
continue; // Skip odd numbers
}
total += values[i];
}
return total;
}
Early Returns¶
function validate(uint256 value) public pure returns (bool) {
if (value == 0) {
return false; // Early return
}
if (value > 1000) {
return false; // Early return
}
// More validation...
return true;
}
Require Statements¶
Stop execution if condition fails:
function withdraw(uint256 amount) public {
require(amount > 0, "Amount must be positive");
require(amount <= balance, "Insufficient balance");
require(msg.sender == owner, "Not authorized");
balance -= amount;
// Transfer...
}
Assert Statements¶
For invariant checking:
function transfer(address to, uint256 amount) public {
uint256 totalBefore = balances[msg.sender] + balances[to];
balances[msg.sender] -= amount;
balances[to] += amount;
// Invariant: total should remain the same
assert(balances[msg.sender] + balances[to] == totalBefore);
}
Revert Statements¶
Explicit revert with custom errors:
error InvalidAmount(uint256 provided, uint256 minimum);
error Unauthorized(address caller);
function deposit(uint256 amount) public {
if (amount < minimumDeposit) {
revert InvalidAmount(amount, minimumDeposit);
}
if (msg.sender != authorized) {
revert Unauthorized(msg.sender);
}
// Process deposit...
}
Patterns¶
Guard Pattern¶
Check conditions early:
function transfer(address to, uint256 amount) public {
// Guards at the top
require(to != address(0), "Invalid recipient");
require(amount > 0, "Invalid amount");
require(balances[msg.sender] >= amount, "Insufficient balance");
// Logic after guards pass
balances[msg.sender] -= amount;
balances[to] += amount;
emit Transfer(msg.sender, to, amount);
}
State Machine¶
enum State { Created, Active, Completed, Cancelled }
State public state;
modifier inState(State expected) {
require(state == expected, "Invalid state");
_;
}
function activate() public inState(State.Created) {
state = State.Active;
}
function complete() public inState(State.Active) {
state = State.Completed;
}
function cancel() public {
require(state != State.Completed, "Cannot cancel completed");
state = State.Cancelled;
}
Bounded Loops¶
Always bound your loops:
// Good - bounded by array length (which should be reasonable)
function sumAll(uint256[] memory values) public pure returns (uint256) {
uint256 total = 0;
for (uint256 i = 0; i < values.length; i++) {
total += values[i];
}
return total;
}
// Good - explicit maximum
uint256 constant MAX_ITERATIONS = 100;
function processItems() public {
uint256 processed = 0;
uint256 i = startIndex;
while (i < items.length && processed < MAX_ITERATIONS) {
// Process item
processed++;
i++;
}
startIndex = i; // Save progress for next call
}
Short-Circuit Evaluation¶
// Second condition not evaluated if first is false
if (user != address(0) && balances[user] > 0) {
// Safe to access balances
}
// Second condition not evaluated if first is true
if (isAdmin || msg.sender == owner) {
// Authorized
}
Common Mistakes¶
Unbounded Loops¶
// BAD - could run out of gas
function sendToAll() public {
for (uint256 i = 0; i < recipients.length; i++) {
// If recipients grows large, this will fail
send(recipients[i], amount);
}
}
// GOOD - process in batches
function sendBatch(uint256 start, uint256 count) public {
uint256 end = start + count;
if (end > recipients.length) end = recipients.length;
for (uint256 i = start; i < end; i++) {
send(recipients[i], amount);
}
}
Off-by-One Errors¶
// BAD - skips last element
for (uint256 i = 0; i < arr.length - 1; i++) { }
// GOOD - includes all elements
for (uint256 i = 0; i < arr.length; i++) { }
Integer Overflow in Loops¶
// Be careful with uint256 underflow
for (uint256 i = arr.length - 1; i >= 0; i--) {
// i will underflow to max uint256 when it reaches 0!
}
// GOOD - safe reverse iteration
for (uint256 i = arr.length; i > 0; i--) {
uint256 index = i - 1;
// Use index
}
Next Steps¶
- Events & Errors - Logging and error handling
- Modifiers - Reusable conditions
- Interfaces - Contract interfaces