Management-fee calculation results in lower effective rate
Description
The BasketToken._harvestManagementFee()
mints new shares as a fee instead of transferring an existing token amount. This approach causes the effective fee rate to be lower than the intended rate due to dilution effects.
function _harvestManagementFee(uint16 feeBps, address feeCollector) internal {
// Checks
if (feeBps > _MAX_MANAGEMENT_FEE) {
revert InvalidManagementFee();
}
uint256 timeSinceLastHarvest = block.timestamp - lastManagementFeeHarvestTimestamp;
// Effects
lastManagementFeeHarvestTimestamp = uint40(block.timestamp);
if (feeBps != 0) {
if (timeSinceLastHarvest != 0) {
if (timeSinceLastHarvest != block.timestamp) {
// remove shares held by the treasury or currently pending redemption from calculation
uint256 currentTotalSupply = totalSupply() - balanceOf(feeCollector)
- pendingRedeemRequest(lastRedeemRequestId[feeCollector], feeCollector);
uint256 fee = FixedPointMathLib.fullMulDiv(
currentTotalSupply, feeBps * timeSinceLastHarvest, _MANAGEMENT_FEE_DECIMALS * uint256(365 days)
);
if (fee != 0) {
emit ManagementFeeHarvested(fee);
_mint(feeCollector, fee);
// Interactions
FeeCollector(feeCollector).notifyHarvestFee(fee);
}
}
}
}
}
Take the following example.
If the total supply is 100 and the fee rate is 5%, the protocol mints five shares as the fee. However, after minting, the total supply becomes 105, making the effective fee rate 5/105 = 4.7%, which is less than the intended 5%. This discrepancy can result in the protocol consistently collecting a lower fee amount than expected.
Impact
The current fee-calculation method leads to undercollected fees, causing financial loss to the protocol and underperformance of fee collection for the protocol.
Recommendations
Adjust the calculation logic in _harvestManagementFee()
to mint a higher amount of shares to match the intended fee rate after accounting for the dilution effect. It can use the formula (total_supply * fee_rate) / (fee_precision - fee_rate)
.
Remediation
This issue has been acknowledged by Storm Labs, and a fix was implemented in commit 1a539a7f↗.