Normal behavior: claimFaucetTokens() should enforce a 3-day cooldown and daily limits, and optionally send a one-time Sepolia ETH bonus to first-time claimers. State (last claim time, daily counters, hasClaimedEth) must be updated before any external calls.
Issue: The contract performs an external ETH transfer to msg.sender before updating critical state variables (lastClaimTime, dailyClaimCount, etc.), violating Checks-Effects-Interactions and enabling reentrancy by a malicious contract. A reentrant caller can repeatedly claim tokens (and possibly ETH) within one transaction, bypassing cooldowns and limits.
Likelihood
1.Occurs when a claimer uses a malicious smart contract that implements a payable fallback/receive which re-calls claimFaucetTokens() during the ETH transfer.
2.Because state updates happen after the external call, the reentered call still passes cooldown/daily checks.
Impact
1.Attacker can drain faucet ERC20 tokens (depleting the faucet supply).
2.Attacker can also exhaust the faucet’s Sepolia ETH allocation, denying gas to legitimate users.
Explanation / steps to reproduce: Deploy the attacker contract with the faucet address.Ensure attacker contract hasn’t previously claimed ETH (so hasClaimedEth is false). Call attack() from attacker owner. The faucet sends ETH to the attacker; attacker receive() re-enters claimFaucetTokens() because lastClaimTime was not yet set. Repeat until tokens drained or caps reached.
Add reentrancy protection: inherit ReentrancyGuard and mark claimFaucetTokens() nonReentrant.
Apply Checks → Effects → Interactions: update lastClaimTime, dailyClaimCount, and hasClaimedEth before any external calls (ETH transfer). Use local variables for claimer = msg.sender instead of storing faucetClaimer in contract storage.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.
The contest is complete and the rewards are being distributed.