BriVault

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

BriVault::joinEvent can be called more than once by the same user, potentially causing denial of service.

Root + Impact

Description

  • The BriVault contract requires users to deposit assets via deposit to become eligible for tournament participation, then call joinEvent once before eventStartDate to select a country, which registers their shares to totalParticipantShares and numberOfParticipants for accurate reward distribution among winners via withdraw.

  • However, joinEvent lacks a check to prevent multiple calls by the same user, allowing repeated invocations that inflate totalParticipantShares and numberOfParticipants without adding new participants, leading to incorrect calculations and reduced rewards for legitimate winners during distribution.

// Root cause in the codebase with @> marks to highlight the relevant section
@> 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);
numberOfParticipants++;
totalParticipantShares += participantShares;
emit joinedEvent(msg.sender, countryId);
}

Risk

Likelihood:

  • Users or malicious actors call joinEvent multiple times during the pre-event joining window.

  • External frontends or scripts erroneously trigger repeated calls due to retry logic or bugs.

Impact:

  • Winning pool calculations are skewed, resulting in under-distribution of rewards and potential losses for legitimate participants.

  • Denial of service to the protocol's core functionality, eroding user trust and enabling griefing attacks that disrupt tournament fairness.

Proof of Concept

Add the following code snippet to the briVault.t.sol test file.This test verifies that user can join multiple times by calling ERC4626::joinEvent disrupting winning pool calculation.

function test_userCanJoinMoreThanOnceCausesDOS() public {
vm.startPrank(owner);
briVault.setCountry(countries);
vm.stopPrank();
vm.startPrank(user1);
mockToken.approve(address(briVault), 5 ether);
uint256 user1Shares = briVault.deposit(5 ether, user1);
for (uint256 i = 0; i < 3; i++) {
briVault.joinEvent(10);
}
ERC4626(briVault).withdraw(ERC4626(briVault).balanceOf(user1), user1, user1);
uint256 numberOfParticipants = briVault.numberOfParticipants();
uint256 totalParticipantShares = briVault.totalParticipantShares();
vm.stopPrank();
vm.startPrank(owner);
vm.warp(eventEndDate + 1);
briVault.setWinner(10);
vm.stopPrank();
uint256 balanceBeforeUser1 = mockToken.balanceOf(user1);
uint256 totalAssets = briVault.totalAssets();
console.log("balanceBeforeUser1", balanceBeforeUser1);
vm.prank(user1);
briVault.withdraw();
uint256 totalAssetAfterWithdrawl = briVault.totalAssets();
console.log("total assets left in the vault: ", totalAssetAfterWithdrawl);
uint256 curentBalance = mockToken.balanceOf(user1);
uint256 expectedBalance = balanceBeforeUser1 + totalAssets;
assertEq(curentBalance, expectedBalance, "Must be equal");
assertEq(totalAssetAfterWithdrawl, 0, "All funds withdrawn as there is only one participant");
}

Recommended Mitigation

Potential mitigation is to add a user already joined check and custom error that would prevent totalParticipantShares state variable change.

...
+ error alreadyJoined();
...
function joinEvent(uint256 countryId) public {
+ if (bytes(userToCountry[msg.sender]).length > 0) {
+ revert alreadyJoined();
+ }
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);
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!