msg.value is read multiple times inside a loop or a payable multicall, so a single ETH payment is counted once per iteration. The caller pays for one but is credited for many — minting/buying N times while sending value for one.
// payable multicall([buy(), buy(), buy()]) with msg.value = 1 price. // each buy() checks require(msg.value == price) -> passes 3x. // attacker gets 3 items for the price of 1.
// track consumed value, or forbid payable multicall
uint256 remaining = msg.value;
for (uint i; i < n; i++) { require(remaining >= price); remaining -= price; ... }
Largest recorded losses in the same failure class — related, not the same bug:
The SaferICO scanner runs 201 detectors over your Solidity source, SAFE-0146 among them. Paste an address or the source itself — a small per-scan fee, shown before you sign, or unlimited on any plan.