BriVault

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

The protocol allows a user to deposit funds without joining the event

Description

The protocol allows users to deposit assets into the vault. After depositing, they can call joinEvent to select a team to represent in the event. If their team wins, they can withdraw assets from the vault and receive an amount proportional to their shares compared to other teammates.

However, a user can deposit funds without ever calling joinEvent. If this occurs, they will not have the opportunity to be part of a winning team and will effectively have no chance of earning a return on their stake. This issue becomes more problematic as the eventStartDate approaches, since a user can deposit assets shortly before the event starts and lose the opportunity to join once it begins.

The core issue lies in the separation of deposit and joinEvent, which allows users to stake assets without ensuring they are assigned to a team.

Risk

Likelihood:

This will occur whenever a user deposits assets but does not subsequently call joinEvent to select a team. It is more likely to happen when block.timestamp is close to eventStartDate, because once the event starts, users are prevented from joining a team.

Impact:

A user who deposits assets without joining a team will have staked funds with no chance of a return. Their assets will be distributed to players on the winning team and will become inaccessible after the event starts.

Proof of Concept

Add this test to the test suite in test/briVault.t.sol.

function testAllowsDepositWithoutJoiningEvent() public {
uint256 depositAmount = 10e18;
uint256 briVaultBase = 10000;
uint256 user1StartAmount = mockToken.balanceOf(user1);
vm.warp(eventStartDate - 1);
vm.startPrank(user1);
mockToken.approve(address(briVault), depositAmount);
briVault.deposit(depositAmount, user1);
assert(mockToken.balanceOf(user1) == user1StartAmount - depositAmount);
assert(briVault.stakedAsset(user1) == depositAmount - (depositAmount * participationFeeBsp) / briVaultBase);
vm.warp(eventStartDate + 1);
vm.expectRevert(BriVault.eventStarted.selector);
briVault.joinEvent(0);
vm.expectRevert(BriVault.eventStarted.selector);
briVault.cancelParticipation();
vm.stopPrank();
assertEq(
keccak256(bytes(briVault.userToCountry(user1))),
keccak256(bytes(""))
);
}

“This test will show that a user can deposit but, once the event starts, cannot join a team or cancel participation. It also confirms that the user has not been assigned a country, meaning they have no chance to win.

Recommended Mitigation

There are a few ways to mitigate this issue.

One approach is to add a function allowing users who have not joined a team to reclaim their deposits before the event ends.

+ function getDepositBack() external {
+ if(block.timestamp >= eventEndDate) {
+ revert();
+ }
+ if(bytes(userToCountry(msg.sender)).length != 0) {
+ revert();
+ }
+ uint256 refundAmount = stakedAsset[msg.sender];
+ if(refundAmount == 0) {
+ revert();
+ }
+ stakedAsset[msg.sender] = 0;
+ uint256 shares = balanceOf(msg.sender);
+ _burn(msg.sender, shares);
+ IERC20(asset()).safeTransfer(msg.sender, refundAmount);
+ }

This allows users to recover their deposited funds if they are not on a team, though they would still lose the participation fee. However, this only works before the event ends.

A better solution would be to combine the logic of deposit and joinEvent, ensuring users cannot deposit without selecting a team.

- function deposit(uint256 assets, address receiver) public override returns (uint256) {
+ function deposit(uint256 assets, address receiver, uint256 countryId) public override returns (uint256) {
...
+ joinEvent(countryId);
return participantShares;
}
- function joinEvent(uint256 countryId) public {
+ function joinEvent(uint256 countryId) internal {
...
}

Note that this solution would require updated logic to handle users who deposit multiple times and users who want to switch teams before the event starts.

Updates

Appeal created

bube Lead Judge 20 days ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
darkfox Submitter
20 days ago
bube Lead Judge
16 days ago
bube Lead Judge 15 days ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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

Give us feedback!