Severity: High
Files Affected
cadence/contracts/FlowALPv1.cdc
Description
The protocol calculates insurance and stability fees as a percentage of the debitIncome within updateInterestRates(). This fee is mathematically deducted from the interest distributed to lenders via the currentCreditRate, implicitly leaving those tokens in the pool's reserves to be claimed by the protocol. However, during collectInsurance() and collectStability(), if the pool's reserveVault lacks sufficient liquidity to cover the accrued fee, the withdrawal is strictly capped at the available balance. The contract then unconditionally updates the lastInsuranceCollectionTime and lastStabilityFeeCollectionTime to the current block timestamp. Because the uncollected remainder is not tracked, it is permanently erased. This not only results in a direct loss of protocol revenue but also corrupts the internal accounting; when borrowers eventually repay loans and replenish the reserves, those uncollected fee tokens become permanently trapped as unallocated surplus. Exploit Scenario:
- A supported token experiences extremely high utilization, leaving the pool's reserveVault with near-zero available liquidity.
- The protocol accrues significant debt income over a month, automatically deducting a 5% insurance fee from the lenders' credit rate allocation.
- A user interaction triggers updateInterestRatesAndCollectInsurance(), which correctly calculates that 1,000 tokens are owed to the insurance fund.
- Because the reserveVault currently only holds 10 tokens, the collectInsurance() function withdraws the 10 tokens, abandons the remaining 990 tokens, and resets the collection timestamp to the current time.
- The 990 tokens are permanently lost to the protocol; when borrowers eventually repay their loans and replenish the vault, those 990 tokens will remain trapped in the reserves without belonging to either the protocol or the lenders.
Recommendation
Introduce pendingInsuranceFee and pendingStabilityFee state variables within the TokenState struct to properly track accrued but uncollected fees. During collection, add the newly calculated fee to the pending tracker, attempt to withdraw as much of the pending fee as the reserveVault allows, and strictly subtract only the successfully withdrawn amount from the tracker.
Parent Issue: #209
Severity: High
Files Affected
cadence/contracts/FlowALPv1.cdcDescription
The protocol calculates insurance and stability fees as a percentage of the debitIncome within updateInterestRates(). This fee is mathematically deducted from the interest distributed to lenders via the currentCreditRate, implicitly leaving those tokens in the pool's reserves to be claimed by the protocol. However, during collectInsurance() and collectStability(), if the pool's reserveVault lacks sufficient liquidity to cover the accrued fee, the withdrawal is strictly capped at the available balance. The contract then unconditionally updates the lastInsuranceCollectionTime and lastStabilityFeeCollectionTime to the current block timestamp. Because the uncollected remainder is not tracked, it is permanently erased. This not only results in a direct loss of protocol revenue but also corrupts the internal accounting; when borrowers eventually repay loans and replenish the reserves, those uncollected fee tokens become permanently trapped as unallocated surplus. Exploit Scenario:
Recommendation
Introduce pendingInsuranceFee and pendingStabilityFee state variables within the TokenState struct to properly track accrued but uncollected fees. During collection, add the newly calculated fee to the pending tracker, attempt to withdraw as much of the pending fee as the reserveVault allows, and strictly subtract only the successfully withdrawn amount from the tracker.
Parent Issue: #209