function joinEvent(uint256 countryId) public {
userToCountry[msg.sender] = teams[countryId];
uint256 participantShares = balanceOf(msg.sender);
userSharesToCountry[msg.sender][countryId] = participantShares;
usersAddress.push(msg.sender);
numberOfParticipants++;
totalParticipantShares += participantShares;
}
function _getWinnerShares () internal returns (uint256) {
for (uint256 i = 0; i < usersAddress.length; ++i){
address user = usersAddress[i];
totalWinnerShares += userSharesToCountry[user][winnerCountryId];
}
return totalWinnerShares;
}
function test_duplicateEntriesVulnerability() public {
vm.startPrank(user1);
mockToken.approve(address(briVault), 5 ether);
uint256 user1shares = briVault.deposit(5 ether, user1);
briVault.joinEvent(10);
briVault.joinEvent(10);
briVault.joinEvent(10);
vm.stopPrank();
vm.startPrank(user2);
mockToken.approve(address(briVault), 5 ether);
uint256 user2shares = briVault.deposit(5 ether, user2);
briVault.joinEvent(10);
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(40);
vm.stopPrank();
assertEq(briVault.balanceOf(user1), user1shares);
assertEq(briVault.balanceOf(user2), user2shares);
assertEq(briVault.balanceOf(user3), user3shares);
assertEq(briVault.balanceOf(user4), user4shares);
vm.warp(eventEndDate + 1);
vm.prank(owner);
briVault.setWinner(10);
uint256 expectedWinnerShares = user1shares + user2shares;
uint256 actualWinnerShares = briVault.totalWinnerShares();
assertEq(
actualWinnerShares,
user1shares * 3 + user2shares,
"User1 counted 3 times! totalWinnerShares is inflated"
);
uint256 vaultBalance = briVault.finalizedVaultAsset();
uint256 user1ShouldGet = (user1shares * vaultBalance) / expectedWinnerShares;
uint256 user2ShouldGet = (user2shares * vaultBalance) / expectedWinnerShares;
uint256 user1WillGet = (user1shares * vaultBalance) / actualWinnerShares;
uint256 user2WillGet = (user2shares * vaultBalance) / actualWinnerShares;
uint256 totalLocked = (user1ShouldGet - user1WillGet) + (user2ShouldGet - user2WillGet);
assertLt(user1WillGet, user1ShouldGet, "User1 receives less due to inflated denominator");
assertLt(user2WillGet, user2ShouldGet, "User2 receives less due to inflated denominator");
console.log("expectedWinnerShares is: ", expectedWinnerShares);
console.log("actualWinnerShares is: ", actualWinnerShares);
console.log("user1ShouldGet is: ", user1ShouldGet);
console.log("user2ShouldGet is: ", user2ShouldGet);
console.log("user1WillGet is: ", user1WillGet);
console.log("user1WillGet is: ", user1WillGet);
}
Add a mapping to track whether users have already joined the event and validate against duplicate entries:
+ mapping(address => bool) public hasJoined;
function joinEvent(uint256 countryId) public {
if (stakedAsset[msg.sender] == 0) {
revert noDeposit();
}
+ if (hasJoined[msg.sender]) {
+ revert alreadyJoined();
+ }
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);
+ hasJoined[msg.sender] = true;
numberOfParticipants++;
totalParticipantShares += participantShares;
}
+ function cancelParticipation() public {
// ... existing code ...
+ hasJoined[msg.sender] = false;
}