Fault stakedAsset Tracking in BriVault::deposit function Locks Part of User Assets
Description
In the deposit function, each time a user deposits, the contract updates stakedAsset[receiver] with the new assets, instead of accumulating it.As a result, the previous deposited funds are overwritten and no longer tracked.When a user makes a second deposit, the first deposit becomes effectively locked.If the user deposits a third time, both the first and second deposits are no longer accounted for.
if (minimumAmount + fee > assets) {
revert lowFeeAndAmount();
}
@> uint256 stakeAsset = assets - fee;
@> stakedAsset[receiver] = stakeAsset;
uint256 participantShares = _convertToShares(stakeAsset);
Risk
Likelihood:
When a user attempts to deposit more than once, the contract overwrites their previous staked amount instead of accumulating it.
Impact:
-
Impact 1:Users may be unable to withdraw the full amount they deposited, resulting in potential loss of funds.
-
Impact 2: Vault accounting becomes inconsistent, causing discrepancies between total staked assets and total shares issued.
Proof of Concept
The user approves 10 ETH for the BriVault contract.
The user makes two deposits of 5 ETH each.
The user calls cancelParticipation() to withdraw all previously deposited funds, excluding the participation fee.
Proof Of Code
Place the following code in briVault.t.sol.
function testCannotWithdrawAllTheDeposit() public {
vm.startPrank(user1);
console.log("user balance before withdraw:", mockToken.balanceOf(user1));
mockToken.approve(address(briVault), 10 ether);
briVault.deposit(5 ether, user1);
briVault.deposit(5 ether, user1);
uint256 participationFee = (5 ether * participationFeeBsp) / 10000;
briVault.cancelParticipation();
vm.stopPrank();
uint256 BalanceExpected = 20 ether - (participationFee*2);
uint256 BalanceAfterCancelParticipation = mockToken.balanceOf(user1);
assert(BalanceExpected > BalanceAfterCancelParticipation);
assertEq(BalanceAfterCancelParticipation, (15 ether-participationFee));
}
After calling cancelParticipation(), the user’s balance is expected to be 20 ETH, excluding participation fees for both deposits, but the actual balance is only 15 ETH, excluding the fee for the second deposit, because the first deposit was overwritten.
Recommended Mitigation
Each user can either deposit once or update their existing deposit.
function deposit(uint256 assets, address receiver) public override returns (uint256) {
require(receiver != address(0));
if (block.timestamp >= eventStartDate) {
revert eventStarted();
}
uint256 fee = _getParticipationFee(assets);
// charge on a percentage basis points
if (minimumAmount + fee > assets) {
revert lowFeeAndAmount();
}
- uint256 stakeAsset = assets - fee;
- stakedAsset[receiver] = stakeAsset;
+ uint256 currentStakedAsset = stakedAsset[receiver];
+ uint256 stakeAsset = assets - fee;
+ stakedAsset[receiver] = currentStakedAsset + stakeAsset;