joinEvent writes the caller’s current share balance to userSharesToCountry[msg.sender][countryId] and pushes the address onto usersAddress every time it is invoked (src/briVault.sol:260-266). If a user calls joinEvent twice before the tournament starts, the second snapshot can be zero (for example after transferring or burning shares). Later, cancelParticipation relies on balanceOf(msg.sender) when calling _burn (src/briVault.sol:280-288). With a stale snapshot the balance may be lower than the recorded stake, causing _burn to revert (ERC20: burn amount exceeds balance) and leaving the user unable to cancel or recover funds.
Likelihood: Low – requires a duplicate join before the event starts, typically due to user impatience or UI glitches.
Victims cannot cancel participation despite the event not starting, trapping funds in the vault.
A malicious actor could grief others by scripting duplicate joins for them via front-running (if UI auto-joins).
User deposits the minimum stake.
User calls joinEvent(team) twice.
User attempts to call cancelParticipation().
Transaction reverts with ERC20: burn amount exceeds balance; funds are stuck.
(Reproduced in test/BriVaultAttack.t.sol:293 via testCancelAfterDuplicateJoinCausesRevert.)
Reject duplicate joinEvent calls per address (require(userToCountry[msg.sender].length == 0, "already joined")) or overwrite rather than append.
Ensure withdrawal/cancel routines reconcile against actual live share balances (e.g., recompute snapshots or prevent zero-balance joins).
Add tests covering multi-join scenarios to confirm cancellation stays functional.
Proposed patch (Solidity-like pseudocode):
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.