Calls in Loop
Detects external calls inside loops that can cause denial-of-service when any single call reverts or the loop exceeds the block gas limit.
Calls in Loop
Overview
The calls-in-loop detector identifies functions that perform external calls (CALL, STATICCALL, DELEGATECALL) inside a loop. If any single call reverts — because the recipient is a contract that throws, a blacklisted address, or simply an address that does not exist — the entire transaction reverts. This gives any single participant the power to block operations that affect all participants.
Why This Is an Issue
Common patterns like distributing rewards to all stakers, refunding multiple participants, or batch-transferring tokens to many recipients are vulnerable. One malicious or malfunctioning recipient causes the entire batch to fail. Even without malicious intent, if the recipient list grows large enough, the cumulative gas cost exceeds the block gas limit and the function becomes permanently uncallable.
How to Resolve
// Before: Vulnerable — reverts if any transfer fails
function distributeRewards(address[] memory recipients, uint256[] memory amounts) external {
for (uint i = 0; i < recipients.length; i++) {
payable(recipients[i]).transfer(amounts[i]); // Reverts on failure
}
}
// After: Fixed — pull pattern, each user withdraws individually
mapping(address => uint256) public pendingRewards;
function distributeRewards(address[] memory recipients, uint256[] memory amounts) external {
for (uint i = 0; i < recipients.length; i++) {
pendingRewards[recipients[i]] += amounts[i]; // No external call
}
}
function claimReward() external {
uint256 amount = pendingRewards[msg.sender];
pendingRewards[msg.sender] = 0;
payable(msg.sender).transfer(amount);
}
Detection Methodology
- Loop identification: Detects loop structures via back-edges in the control flow graph.
- External call in loop body: Checks for CALL, STATICCALL, or DELEGATECALL opcodes within loop-body blocks.
- Revert propagation: Determines whether a failed call causes the entire transaction to revert (no try/catch or success-check-and-continue pattern).
- Bound analysis: Checks whether the loop iteration count is bounded by a constant or can grow unbounded.
Limitations
False positives: Loops with a small, fixed iteration count (e.g., distributing to 3 known addresses) are safe but may be flagged. False negatives: External calls made through internal function calls within the loop body may not be detected if the call depth exceeds analysis limits.
Related Detectors
- DoS — detects denial-of-service patterns broadly
- Controlled Array Length — detects unbounded arrays
- Gas Griefing — detects gas manipulation attacks