Mishandling ether due to The lack of a mechanism for refunding money in case all participants bet on losing teams, This leads to the freezing of funds within the contract.
Description
-
The vault mishandles user funds when the owner sets a winning team that has no participants. Because setWinner() finalizes the vault without validating that totalWinnerShares > 0, the withdrawal mechanism (withdraw()) can never distribute assets. Every call to withdraw() reverts with didNotWin(), and even the owner cannot recover the locked tokens. This results in a permanent loss of access to deposited funds — a classic “locked funds” or mishandled Ether/tokens vulnerability.
function withdraw() external winnerSet {
if (block.timestamp < eventEndDate) {
revert eventNotEnded();
}
if (
keccak256(abi.encodePacked(userToCountry[msg.sender])) !=
keccak256(abi.encodePacked(winner))
) {
revert didNotWin();
}
uint256 shares = balanceOf(msg.sender);
uint256 vaultAsset = finalizedVaultAsset;
uint256 assetToWithdraw = Math.mulDiv(shares, vaultAsset, totalWinnerShares);
_burn(msg.sender, shares);
IERC20(asset()).safeTransfer(msg.sender, assetToWithdraw);
emit Withdraw(msg.sender, assetToWithdraw);
}
Risk
Likelihood:
Impact:
Proof of Concept
Place this test in `briVault.t.sol`
<details>
<summary>POC</summary>
```javascript
function test_WithdrawFails_WhenAllUsersBetOnLosingTeams() public {
vm.startPrank(owner);
briVault.setCountry(countries);
vm.stopPrank();
address[5] memory users = [user1, user2, user3, user4, user5];
uint256 amount = 10 ether;
uint256[5] memory teamsIds = [uint256(0), 1, 2, 3, 4];
for (uint256 i = 0; i < users.length; i++) {
vm.startPrank(users[i]);
mockToken.approve(address(briVault), amount);
briVault.deposit(amount, users[i]);
briVault.joinEvent(teamsIds[i]);
vm.stopPrank();
}
vm.warp(eventEndDate + 1);
vm.startPrank(owner);
briVault.setWinner(10);
vm.stopPrank();
for (uint256 i = 0; i < users.length; i++) {
vm.startPrank(users[i]);
vm.expectRevert(abi.encodeWithSignature("didNotWin()"));
briVault.withdraw();
vm.stopPrank();
}
vm.startPrank(owner);
vm.expectRevert();
briVault.withdraw();
vm.stopPrank();
console.log("All users bet on losing teams and could not withdraw. Owner also failed to withdraw.");
}
```
</details>
Recommended Mitigation
1. Add to it a `withdraw` function that enables participants to get their money back if there is no winner.
```diff
function withdraw() external winnerSet {
if (block.timestamp < eventEndDate) {
revert eventNotEnded();
}
+ // If there are no winners, allow all users to refund their deposited amount
+ if (totalWinnerShares == 0) {
+ uint256 refundAmount = stakedAsset[msg.sender];
+ require(refundAmount > 0, "No funds to refund");
+
+ stakedAsset[msg.sender] = 0;
+
+ uint256 shares = balanceOf(msg.sender);
+ _burn(msg.sender, shares);
+
+ IERC20(asset()).safeTransfer(msg.sender, refundAmount);
+
+ emit Withdraw(msg.sender, refundAmount);
+ return;
+ }
if (
keccak256(abi.encodePacked(userToCountry[msg.sender])) !=
keccak256(abi.encodePacked(winner))
) {
revert didNotWin();
}
uint256 shares = balanceOf(msg.sender);
uint256 vaultAsset = finalizedVaultAsset;
uint256 assetToWithdraw = Math.mulDiv(shares, vaultAsset, totalWinnerShares);
_burn(msg.sender, shares);
IERC20(asset()).safeTransfer(msg.sender, assetToWithdraw);
emit Withdraw(msg.sender, assetToWithdraw);
}
```