BriVault

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

Arbitrary Winner Selection (Centralized Oracle Risk)

Root + Impact

Description

  • i don't think there's any normal behavior because the owner can always, in every situation choose anyone they favour.

  • The `briVault::setWinner()` function allows the owner to choose any `countryIndex` after the event ends, with no external verification.

    This gives full control over the final outcome to the contract owner, allowing them to select any team as the winner even if that team didn’t actually win.

    This completely breaks the trustless premise of the vault and enables fund theft, since all funds from losers are redistributed to the “winning” users chosen by the owner.

`briVault::setWinner()`

Risk

Likelihood:

Every time an event ends and the owner picks the winner

Impact:

The owner can:

1. Assign themselves or any preferred address to a winning team.

2. Trigger _getWinnerShares() and _setFinallizedVaultBalance() to finalize vault state.

3. Withdraw all funds using the normal winner logic.

Effectively, the owner can drain the vault post-event.


Proof of Concept


Steps to Reproduce:

1. Deploy the vault with multiple users depositing and joining different teams.

2. After the eventEndDate, call setWinner() as the owner, choosing the index of a team that contains only your own address or address of someone that you favour.

3. Call withdraw() to drain all vault assets.

function test_owner_can_force_winner_and_withdraw() public {
// alice deposits and joins team 1
vm.prank(alice);
mockToken.approve(address(briVault), 5 ether);
vm.prank(alice);
briVault.deposit(5 ether, alice);
vm.prank(alice);
briVault.joinEvent(0);
// bob deposits and joins team 2
vm.prank(bob);
mockToken.approve(address(briVault), 3 ether);
vm.prank(bob);
briVault.deposit(3 ether, bob);
vm.prank(bob);
briVault.joinEvent(1);
// Fast forward to after event end
vm.warp(block.timestamp + 60 days);
vm.roll(block.number + 1);
// Owner chooses team 1 at 0 index even if he shouldn't — choose a team that contains only alice
vm.prank(owner);
briVault.setWinner(0);
// Owner is not a participant in team 1 - but if owner is participant he could withdraw huge
// Bob should revert when withdrawing
vm.expectRevert(BriVault.didNotWin.selector);
vm.prank(bob);
briVault.withdraw();
}

Recommended Mitigation

Integrate a verifiable randomness or oracle-based winner determination system (e.g., Chainlink VRF).

If results are off-chain, use a multi-sig-controlled oracle or community vote.

Or delegate the authority to a trusted oracle contract.

- function setWinner(uint256 countryIndex) public onlyOwner returns (string memory) {
+ function setWinner(uint256 countryIndex, bytes calldata oracleProof)
+ external onlyOwner returns (string memory) {
+ require(verifyOracleResult(oracleProof, countryIndex), "Invalid winner proof");
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!