BriVault

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

CRITICAL-01: Multiple joinEvent Calls Allow Share Manipulation

Root + Impact

Users can call joinEvent() multiple times without restrictions, artificially inflating their share count and diluting rewards for other winners. This allows attackers to steal funds from legitimate winners.

Description

Normal behavior expects users to call joinEvent() once after depositing to select their team. The function should track that a user has already joined and prevent duplicate entries.

The current implementation has no check to prevent multiple joinEvent() calls. Each call increments numberOfParticipants, adds the user address to usersAddress[] again, and accumulates totalParticipantShares repeatedly with the same user's shares.

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;
// @> No check to prevent calling this function multiple times
// @> usersAddress.push(msg.sender) can add same user repeatedly
usersAddress.push(msg.sender);
// @> numberOfParticipants increments each call
numberOfParticipants++;
// @> totalParticipantShares accumulates same shares multiple times
totalParticipantShares += participantShares;
emit joinedEvent(msg.sender, countryId);
}

Risk

Likelihood:

  • User can call joinEvent() immediately after first call, with no on-chain restrictions preventing it

  • The function only checks for deposit existence and valid countryId, both of which remain true on subsequent calls

  • Attack can be executed before event starts, with zero additional cost beyond gas

Impact:

  • Attacker's shares are counted multiple times in totalParticipantShares, artificially inflating the denominator

  • When _getWinnerShares() executes in setWinner(), attacker's shares are added multiple times to totalWinnerShares

  • During withdraw(), each winner's payout = (userShares * finalizedVaultAsset) / totalWinnerShares

  • Inflated totalWinnerShares means all winners receive proportionally less

  • Attacker steals value from all other winning participants

  • usersAddress[] array fills with duplicates, wasting gas and potentially causing DOS

Proof of Concept

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "forge-std/Test.sol";
contract ExploitTest is Test {
function testMultipleJoinEventExploit() public {
// Deploy contracts
// Both users deposit same amount (1000 tokens each)
// User1 joins once normally
vault.joinEvent(0);
// Attacker joins and then calls joinEvent 10 more times
vault.joinEvent(0);
for(uint i = 0; i < 10; i++) {
vault.joinEvent(0); // Called 11 times total
}
// Check state:
// numberOfParticipants = 12 (should be 2)
// totalParticipantShares = inflated by 10x attacker's shares
// usersAddress.length = 12 (attacker listed 11 times)
// After winner is set and withdrawals:
// Attacker receives much more than fair share
// Victim receives much less than deserved
}
}

Recommended Mitigation

function joinEvent(uint256 countryId) public {
if (stakedAsset[msg.sender] == 0) {
revert noDeposit();
}
+ // Prevent multiple joins by checking if user already selected a team
+ if (bytes(userToCountry[msg.sender]).length != 0) {
+ revert("Already joined event");
+ }
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);
}
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!