BriVault

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

Zero risk investment

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.

// Root cause in the codebase with @> marks to highlight the relevant section
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.
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);
}
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!