Incorrect performance-fee calculation can lead to a denial-of-service condition
Description
The calculateTieredFee
function in the FeesHelper library calculates the performance fee based on the current price of 1e18 vault shares (shareValue
) and the last reached highest price (highWaterMark
). The function incorrectly calculates the fee by dividing by 10 ** underlayingDecimals
instead of the shareValue
.
function calculateTieredFee(
uint256 shareValue,
uint256 highWaterMark,
uint256 totalAssets,
VaultFees storage fees,
uint256 underlayingDecimals
) public view returns (uint256 fee) {
// [...]
fee = ((shareValue - highWaterMark) * totalAssets).mulDiv(
fees.performanceFee[i].fee, MAX_BASIS_POINTS * 10 ** underlayingDecimals, Math.Rounding.Floor
);
// [...]
}
}
This error can cause calculateTieredFee
to return a fee
value that is larger than the vault's totalAssets
. When this happens, the subtraction in _totalAssets - totalFee
within the takeFees
modifier will underflow and cause a revert.
modifier takeFees() {
if (!paused()) {
uint256 totalFee = accruedProtocolFee() + accruedPerformanceFee();
uint256 shareValue = convertToAssets(1e18);
uint256 _totalAssets = totalAssets();
if (shareValue > highWaterMark) highWaterMark = shareValue;
if (totalFee > 0 && _totalAssets > 0) {
uint256 supply = totalSupply();
uint256 feeInShare =
supply == 0 ? totalFee : totalFee.mulDiv(supply, _totalAssets - totalFee, Math.Rounding.Floor);
_mint(feeRecipient, feeInShare);
feesUpdatedAt = block.timestamp;
}
}
_;
}
Because the takeFees
modifier executes before key user operations — including deposit
, mint
, withdraw
, and redeem
— this vulnerability can place the vault into a permanent denial-of-service (DOS) state, blocking all core functionality.
Impact
An attacker can trigger this vulnerability by directly transferring assets into the vault, which inflates the shareValue
.
By inflating shareValue
sufficiently, an attacker can force calculateTieredFee
to return a fee
that exceeds the vault's total assets. This condition will cause the takeFees
modifier to revert consistently. As a result, all user-facing functions that use the modifier will be blocked, leading to a permanent DOS condition and freezing all user funds within the vault.
Recommendations
We recommend correcting the performance-fee calculation in calculateTieredFee
. The fee should be calculated relative to the shareValue
to ensure it is always proportional to the actual profit generated.
fee = ((shareValue - highWaterMark) * totalAssets).mulDiv(
- fees.performanceFee[i].fee, MAX_BASIS_POINTS * 10 ** underlayingDecimals, Math.Rounding.Floor
+ fees.performanceFee[i].fee, MAX_BASIS_POINTS * shareValue, Math.Rounding.Floor
);
Remediation
This issue has been acknowledged by Blueprint Finance, and a fix was implemented in commit c8e45fa4↗.