Missing Share Balance Check Allows Users to Stay Registered Without Stake Burn
Normal behavior: Users can deposit assets and join an event, where their stake is tracked via stakedAsset, their shares are minted, and their participation is recorded in usersAddress and related mappings. When a user calls cancelParticipation(), their staked assets should be refunded, their shares burned, and they should be fully removed from the event’s participant tracking.
Issue: The cancelParticipation() function only refunds assets and burns shares but does not remove the user from participant arrays or mappings (usersAddress, userToCountry, userSharesToCountry, totalParticipantShares). This allows users to remain registered as active participants even after canceling, causing their shares to incorrectly influence winner calculations and event logic.
Likelihood:
Every time a user deposits and joins an event, then calls cancelParticipation() before the event starts, the participant remains in the usersAddress array and related mappings.
Any event with multiple participants has a high chance of accumulating “ghost participants” if multiple users cancel, because the function does not clean up logical participation state.
Impact:
Users who have canceled can still affect winner calculations, potentially skewing rewards or outcomes unfairly.
The event’s totalParticipantShares becomes inaccurate, undermining integrity of share-based logic and any dependent financial distributions.
User deposits tokens and joins an event successfully (joinEvent() emits joinedEvent).
The user then cancels their participation (cancelParticipation()), triggering refund of their deposit.
After cancelation:
stakedAsset(user) returns 0 (as expected).
totalParticipantShares remains unchanged (unexpected).
usersAddress still contains the user address.
Below is PoC test that confirms ghost participants after they cancel participation:
Remove user from participant list upon cancelation:
When cancelParticipation() executes, the user’s address should be removed from the usersAddress array or marked as inactive in a mapping such as isParticipant[user] = false.
Adjust total shares accurately:
Decrease totalParticipantShares by the user’s current shares before zeroing them.
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.