userSharesToCountryUsers deposit assets and call joinEvent() to participate, storing their vault shares in userSharesToCountry[msg.sender][countryId]. The cancelParticipation() function allows users to withdraw their deposit and burn their shares before the event starts.
But, cancelParticipation() does not clear the entry in userSharesToCountry. When setWinner() is called, _getWinnerShares() iterates over usersAddress[] and includes canceled users' shares, inflating totalWinnerShares and diluting payouts.
Likelihood:
Users cancel participation before event start // occurs when changing predictions or correcting mistakes
setWinner() is called after cancellations // standard post event flow
Impact:
totalWinnerShares is inflated by ghost shares // reduces payout per share
Legitimate winners receive less than their fair share // direct financial loss and funds stuck in vault
Description:
Alice cancels after joining, removing her 1000 shares. Bob is the only active participant with 1000 assets in the vault. But _getWinnerShares() includes Alice’s stale shares via usersAddress[], inflating totalWinnerShares to 2000. Bob receives only 500 instead of 1000, and 500 tokens remain locked.
CancelParticipation burns shares but leaves the address inside usersAddress and keeps userSharesToCountry populated.
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.