Canceled participants keep their winner share weighting, allowing griefers to dilute real winners’ payouts and permanently trap assets in the vault (High).
joinEvent() permanently records each participant’s shares for their chosen country in userSharesToCountry and tracks them in usersAddress:
When a user later cancels, the contract burns their shares and refunds stakedAsset, but it never clears their recorded country allocation:
During setWinner, _getWinnerShares() iterates over usersAddress and sums userSharesToCountry[user][winnerCountryId] without filtering out canceled participants:
A malicious user can join the eventual winning country, then cancel to retrieve their stake but still remain counted in totalWinnerShares. Because finalizedVaultAsset excludes the refunded stake, each real winner’s withdrawal is diluted and leaves the vault with unusable leftover tokens.
Any participant can execute this griefing pattern: join a country, cancel before eventStartDate, and later let the true winners withdraw less. No privileged access is required, and the attack has zero net cost because the attacker recovers their entire stake.
Honest winners receive only a fraction of the vault balance even though they hold all remaining shares.
Residual assets remain stranded in the vault forever, causing a permanent loss of funds for winners.
Alice deposits 100 tokens and calls joinEvent(countryX).
Bob deposits 100 tokens, calls joinEvent(countryX), then calls cancelParticipation before eventStartDate, recovering his 100 tokens while burning his shares.
After the event, the owner calls setWinner(countryX). _getWinnerShares adds both Alice’s 100 shares and Bob’s stale 100 shares, setting totalWinnerShares = 200 even though only Alice’s 100 shares remain.
Alice calls withdraw(). finalizedVaultAsset equals 100 (only Alice’s stake), so Alice receives Math.mulDiv(100, 100, 200) = 50 tokens while the remaining 50 tokens are trapped in the vault.
Track each participant’s active country and, when canceling, reset both that mapping and the user’s userSharesToCountry entry so _getWinnerShares excludes them:
Consider recomputing totalWinnerShares from live share balances instead of static snapshots, or use ERC4626-style accounting so withdrawals scale only by outstanding shares.
Add tests for the scenario where a participant joins, cancels, and the same country later wins to ensure they no longer affect winner accounting.
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.