The withdrawWinnings() function should follow the Checks-Effects-Interactions (CEI) pattern by first validating conditions, then updating all relevant state variables, and finally making external calls to transfer funds. This pattern prevents reentrancy attacks by ensuring that state changes are committed before any external interactions that could potentially call back into the contract.
The withdrawWinnings() function violates the CEI pattern by clearing the pendingWinnings[msg.sender] state variable after making the external call to transfer ETH via payable(msg.sender).call{value: amount}(""). This creates a reentrancy window where the state still shows pending winnings during the external call execution, making the contract vulnerable to drain attacks if the nonReentrant modifier protection ever fails, is removed, or is bypassed. The vulnerability is inconsistent with the codebase since withdrawPlatformFees() correctly implements the CEI pattern by clearing platformFeesBalance before making external calls.
The external call happens before the state update, violating the CEI pattern and creating a potential reentrancy window.
Likelihood:
This vulnerability becomes exploitable whenever the nonReentrant modifier protection is removed during contract upgrades, maintenance, or refactoring, as the underlying CEI violation remains present in the code structure regardless of external protections
The vulnerability activates during any legitimate withdrawal attempt where an attacker controls the receiving address (through malicious contracts), creating an immediate reentrancy window during the external call execution before state cleanup
Impact:
Complete drainage of the contract's ETH balance through recursive calls to withdrawWinnings(), as the attacker can repeatedly withdraw the same pending winnings amount before the state is cleared, multiplying their extraction beyond their legitimate entitlement
Cascading financial losses affecting all other users with legitimate pending winnings, as the attacker's exploitation depletes the contract's available funds and prevents other users from successfully withdrawing their rightful winnings
This PoC proves the CEI violation exists by analyzing the code structure and demonstrating the vulnerability window. The tests show that withdrawWinnings() makes external calls before updating state, creating a reentrancy risk that currently relies on the nonReentrant modifier for protection. The PoC also highlights the inconsistency with withdrawPlatformFees(), which correctly implements the CEI pattern, proving that proper implementation is already demonstrated elsewhere in the codebase.
Move the state update (pendingWinnings[msg.sender] = 0) before the external call to follow the Checks-Effects-Interactions pattern. This eliminates the reentrancy window by ensuring that the user's pending winnings are cleared before any external interaction occurs
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.