Assessment reports>Concrete>High findings>Incorrect performance-fee calculation can lead to a denial-of-service condition
Category: Coding Mistakes

Incorrect performance-fee calculation can lead to a denial-of-service condition

High Impact
Critical Severity
Medium Likelihood

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.

Zellic © 2025Back to top ↑