BriVault

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

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.

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:

  • It might occur under specific conditions. For example, all participants bet on losing teams


Impact:

  • Freezing of funds within the contract.

Proof of Concept

Place this test in `briVault.t.sol`
<details>
<summary>POC</summary>
```javascript
function test_WithdrawFails_WhenAllUsersBetOnLosingTeams() public {
// Owner sets the list of countries
vm.startPrank(owner);
briVault.setCountry(countries);
vm.stopPrank();
// Five users deposit 10 ETH each and choose different teams
address[5] memory users = [user1, user2, user3, user4, user5];
uint256 amount = 10 ether;
uint256[5] memory teamsIds = [uint256(0), 1, 2, 3, 4]; // five different countries
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();
}
// Simulate the end of the event and set a winner that no one chose (e.g., team #10)
vm.warp(eventEndDate + 1); // move time past event end
vm.startPrank(owner);
briVault.setWinner(10); // team 10 was not selected by any user
vm.stopPrank();
// Each user tries to withdraw -> should fail because they all lost
for (uint256 i = 0; i < users.length; i++) {
vm.startPrank(users[i]);
vm.expectRevert(abi.encodeWithSignature("didNotWin()"));
briVault.withdraw();
vm.stopPrank();
}
// Owner tries to withdraw -> should fail because only participants can withdraw
vm.startPrank(owner);
vm.expectRevert(); // any revert is enough to prove the owner cannot withdraw
briVault.withdraw();
vm.stopPrank();
// Log the successful logical outcome of the test
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);
}
```
Updates

Appeal created

bube Lead Judge 19 days ago
Submission Judgement Published
Validated
Assigned finding tags:

Division by Zero in Withdraw Function When No Winners Bet on Winning Team

When no one bet on the winning team, making totalWinnerShares = 0, causing division by zero in withdraw and preventing any withdrawals.

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.

Give us feedback!