The refund() function contains no check on the current block timestamp relative to the raffle end time. This means players can call refund() at any point — including after the raffle duration has elapsed and selectWinner() can be called:// Root cause in the codebase with @> marks to highlight the relevant section
Compare this to selectWinner(), which correctly enforces a time boundary:
This creates a window where a player can observe the blockchain state after the raffle ends — potentially front-running selectWinner() — and refund their ticket if they determine they are not the winner, while still having participated during the raffle period. This undermines the fairness of the protocol and can be combined with the weak randomness vulnerability to guarantee a free exit when losing.
Likelihood:
The raffle end time is publicly visible on-chain (raffleStartTime + raffleDuration)
Any player can monitor the blockchain and call refund() in the same block as the raffle end
Combined with the weak randomness vulnerability, a player can compute the winner before calling selectWinner(), and refund if they are not the winner — a risk-free strategy
This requires only basic blockchain monitoring tools available to any user
The attack is repeatable every raffle round
Impact:
Players can participate in the raffle with zero financial risk: enter, observe the outcome, and refund if they lose
Honest players who do not exploit this have a disadvantage compared to those who do
If many players refund at the last moment, the players array fills with address(0) slots, increasing the probability of the prize being sent to address(0) (separate finding)
The protocol's stated rules — "users are allowed to get a refund if they call the refund function" — do not specify this is intended to be possible after the raffle ends
The protocol may fail to reach minimum player count after last-minute refunds, blocking selectWinner() from executing
The Certora Prover rule refund_only_before_raffle_ends was formally verified and returned VIOLATED, providing mathematical proof that refund() succeeds even when block.timestamp >= raffleStartTime + raffleDuration:
Add a time boundary check to refund() that mirrors the one already present in selectWinner():
This ensures refunds are only possible while the raffle is still active, preventing players from using post-raffle information to make risk-free decisions. Additionally, consider implementing a commit-reveal scheme for winner selection to prevent front-running based on predictable randomness.
The contest is live. Earn rewards by submitting a finding.
Submissions are being reviewed by our AI judge. Results will be available in a few minutes.
View all submissionsThe contest is complete and the rewards are being distributed.