BriVault

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

`Function:cancelParticipation` only `_burn` shares, but the others related to shares are left unchanged

[H-5] Function:cancelParticipation only _burn shares, but the others related to shares are left unchanged

Description

In the Function:cancelParticipation user only _burn shares:

function cancelParticipation () public {
if (block.timestamp >= eventStartDate){
revert eventStarted();
}
uint256 refundAmount = stakedAsset[msg.sender];
stakedAsset[msg.sender] = 0;
uint256 shares = balanceOf(msg.sender);
@> _burn(msg.sender, shares);
IERC20(asset()).safeTransfer(msg.sender, refundAmount);
}

But if this user has joined event, Function:joinEvent update some variables related to his shares:

function joinEvent(uint256 countryId) public {
......
userToCountry[msg.sender] = teams[countryId];
uint256 participantShares = balanceOf(msg.sender);
@> userSharesToCountry[msg.sender][countryId] = participantShares;
usersAddress.push(msg.sender);
numberOfParticipants++;
@> totalParticipantShares += participantShares;
emit joinedEvent(msg.sender, countryId);
}

Impact

Assume that userA deposits some assets and attains some share,
then userA join event, if he choose countryId 0, userSharesToCountry[userA][0] = userA's shares,
after that userA cancelParticipation, his balance in briVault is burned,
but userSharesToCountry[userA][0] isn't set 0, and totalParticipantShares is also not set correctly.

Proof of Concepts

Run the test function below in test/briVault.t.sol:

/**
*Scenario:
-user1: deposit and joinEvent
-user2: deposit, joinEvent and cancelParticipation
*Expected:
-user1 withdraw IERC20(address(mockToken)).balanceOf(address(briVault)) back
*Actual:
-user1 only withdraw half of IERC20(address(mockToken)).balanceOf(address(briVault))
*/
function test_cancelParticipation_incorrect_shares_update() public {
uint256 expected_assetToWithdraw;
vm.startPrank(user1);
mockToken.approve(address(briVault), 5 ether);
briVault.deposit(5 ether, user1);
briVault.joinEvent(0);
vm.stopPrank();
vm.startPrank(user2);
mockToken.approve(address(briVault), 5 ether);
briVault.deposit(5 ether, user2);
briVault.joinEvent(0);
briVault.cancelParticipation();
vm.stopPrank();
vm.warp(eventEndDate + 1);
vm.startPrank(owner);
briVault.setWinner(0);
expected_assetToWithdraw = IERC20(address(mockToken)).balanceOf(address(briVault));
vm.expectEmit(address(briVault));
emit BriVault.Withdraw(user1, expected_assetToWithdraw);
vm.startPrank(user1);
briVault.withdraw();
vm.stopPrank();
}

Recommended mitigation

function cancelParticipation () public {
if (block.timestamp >= eventStartDate){
revert eventStarted();
}
//q: maybe need some checks?
uint256 refundAmount = stakedAsset[msg.sender];
stakedAsset[msg.sender] = 0;
uint256 shares = balanceOf(msg.sender);
+ totalParticipantShares -= shares;
+ for (uint256 i = 0; i < teams.length; ++i) {
+ userSharesToCountry[msg.sender][i] = 0;
+ }
_burn(msg.sender, shares);
IERC20(asset()).safeTransfer(msg.sender, refundAmount);
}
Updates

Appeal created

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

`cancelParticipation` Leaves Stale Winner Data

CancelParticipation burns shares but leaves the address inside usersAddress and keeps userSharesToCountry populated.

Support

FAQs

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

Give us feedback!