joinEvent records the caller’s current share balance and appends their address to usersAddress every time it is invoked (src/briVault.sol:260-264). cancelParticipation later zeroes stakedAsset but never removes the address or clears userSharesToCountry (src/briVault.sol:280-288). When the owner finalizes, _getWinnerShares iterates the entire array and adds all recorded shares (src/briVault.sol:191-197), including stale entries from users who already exited.
Likelihood: High – the attack uses only public functions and costs at most the participation fee.
Honest winners receive only pennies because the payout denominator explodes.
Vault retains most assets indefinitely, enabling griefing for minimal cost.
Attacker deposits once and repeatedly calls joinEvent(winningTeam) before the event starts.
Attacker calls cancelParticipation() to recover their stake (minus fee).
Honest bettors deposit and join the same team.
Owner finalizes with setWinner(winningTeam).
Honest winners call withdraw() and receive a trivial amount; vault balance remains high.
(Reproduced in test/BriVaultAttack.t.sol:112 via testPhantomSharesLockVault.)
Limit each address to a single joinEvent entry or overwrite the prior record instead of appending.
Clear usersAddress entries and userSharesToCountry values when participants cancel or exit.
Recompute winner allocations from live balances during withdrawal, or maintain accurate per-team registries.
Proposed patch (Solidity-like pseudocode):
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.