BriVault

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

A user can join more than once, breaking the protocol counter

A user is only supposed to join the event once, but he can join more than once, breaking the protocol counter and making their funds locked.

Description

  • user can join the event infinitely, picking whichever team they want

  • if they join the event more than once, but pick the same team, they win, but they also got their funds locked

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);
numberOfParticipants++;
totalParticipantShares += participantShares;
emit joinedEvent(msg.sender, countryId);
}

Risk: Locked funds

Likelihood: Low

  • There are no checks preventing a user from calling joinEvent() multiple times

  • Users can exploit this by calling the function repeatedly with different or same country IDs

Impact:

  • Users who join multiple times will have their funds permanently locked in the contract

  • The numberOfParticipants and totalParticipantShares counters becomes wrong, possibly breaking the future defi protocol that want to integrate this contract

Proof of Concept

add this to your follwoing test suite :

function test_singleUserCanJoinMultipleTeamsAndWithdraw2() public {
// Setup: Owner sets available countries
vm.prank(owner);
briVault.setCountry(countries);
vm.startPrank(user6);
// User6 deposits 1 ether into the vault
mockToken.mint(user6, 1 ether);
mockToken.approve(address(briVault), 1 ether);
briVault.deposit(1 ether, user6);
uint256 user6InitialShares = briVault.balanceOf(user6);
console.log("user6 initial shares:", user6InitialShares);
uint256 totalSharesBeforeJoining = briVault.totalParticipantShares();
uint256 briVaultBalance = mockToken.balanceOf(address(briVault));
console.log("briVault balance before joining:", briVaultBalance);
console.log("total participant shares before joining:", totalSharesBeforeJoining);
briVault.joinEvent(0);
briVault.joinEvent(0);
uint256 totalSharesAfterJoin = briVault.totalParticipantShares();
console.log("total participant shares after join:", totalSharesAfterJoin);
uint256 totalParticipants = briVault.numberOfParticipants();
uint256 totalSharesAfterAllJoins = briVault.totalParticipantShares();
console.log("number of participants:", totalParticipants);
console.log("total participant shares:", totalSharesAfterAllJoins);
vm.stopPrank();
// Fast forward to after event ends
vm.warp(eventEndDate + 1);
// Owner sets the winning team
vm.prank(owner);
briVault.setWinner(0);
// User6 withdraws their winnings
vm.prank(user6);
briVault.withdraw();
uint256 user6FinalBalance = mockToken.balanceOf(user6);
uint256 user6FinalShares = briVault.balanceOf(user6);
console.log("user6 final shares after withdraw:", user6FinalShares);
uint256 vaultFinalBalance = briVault.finalizedVaultAsset();
console.log("\n=== Final balances ===");
console.log("user6 final token balance:", user6FinalBalance);
console.log("vault final balance:", vaultFinalBalance);
assert(user6FinalBalance < briVaultBalance);
}

Recommended Mitigation

add checks for joinEvent

+ mapping(address => bool) userJoined;
+ error userAlreadyJoin();
///
function joinEvent(uint256 countryId) public {
if (stakedAsset[msg.sender] == 0) {
revert noDeposit();
}
+ if (userJoined(msg.sender) = true) {
+ revert userAlreadyJoin();
+ } else {
+ userJoined(msg.sender) = true;
+ }
// 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);
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!