F-2025-0010·missing-fee-collection

Incomplete fee accounting during pool migration leads to protocol revenue loss

Fixedliquid-stakinglststaking-poolsgithub.com/matchain/contracts
TL;DR

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.

Severity
MEDIUM
Impact
MEDIUM
Likelihood
MEDIUM
Method
MManual review
CAT.
Complexity
MEDIUM
Exploitability
LOW
02Section · Description

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:

  1. changeStakingPool() calls exchangeRate() to update fee accounting before unstaking
  2. completeMigration() resets lastRecordedStake to the current stake in the new pool
  3. 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).

03Section · Impact

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.

04Section · Recommendation

Recommendation

Track Unbonding Rewards: Modify the completeMigration() function to track and account for rewards generated during the unbonding period:

solidity
function completeMigration() external onlyOwner {
// Existing checks...
stakingPool.safeClaimUnstakes();
// Calculate rewards generated during unbonding
uint256 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 rewards
if (availableBalance > pendingMigration.amount) {
uint256 unbondingRewards = availableBalance - pendingMigration.amount;
uint256 feeAmount = (unbondingRewards * lspFee) / 10000;
accumulatedFees += feeAmount;
}
// Continue with existing migration logic...
}
F-2025-0010

oog
zealynx

Smart Contract Security Digest

Monthly exploit breakdowns, audit checklists, and DeFi security research — straight to your inbox

© 2026 Zealynx