BriVault

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

The `totalParticipantShares` global variable which store the totalShare for the user is not updated after `cancelParticipation()` function is called. This can cause accounting inconsistency and logic flaw.


Description

The `cancelParticipation()` function fails to decrease the `totalParticipantShares` variable when a user cancels their participation. When users deposit and join an event, their share amount is added to `totalParticipantShares`. However, upon calling `cancelParticipation()` function while their tokens are refunded and shares are burned, the global total remains unchanged.
This causes `totalParticipantShares` to become inflated and no longer accurately represent the sum of all active participants’ shares.

Impact

The accounting logic becomes inconsistent, causing any reward distribution, or proportional calculation based on `totalParticipantShares` to be inaccurate. Since canceled users’ shares remain included in the total, active participants’ rewards may be diluted, leading to underpayment or potential misallocation of winnings over time.

Proof of Concept

/* The test shows four users deposited and joinEvent, but User1 decided to opt-out
of the competition by calling the `cancelParticipation()` function, upon existing
the competition, his token was burn and his token was burned and his stakedAsset
transferred to his address. The issue here is that his stakedAsset wasn't deducted
from the `totalParticipantShares` which is a big issue here and `totalParticipantShares`
wasn't updated.
*/
function test_cancelDoesNotUpdateTotalParticipantShares() public {
// totalShare before any user joins
uint256 totalSharesBeforeEntry = briVault.totalParticipantShares();
vm.startPrank(user1);
mockToken.approve(address(briVault), 5 ether);
uint256 user1shares = briVault.deposit(5 ether, user1);
briVault.joinEvent(10);
console.log("user1 shares", user1shares);
vm.stopPrank();
vm.startPrank(user2);
mockToken.approve(address(briVault), 5 ether);
uint256 user2shares = briVault.deposit(5 ether, user2);
briVault.joinEvent(20);
console.log("user2 shares", user2shares);
vm.stopPrank();
vm.startPrank(user3);
mockToken.approve(address(briVault), 5 ether);
uint256 user3shares = briVault.deposit(5 ether, user3);
briVault.joinEvent(30);
console.log("user3 shares", user3shares);
vm.stopPrank();
vm.startPrank(user4);
mockToken.approve(address(briVault), 5 ether);
uint256 user4shares = briVault.deposit(5 ether, user4);
briVault.joinEvent(40);
console.log("user4 shares", user4shares);
vm.stopPrank();
// Record totalParticipantShares before cancellation
uint256 totalSharesBeforeAnyUserCancelParticipation = briVault.totalParticipantShares();
// User1 cancels participation
vm.startPrank(user1);
briVault.cancelParticipation();
vm.stopPrank();
uint256 totalSharesAfterUser1CancelParticipation = briVault.totalParticipantShares();
console.log("Total Shares Before Entry:", totalSharesBeforeEntry);
console.log("Total Shares Before Any User Cancel Participation:", totalSharesBeforeAnyUserCancelParticipation);
console.log("Total Shares After User1 Cancel Participation:", totalSharesAfterUser1CancelParticipation);
assertEq(totalSharesBeforeAnyUserCancelParticipation, totalSharesAfterUser1CancelParticipation);
}

Recommended Mitigation

//Update the `totalParticipantShare` before executing external call,This line should be addeed to update/remove USER1 share from the global `totalParticipantShares`
//after he calls the `cancelParticipation()` function.
+ totalParticipantShares -= shares;
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!