BriVault

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

An attacker can get free shares using briVault.sol::cancelParticipation()

Root + Impact

An attacker can transfer 99% of his shares to another account and then trigger cancelParticipation() to get a total refund of his stake.
He would be refunded in total but only 1% of his shares would be burned. He can then get the 99% shares he transfered before, back to his account and use them later on, for free.

https://github.com/CodeHawks-Contests/2025-11-brivault/blob/main/src/briVault.sol#L280-L288

Description

  • Here stakedAsset[msg.sender] will be refunded but only shares = balanceOf(msg.sender) will be burned.

  • The normal behavior would have been to refund the exact corresponding shares held by the user account.

uint256 refundAmount = stakedAsset[msg.sender];
stakedAsset[msg.sender] = 0;
uint256 shares = balanceOf(msg.sender); //<@ shares held in the account
_burn(msg.sender, shares); //<@ only shares held in the account are burned
IERC20(asset()).safeTransfer(msg.sender, refundAmount); //<@ but the total staked amount is refunded

Risk

Likelihood: High

  • Easy to reproduce, simply transfer 99% of the shares to another account before calling cancelParticipation()

Impact: High

  • Get 99% of the shares for free


Proof of Concept

Step 1 : Transfer 99% of the shares to another account
Step 2 : Trigger cancelParticipation() to get refunded of the total amount staked
Step 3 : Transfer back the 99% of shares
Step 4 : Now the user have 99% of the previous shares for FREE.

Recommended Mitigation

function cancelParticipation () public {
if (block.timestamp >= eventStartDate){
revert eventStarted();
}
- uint256 refundAmount = stakedAsset[msg.sender];
stakedAsset[msg.sender] = 0;
uint256 shares = balanceOf(msg.sender);
+ uint256 refundAmount = _convertToAssets(shares);
_burn(msg.sender, shares);
IERC20(asset()).safeTransfer(msg.sender, refundAmount);
}
+ function _convertToAssets(uint256 shares) internal view returns (uint256 assets) {
+ uint256 balanceOfVault = IERC20(asset()).balanceOf(address(this));
+ uint256 totalShares = totalSupply();
+ if (totalShares == 0 || balanceOfVault == 0) {
+ return 0;
+ }
+ assets = Math.mulDiv(shares, balanceOfVault, totalShares);
+ }
Updates

Appeal created

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

Unrestricted ERC4626 functions

Support

FAQs

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

Give us feedback!