Incomplete fee accounting during pool migration leads to protocol revenue loss
completeMigration resets lastRecordedStake to the new pool's stake without applying fees on rewards accrued during the unbonding period, so all rewards earned over the typical two-week migration window bypass the fee system.
Description
The LiquidStakingPool contract fails to properly account for rewards generated during the unbonding period of a pool migration. When a migration is initiated via changeStakingPool(), the contract unstakes all funds from the current pool and creates a pending migration. After the unbonding period, completeMigration() stakes the available funds in the new pool and resets lastRecordedStake to the current stake amount.
However, the contract does not apply fee calculations to rewards that accumulate during the unbonding period. This creates a gap in fee accounting where rewards generated during this period (typically 2 weeks) completely bypass the protocol's fee system.
The issue occurs because:
changeStakingPool()callsexchangeRate()to update fee accounting before unstakingcompleteMigration()resetslastRecordedStaketo the current stake in the new pool- No fee calculation is performed on rewards accrued during the unbonding period
For a protocol with significant TVL, this can result in substantial fee revenue loss with each migration. For example, with $10M TVL and 5% APY, approximately $1,923 in fee revenue would be lost during a single 2-week migration period (assuming 10% fee rate).
Impact
Protocol fee revenue is silently leaked during every pool migration. The longer the unbonding window, the larger the leak; for a 2-week unbonding period this can compound into thousands of dollars per migration on protocols with meaningful TVL.
Recommendation
Track Unbonding Rewards: Modify the completeMigration() function to track and account for rewards generated during the unbonding period:
function completeMigration() external onlyOwner {// Existing checks...stakingPool.safeClaimUnstakes();// Calculate rewards generated during unbondinguint256 currentBalance = matToken.balanceOf(address(this));uint256 reservedBalance = flashPool + totalPendingClaims;uint256 availableBalance = currentBalance > reservedBalance ?currentBalance - reservedBalance : 0;// If we received more than the original unstaked amount, the difference is rewardsif (availableBalance > pendingMigration.amount) {uint256 unbondingRewards = availableBalance - pendingMigration.amount;uint256 feeAmount = (unbondingRewards * lspFee) / 10000;accumulatedFees += feeAmount;}// Continue with existing migration logic...}

