The withdraw() function verifies if a user won by checking if userToCountry[msg.sender] == winner on lines 299-303. Under normal behavior, users who joined the winning country should be able to withdraw their share of the vault, regardless of whether they joined other countries.
However, the userToCountry mapping is overwritten each time a user calls joinEvent(), storing only the last country they joined. If a user joins the winning country first, then joins a different country, userToCountry[msg.sender] will show the non-winning country, causing the withdraw function to revert with didNotWin() even though the user has valid shares for the winning country stored in userSharesToCountry[msg.sender][winnerCountryId]. This locks user funds, as they cannot withdraw their legitimate winnings.
This vulnerability occurs when users join multiple countries, as the userToCountry mapping is overwritten on each join, storing only the last country joined
The bug manifests during withdrawal attempts when users who joined the winning country first but then joined another country cannot withdraw, even though they have valid shares for the winning country in userSharesToCountry
Users who joined the winning country but then joined another country cannot withdraw their legitimate winnings, causing their funds to be permanently locked in the vault
The verification logic incorrectly denies withdrawals to legitimate winners who have valid shares for the winning country, breaking the core functionality of the vault
Explanation of PoC:
This proof of concept demonstrates how users who joined the winning country but then joined another country cannot withdraw their winnings. The test shows that even though the user has valid shares for the winning country, the withdraw function reverts because userToCountry only stores the last country joined.
Test Results:
✅ User joins winning country first
✅ User joins losing country second (overwrites userToCountry)
✅ User has valid shares for winning country
✅ Withdraw reverts even though user should be able to withdraw
Explanation:
The recommended mitigation checks if the user has shares for the winning country using userSharesToCountry[owner][winnerCountryId] instead of relying on userToCountry, which only stores the last country joined. This ensures users can withdraw if they have valid shares for the winning country, regardless of which country they joined last.
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.