An user can join the event multiple times with the same staked assets in briVault::userAddress and the shares at the time his last join are added to the briVault::totalWinnerShares multiple times.
Description
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);
}
function _getWinnerShares() internal returns (uint256) {
for (uint256 i = 0; i < usersAddress.length; ++i) {
@> address user = usersAddress[i];
totalWinnerShares += userSharesToCountry[user][winnerCountryId];
}
return totalWinnerShares;
}
Risk
Likelihood: Medium
-
Reason 1: A user who does not know this issue may join the game many times and appear many times in the userAddress array.
-
Reason 2: An attacker can dilute the shares of the winner by joining multiple times, but it is not beneficial for him and it will cost him a lot of gas fee.
Impact: High
Proof of Concept
In this example, user1 was able to join 50000 times by depositing once.
function testJoin50000() public {
vm.pauseGasMetering();
vm.startPrank(owner);
briVault.setCountry(countries);
vm.stopPrank();
vm.startPrank(user1);
mockToken.approve(address(briVault), 5 ether);
briVault.deposit(5 ether, user1);
vm.stopPrank();
uint256 numberOfJoin = 50000;
for (uint256 i = 0; i < numberOfJoin; i++) {
vm.prank(user1);
briVault.joinEvent(10);
}
vm.warp(eventEndDate + 1);
vm.roll(block.number + 1);
assertEq(briVault.numberOfParticipants(), numberOfJoin);
}
Recommended Mitigation
Ensure that the stackedAsset of the user is updated when joining the game and check if the user has already joined the event before adding him to the userAddress array.
mapping(address=>bool) userJoined;
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);
+ uint256 participantShares = stakedAsset[msg.sender];
+ stakedAsset[msg.sender] = 0;
- userSharesToCountry[msg.sender][countryId] = participantShares;
+ userSharesToCountry[msg.sender][countryId] += participantShares;
+ if(!userJoined){
usersAddress.push(msg.sender);
numberOfParticipants++;
+}
totalParticipantShares += participantShares;
emit joinedEvent(msg.sender, countryId);
}