BriVault

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

Users can join the event multiple times (duplicate entries inflate participant counters and shares)

Root + Impact

Description

  • Normally, each participant should be recorded exactly once when they join the event; the contract should only count a user as a participant the first time they join and should not allow re-joining to double-count shares or participants.

  • The joinEvent function currently pushes msg.sender into usersAddress and increments numberOfParticipants / totalParticipantShares every time it's called, without checking whether the user already joined. This allows repeated joinEvent calls to create duplicate entries and inflate counters used in winner-share calculations.

// Root cause in the codebase with @> marks to highlight the relevant section
pragma solidity ^0.8.24;
...
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); // user pushed every call (no duplicate check)
@> numberOfParticipants++;
@> totalParticipantShares += participantShares; // added again on every join
emit joinedEvent(msg.sender, countryId);
}

Risk

Likelihood:

  • Occurs whenever a user calls joinEvent() more than once (either intentionally or by accident), because the function lacks any guard to prevent re-joining.

  • Occurs whenever the front-end or integrator doesn't block duplicate joins, since the smart contract itself does not enforce uniqueness.

Impact:

  • Impact 1: Duplicate entries inflate numberOfParticipants and totalParticipantShares, causing incorrect aggregate accounting and wrong proportions when computing winner payouts (e.g., _getWinnerShares() sums over usersAddress and will double-count).

  • Impact 2: A malicious participant can repeatedly call joinEvent to manipulate totalParticipantShares or totalWinnerShares calculations, degrading rewards for honest winners or breaking payout logic.

Proof of Concept

Observed effect: After demoJoinTwice, usersAddress contains the same address twice; numberOfParticipants increased by 2; totalParticipantShares increased by participantShares twice — causing downstream calculations to be incorrect.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
contract PoC_DuplicateJoin {
BriVault public vault;
IERC20 public token;
constructor(BriVault _vault, IERC20 _token) {
vault = _vault;
token = _token;
}
function demoJoinTwice(uint256 amt, uint256 countryId) external {
// assume deposit already done and stakedAsset[msg.sender] > 0
// First join
vault.joinEvent(countryId);
// Second join (same user) — duplicates in usersAddress and increments counters again
vault.joinEvent(countryId);
}
}

Recommended Mitigation

Explanation: Ensure joinEvent only records a user once. Add a guard (hasJoined or check userToCountry) to prevent duplicate joins, and only increment participant counters and push to usersAddress on the first join. This preserves correct totals used in payout calculations.

- userToCountry[msg.sender] = teams[countryId];
-
- uint256 participantShares = balanceOf(msg.sender);
- userSharesToCountry[msg.sender][countryId] = participantShares;
- usersAddress.push(msg.sender);
- numberOfParticipants++;
- totalParticipantShares += participantShares;
+ // Prevent duplicate joins: user must not have joined before
+ require(bytes(userToCountry[msg.sender]).length == 0, "already joined");
+
+ userToCountry[msg.sender] = teams[countryId];
+
+ uint256 participantShares = balanceOf(msg.sender);
+ userSharesToCountry[msg.sender][countryId] = participantShares;
+
+ // Only push and increment counters on first join
+ usersAddress.push(msg.sender);
+ numberOfParticipants += 1;
+ totalParticipantShares += participantShares;
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!