Root + Impact
Description
Actual behavior:
Any address can flood donations.
Impact
Potential log overflow and gas waste.
Risk
Likelihood:
While any address can send excessive ETH, this behavior is not incentivized or common.
Exploiting this vector (e.g., spam donations to flood logs or skew balances) is unlikely in most practical deployments.
Impact:
Proof of Concept
This attacker contract floods the vulnerable contract with tiny ETH donations in a loop:
pragma solidity ^0.8.0;
interface IVulnerableFaucet {
function getBalance() external view returns (uint256);
}
contract DonationFlooder {
address payable public target;
constructor(address payable _target) {
target = _target;
}
function floodDonations(uint256 count) external payable {
require(msg.value >= count, "Insufficient ETH for flood");
uint256 amountPerTx = msg.value / count;
for (uint256 i = 0; i < count; i++) {
(bool success, ) = target.call{value: amountPerTx}("");
require(success, "Donation failed");
}
}
receive() external payable {}
}
Recommended Mitigation
Add donation cap and tracking.
To mitigate this, the recommended changes enforce donation constraints, track per-user donation history, and control how ETH enters the contract.
- remove this code
// Vulnerable: no limits or tracking
receive() external payable {
emit DonationReceived(msg.sender, msg.value);
}
+ add this code
// Mitigation: enforce per-tx and per-user caps, track donations, optionally require EOA
mapping(address => uint256) public totalDonated;
uint256 public constant MIN_DONATION = 0.01 ether;
uint256 public constant MAX_DONATION = 10 ether;
uint256 public constant MAX_TOTAL_PER_USER = 50 ether;
// Optional: prevent contracts from donating (useful to reduce bot/contract spam)
modifier onlyEOA() {
require(msg.sender == tx.origin, "Only EOA");
_;
}
// Use an explicit donate() entrypoint so we can enforce rules reliably.
// If you still want to accept plain transfers, keep a receive() but call donate().
function donate() external payable onlyEOA {
require(msg.value >= MIN_DONATION, "Donation too small");
require(msg.value <= MAX_DONATION, "Donation too large");
require(totalDonated[msg.sender] + msg.value <= MAX_TOTAL_PER_USER, "Donor cap exceeded");
// Track donation
totalDonated[msg.sender] += msg.value;
emit DonationReceived(msg.sender, msg.value);
}
// Optional: make receive() revert to force callers to use donate()
// receive() external payable {
// revert("Use donate()");
// }