msg.value in Loop Remediation
Overview
Related Detector: msg.value in Loop
msg.value is fixed for the whole transaction. Using it inside a loop that processes N items credits the same ETH to each iteration, so a caller who sends 1 ETH can have it counted N times. The remediation is to account for the total once and require the sum of per-item amounts to equal msg.value.
Recommended Fix
Before (Vulnerable)
function fund(address[] calldata recipients) external payable {
for (uint256 i; i < recipients.length; ++i) {
// Credits msg.value to EVERY recipient — 1 ETH counted N times.
balances[recipients[i]] += msg.value;
}
}
After (Fixed — split a fixed total, and require it to match)
function fund(address[] calldata recipients, uint256[] calldata amounts) external payable {
require(recipients.length == amounts.length, "Length mismatch");
uint256 total;
for (uint256 i; i < recipients.length; ++i) {
balances[recipients[i]] += amounts[i];
total += amounts[i];
}
require(total == msg.value, "Value mismatch"); // the sum must equal what was sent
}
The invariant sum(amounts) == msg.value guarantees the caller cannot credit more ETH than they actually sent.
Common Mistakes
- Reading
msg.valueinside the loop even for a “single” per-item cost, so N items cost 1× instead of N×. - Splitting
msg.value / nwithout checking for a remainder, silently losing or trapping dust. - Comparing against
address(this).balance(which includes prior funds) instead of the transaction’smsg.value.