The withdrawWinnings
function allows users to withdraw their pending winnings. The expected behavior is that the contract first updates the user's state to reflect the withdrawal, and then sends the Ether to the user, following the Checks-Effects-Interactions pattern.
However, in the current implementation, the contract performs the external call (via call{value: amount}
) before updating the internal state (pendingWinnings[msg.sender] = 0
). This violates a fundamental Solidity security pattern and, while currently mitigated by the nonReentrant
modifier, it increases the risk of reentrancy if that protection is ever removed, bypassed, or incorrectly reused elsewhere.
Likelihood:
The issue will manifest any time this pattern is copied without a reentrancy guard.
Developers might mistakenly remove or forget the nonReentrant
modifier in future refactors or similar functions.
Impact:
In the absence of nonReentrant
, this could be exploited for fund theft via reentrancy.
It can lead to unsafe assumptions during auditing or review, affecting the system's long-term security posture.
A malicious contract could exploit the unsafe order of operations by triggering a reentrant call during the Ether transfer. If the nonReentrant
modifier were absent or misapplied, the attacker could repeatedly call withdrawWinnings()
before their balance is cleared, draining funds.
Reorder the logic to update the user's state before making the external call. This follows the Checks-Effects-Interactions pattern and ensures safety even if the reentrancy guard is modified or removed in the future.
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.