Normal behavior: After finalization, each winner withdraws strictly according to the snapshot of their winning shares at the time the winner is set. Moving shares between addresses after finalization should not change any address’s entitled payout.
Issue: The vault pays winners using the live balanceOf(msg.sender) at withdrawal time, while the denominator totalWinnerShares is snapshotted at setWinner. Shares are freely transferable (ERC20). A loser can transfer their shares to a winning address after setWinner to inflate the winner’s numerator without increasing the denominator, allowing the winner to withdraw more than their fair share and drain the vault, preventing other winners from claiming.
Likelihood: High
Any loser can transfer shares to a winner after finalization; nothing prevents transfers.
No linkage between payout shares and the snapshot recorded at join; numerator is manipulable.
Impact: High
A single winner can drain the vault by aggregating transferred shares, leaving other winners unable to claim.
Having at least one winner can be enforced by joining multiple addresses with small deposits
Any winner can drain the whole vault when having enough tokens (or using a flash loan)
Final distribution becomes first-come-first-serve and unfair, breaks game integrity.
Description:
user1 (small deposit) and user2 (large deposit) join the eventual winning country. In reality the person can join with 42 addresses, ensuring at least one will be the winner
After setWinner, user2 (the one with a big deposit) transfers his shares to user1 (the winner).
user1 withdraws using an inflated live balance:
denominator is unchanged
total shares of the user is inflated, if done correctly to have enough shares for the whole payout
user1 receives all the funds, leaving the vault empty
any other claims are reverted as there are no more funds
Pay out based on the snapshot of winning shares, not the live balance. Use userSharesToCountry[msg.sender][winnerCountryId] when computing the numerator, and zero it out after withdrawal to prevent double-claim.
Optionally, freeze share transfers after setWinner (or after eventStartDate) to reduce surface. If freezing transfers, override ERC20 transfer/transferFrom to revert while _setWinner is true.
Optional transfer freeze:
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.