Skip to main content
Sigvex

msg.value in Loop Remediation

How to avoid crediting the same ETH multiple times by never treating msg.value as per-iteration value.

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.

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.value inside the loop even for a “single” per-item cost, so N items cost 1× instead of N×.
  • Splitting msg.value / n without checking for a remainder, silently losing or trapping dust.
  • Comparing against address(this).balance (which includes prior funds) instead of the transaction’s msg.value.

References