Severity: Medium
Files Affected
cadence/contracts/FlowALPv1.cdc
Description
The protocol implements a rate limiting mechanism intended to regenerate available deposit capacity over time up to a maximum limit, tracking both depositCapacity and depositCapacityCap. However, the math within regenerateDepositCapacity() incorrectly adds the newly generated capacity to the depositCapacityCap itself, instead of adding it to the current available capacity. The function then overwrites the available depositCapacity to equal this newly inflated cap. Furthermore, because the per-user limit is derived dynamically from this cap via getUserDepositLimitCap() (self.depositLimitFraction * self.depositCapacityCap), every user's personal allowance also grows endlessly every hour. This completely breaks the token-bucket rate-limiting model and allows unbounded pool growth. Exploit Scenario:
- Governance adds a supported token with a depositCapacityCap of 10,000 and a depositRate of 1,000 per hour.
- The pool experiences a period of low activity where no deposits are made for 24 hours.
- A user interacts with the pool, triggering regenerateDepositCapacity().
- The contract calculates the generated capacity (24 * 1,000 = 24,000) and incorrectly adds it to the cap, raising the depositCapacityCap from 10,000 to 34,000.
- The available depositCapacity is instantly reset to 34,000, and the userDepositLimitCap is drastically increased.
- Over weeks or months, the cap grows so large that rate-limiting is functionally disabled, allowing a single actor to instantly monopolize pool liquidity or execute massive flash-deposits.
Recommendation
Refactor the regenerateDepositCapacity() function to enforce a static cap (acting as the bucket size) and only increase the available depositCapacity (the contents of the bucket). Clamp the available capacity so it never exceeds the static cap.
Parent Issue: #209
Severity: Medium
Files Affected
cadence/contracts/FlowALPv1.cdcDescription
The protocol implements a rate limiting mechanism intended to regenerate available deposit capacity over time up to a maximum limit, tracking both depositCapacity and depositCapacityCap. However, the math within regenerateDepositCapacity() incorrectly adds the newly generated capacity to the depositCapacityCap itself, instead of adding it to the current available capacity. The function then overwrites the available depositCapacity to equal this newly inflated cap. Furthermore, because the per-user limit is derived dynamically from this cap via getUserDepositLimitCap() (self.depositLimitFraction * self.depositCapacityCap), every user's personal allowance also grows endlessly every hour. This completely breaks the token-bucket rate-limiting model and allows unbounded pool growth. Exploit Scenario:
Recommendation
Refactor the regenerateDepositCapacity() function to enforce a static cap (acting as the bucket size) and only increase the available depositCapacity (the contents of the bucket). Clamp the available capacity so it never exceeds the static cap.
Parent Issue: #209