The withdrawWinnings()
function allows a player to withdraw their earnings if they have any pending winnings. The current behavior sends ETH to the player before setting their pendingWinnings[msg.sender]
balance to zero.
This is only secure because of the nonReentrant
modifier. However, relying solely on a modifier creates a fragile trust assumption. The code would be vulnerable to reentrancy attacks if the modifier is removed or bypassed, and is non-idiomatic compared to secure withdrawal patterns.
Likelihood:
This risk materializes whenever someone forgets to apply the nonReentrant
modifier, especially during future upgrades or copy-pasting this logic into new functions.
Contracts using this pattern without a modifier (or with one that’s bypassed via inheritance or proxy logic) will be vulnerable.
Impact:
An attacker can recursively re-enter the function before the balance is zeroed, draining the contract.
Even if not exploited, this pattern goes against Solidity best practices and is flagged in most static analysis tools, causing audit friction and lower trust in code quality.
Imagine a contract without the nonReentrant
modifier or with an accidental omission:
Because pendingWinnings[msg.sender]
is only zeroed after the external call, the attacker repeatedly withdraws funds.
We only audit the current code in scope. We cannot make speculation with respect to how this codebase will evolve in the future. For now there is a nonReentrant modifier which mitigates any reentrancy. CEI is a good practice, but it's not mandatory. Informational
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.