BriVault

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

Duplicate joinEvent() inflates total shares and participants

Root

Normal behavior:
A user deposits tokens, receives vault shares, and calls joinEvent(countryId) once to register for their team.
After the event ends, the owner sets the winner, and the vault distributes funds proportionally to the shares of users who joined the winning team.

Specific issue:
joinEvent() doesn’t validate whether a user has already joined.
Each call pushes the same user again into usersAddress, increments numberOfParticipants, and adds to totalParticipantShares.
As a result, the same account can be counted multiple times and inflate its proportional rewards when _getWinnerShares() sums over usersAddress.

function joinEvent(uint256 countryId) public {
if (stakedAsset[msg.sender] == 0) { revert noDeposit(); }
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); // @> No duplicate check
numberOfParticipants++; // @> Always increments
totalParticipantShares += participantShares; // @> Always accumulates
emit joinedEvent(msg.sender, countryId);

}

Risk

Likelihood:

Can be triggered by any user calling joinEvent() multiple times before eventStartDate.

No access control or boolean flag prevents rejoining.

Cheap and repeatable within the same transaction or block.

Impact:

Artificially inflates totalWinnerShares and totalParticipantShares, breaking reward fairness.

May cause gas bloat or DoS if usersAddress becomes excessively large.

Results in fund misallocation during payout calculation.


Proof of Concept

Minimal Foundry PoCvm.startPrank(attacker);token.approve(address(vault), 1000 ether);uint256 shares = vault.deposit(10 ether, attacker);vault.joinEvent(0);vault.joinEvent(0);vault.joinEvent(0);vm.stopPrank();
vm.warp(block.timestamp + 3000);string[48] memory countries;countries[0] = "Argentina";vault.setCountry(countries);vault.setWinner(0);
// Expected results:// numberOfParticipants == 3// totalWinnerShares == shares * 3// totalParticipantShares == shares * 3
Explanation:The attacker joins three times, creating three identical entries in usersAddress._getWinnerShares() later sums them all, tripling the attacker’s proportional reward share

Mitigation

  • mapping(address => bool) private hasJoined;

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 (hasJoined[msg.sender]) {


  • revert AlreadyJoined(); // new custom error
  • }

    userToCountry[msg.sender] = teams[countryId];

    uint256 participantShares = balanceOf(msg.sender);
    userSharesToCountry[msg.sender][countryId] = participantShares;

    usersAddress.push(msg.sender);
    numberOfParticipants++;
    totalParticipantShares += participantShares;

  • hasJoined[msg.sender] = true;

    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!