Root + Impact
The root cause is the absence of a validation mechanism to verify whether a user has already joined, allowing repeated participation and incorrect incremention to storage variables.
Description
-
The protocol documentation states that "users should only join once." Even if this restriction is not strictly enforced, additional safeguards should be considered to maintain the protocol’s integrity and accuracy since it will be affected. 1 user joins 10 times, numberofparticipants will be 10 (even though is 1). totalPariticpantShares = 10 * user shares.
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:
Impact:
Proof of Concept
Random user deposits 0.5 ether, enough to be able to join events
User can now join an event as many times as he wants with no restriction artifically inflating numberOfParticipants and totalParticipantShares
function test_userCanEnterMultipleTimes() public {
vm.startPrank(owner);
briVault.setCountry(countries);
vm.stopPrank();
vm.startPrank(user1);
mockToken.approve(address(briVault), 0.5 ether);
uint256 shares =briVault.deposit(0.5 ether, user1);
briVault.joinEvent(10);
vm.stopPrank();
assertEq(1, briVault.numberOfParticipants());
assertEq(shares, briVault.totalParticipantShares());
vm.startPrank(user1);
for (uint256 i = 0; i < 10; i++) {
briVault.joinEvent(i);
}
vm.stopPrank();
assertEq(11, briVault.numberOfParticipants());
assertEq(shares + (shares * 10), briVault.totalParticipantShares());
Recommended Mitigation
1. Consider adding a mapping(address => bool) usersJoined;
function joinEvent(uint256 countryId) public {
if (stakedAsset[msg.sender] == 0) {
revert noDeposit();
}
+ if (usersJoined[msg.sender] == true) {
+ revert someCustomError()
}
// should have a check if user has joined already
// Ensure countryId is a valid index in the `teams` array
if (countryId >= teams.length) {
revert invalidCountry();
}
if (block.timestamp > eventStartDate) {
revert eventStarted();
}
+ usersJoined[msg.sender] = true;
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);
}