BriVault

First Flight #52
Beginner FriendlySolidity
100 EXP
View results
Submission Details
Severity: high
Valid

JoinEvent allows duplicates and double-counts shares in totalWinnerShares

Duplicate Registrations in joinEvent() Inflate Participant Counters and Corrupt Share Accounting

Description

  • The joinEvent() function does not restrict a user from joining multiple times before eventStartDate.
    Each call appends the user’s address to usersAddress, increments numberOfParticipants, and adds balanceOf(msg.sender) to totalParticipantShares, regardless of whether the user was already registered.

function joinEvent(uint256 countryId) public {
if (stakedAsset[msg.sender] == 0) {
revert noDeposit();
}
// Ensure countryId is a valid index in the `teams` array
if (countryId >= teams.length) {
revert invalidCountry();
}
if (block.timestamp > eventStartDate) {
revert eventStarted();
}
userToCountry[msg.sender] = teams[countryId];
//@> uint256 participantShares = balanceOf(msg.sender);
//@> userSharesToCountry[msg.sender][countryId] = participantShares;
//@> usersAddress.push(msg.sender);
//@> numberOfParticipants++;
//@> totalParticipantShares += participantShares;
emit joinedEvent(msg.sender, countryId);
}

Because there is no check to prevent duplicates, a single user can call joinEvent() repeatedly, causing inflated participant statistics and distorting the pool’s internal accounting.

When _getWinnerShares() is executed later, it loops through usersAddress and adds up each entry’s shares.

This means duplicate entries for the same address will cause the same user’s shares to be counted multiple times, inflating totalWinnerShares and reducing the payout for legitimate winners.

Risk

Likelihood:

  • Can be triggered easily by any participant before eventStartDate without restrictions.

Impact:

  • Manipulates reward distribution ratios and corrupts event statistics. Winners receive less than their fair payout, and funds remain stuck in the vault.

Proof of Concept

A user deposits and joins a country.

  • Before the event starts, they repeatedly call joinEvent(countryId) multiple times.

  • Each call adds their address to usersAddress and increases totalParticipantShares.

  • When the event ends, _getWinnerShares() iterates over all entries in usersAddress, counting the same user’s shares multiple times.

  • This inflates totalWinnerShares, diluting all payouts proportionally.

Paste the following test to briVault.t.sol

function test_joinEvent_AllowsDuplicates() public {
vm.startPrank(owner);
briVault.setCountry(countries);
vm.stopPrank();
vm.startPrank(user1);
mockToken.approve(address(briVault), 5 ether);
briVault.deposit(5 ether, user1);
// Join the same event multiple times
briVault.joinEvent(0);
briVault.joinEvent(0);
briVault.joinEvent(0);
vm.stopPrank();
// Duplicate entries inflate counts
assertEq(briVault.numberOfParticipants(), 3);
// Same user address appears multiple times
// and totalParticipantShares increased 3x for the same balance
}

Recommended Mitigation

function joinEvent(uint256 countryId) public {
if (stakedAsset[msg.sender] == 0) revert noDeposit();
if (countryId >= teams.length) revert invalidCountry();
if (block.timestamp > eventStartDate) revert eventStarted();
+ if (bytes(userToCountry[msg.sender]).length != 0) revert alreadyJoined();
userToCountry[msg.sender] = teams[countryId];
uint256 participantShares = balanceOf(msg.sender);
userSharesToCountry[msg.sender][countryId] = participantShares;
usersAddress.push(msg.sender);
numberOfParticipants++;
totalParticipantShares += participantShares;
emit joinedEvent(msg.sender, countryId);
}
Updates

Appeal created

bube Lead Judge 19 days ago
Submission Judgement Published
Validated
Assigned finding tags:

Duplicate registration through `joinEvent`

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.

Give us feedback!