BriVault

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

Multiple JoinEvent() Calls Cause Underpayment in _getWinnerShares()

Multiple JoinEvent() Calls Cause Underpayment in _getWinnerShares()

Description

  • Expected behavior: After depositing funds and joining an event via joinEvent(), a user should not be able to call joinEvent() again unless they deposit more funds to gain additional shares.

  • Actual issue: A user can call joinEvent() multiple times after a single deposit, resulting in:

    • The same address being pushed multiple times into usersAddress[]

    • numberOfParticipants being artificially inflated

    • totalParticipantShares double-counting the user's shares

Location: joinEvent() function (lines 243-269)

function joinEvent(uint256 countryId) public {
@>// No check if user already joined
// ...
usersAddress.push(msg.sender); // Can push same address multiple times
numberOfParticipants++; // Increments even if user already joined
totalParticipantShares += participantShares; // Double counts shares
// ...
}

Risk

Likelihood: High – any user can exploit this after one deposit.

Impact:

  • Inflated participant count

  • Incorrect share calculations in _getWinnerShares()

  • Winners receive less than they should (due to inflated totalWinnerShares in withdrawal calculation)

  • Potential DoS if array grows too large

Proof of Concept

Example Attack:

  1. User deposits 10 ether, gets 9.85 shares

  2. User calls joinEvent(10) → added to array, totalParticipantShares += 9.85

  3. User calls joinEvent(10) again → added again, totalParticipantShares += 9.85 (now 19.7, but user only has 9.85 shares)

  4. If country 10 wins, _getWinnerShares() counts the user twice

function test_MultipleJoinEvent() public {
vm.startPrank(user1);
mockToken.approve(address(briVault), 10 ether);
briVault.deposit(10 ether, user1);
uint256 shares1 = briVault.balanceOf(user1);
console.log("Initial shares:", shares1);
// First join
briVault.joinEvent(7);
uint256 participants1 = briVault.numberOfParticipants();
uint256 totalShares1 = briVault.totalParticipantShares();
console.log("After first join - participants:", participants1, "totalShares:", totalShares1);
// Second join (should not be allowed but is)
briVault.joinEvent(10);
uint256 participants2 = briVault.numberOfParticipants();
uint256 totalShares2 = briVault.totalParticipantShares();
console.log("After second join - participants:", participants2, "totalShares:", totalShares2);
// Counts are inflated
assertEq(participants3, participants1 + 2, "Participant count increased");
assertEq(totalShares3, totalShares1 + shares1 + shares1, "Total shares double-counted");
vm.stopPrank();
}

Recommended Mitigation

Prevent multiple joins per user using a boolean flag:

mapping(address => bool) public hasJoined;
function joinEvent(uint256 countryId) public {
if (stakedAsset[msg.sender] == 0) {
revert noDeposit();
}
if (hasJoined[msg.sender]) {
revert alreadyJoined();
}
// ... rest of function ...
hasJoined[msg.sender] = true;
usersAddress.push(msg.sender);
// ...
}
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!