BriVault

First Flight #52
Beginner FriendlySolidity
100 EXP
View results
Submission Details
Impact: high
Likelihood: high
Invalid

Centralization problem, leads to the owner manipulating the results for personal gain.

Centralization problem, leads to the owner manipulating the results for personal gain.

Description

  • The vault allows the contract owner to arbitrarily set the tournament winner via setWinner() (protected only by onlyOwner) without any checks or external randomness. Because the owner can also deposit and join a team, they can place a stake on a chosen team, wait until deposits close, and call setWinner() selecting their own team as the winner. This enables the owner to siphon the vault balance while ordinary participants lose, demonstrating a critical centralization and economic-manipulation risk.

function setWinner(uint256 countryIndex) public onlyOwner returns (string memory) {
if (block.timestamp <= eventEndDate) {
revert eventNotEnded();
}
require(countryIndex < teams.length, "Invalid country index");
if (_setWinner) {
revert WinnerAlreadySet();
}
winnerCountryId = countryIndex;
winner = teams[countryIndex];
_setWinner = true;
_getWinnerShares();
_setFinallizedVaultBalance();
emit WinnerSet (winner);
return winner;
}

Risk

Likelihood:

  • It's highly probable to happen by owner.

Impact:

  • Funds are directly at risk

Proof of Concept

Put this test in `briVault.t.sol`
<details>
<summary>POC</summary>
```javascript
function test_OwnerCanWinByChoosingOwnTeam() public {
// Owner sets the list of countries
vm.startPrank(owner);
briVault.setCountry(countries);
vm.stopPrank();
// Five users deposit 10 ETH each and choose different losing teams
address[5] memory users = [user1, user2, user3, user4, user5];
uint256 amount = 10 ether;
uint256[5] memory teamsIds = [uint256(0), 1, 2, 3, 4]; // all losing teams
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();
}
// Owner deposits 10 ETH and chooses a specific team (e.g., team #10)
vm.startPrank(owner);
mockToken.approve(address(briVault), amount);
briVault.deposit(amount, owner);
briVault.joinEvent(10);
vm.stopPrank();
// Simulate the end of the event and let the owner choose himself as the winner
vm.warp(eventEndDate + 1);
vm.startPrank(owner);
briVault.setWinner(10); // owner's team
vm.stopPrank();
// All users try to withdraw -> should fail because they lost
for (uint256 i = 0; i < users.length; i++) {
vm.startPrank(users[i]);
vm.expectRevert(abi.encodeWithSignature("didNotWin()"));
briVault.withdraw();
vm.stopPrank();
}
// Owner withdraws successfully since he picked himself as the winner
vm.startPrank(owner);
uint256 balanceBefore = mockToken.balanceOf(owner);
briVault.withdraw();
uint256 balanceAfter = mockToken.balanceOf(owner);
vm.stopPrank();
// Assert and log how much profit the owner made
uint256 profit = balanceAfter - balanceBefore;
assertGt(profit, 0, "Owner did not profit from self-declared win");
console.log("Owner bet on his own team and declared it the winner proving centralization risk.");
console.log("Owner profit after withdrawal:", profit);
}
```
</details>

Recommended Mitigation

1. Verifiable randomness: Use Chainlink VRF or an oracle to determine the winner fairly and transparently.
Updates

Appeal created

bube Lead Judge 19 days ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

The winner is set by the owner

This is owner action and the owner is assumed to be trusted and to provide correct input arguments.

Support

FAQs

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

Give us feedback!