FluidLocker::_getUnlockingPercentage() incorrectly divides one of the components of the formula by S, leading to always having 80% penalty
Summary
FluidLocker::_getUnlockingPercentage() calculates the percentage to unlock, which is the amount given to the user, while the remaining goes to other stakers. The formula incorrectly divides Math.sqrt(unlockPeriod * _SCALER) by SCALER:
function _getUnlockingPercentage(uint128 unlockPeriod) internal pure returns (uint256 unlockingPercentageBP) {
unlockingPercentageBP = (
_PERCENT_TO_BP
* (
((80 * _SCALER) / Math.sqrt(540 * _SCALER)) * (Math.sqrt(unlockPeriod * _SCALER) / _SCALER //@audit this _SCALER should not be here)
+ 20 * _SCALER
)
) / _SCALER;
}Root Cause
In FluidLocker:388, it incorrectly divides the term (Math.sqrt(unlockPeriod * _SCALER) by _SCALER.
Internal pre-conditions
None.
External pre-conditions
None.
Attack Path
- User unlocks their Fluid from the locker with a duration bigger than 0, unvesting it through the fountain.
Impact
User suffers a big loss, even if they unlock with the maximum period, they will still get 80% penalty.
PoC
The component (Math.sqrt(unlockPeriod _SCALER) / _SCALER) <= sqrt(540 24 3600 1e18) / 1e18 = 0 is always null, so only 20*_SCALER is left, which always yields a 20% unlocking percentage.
Mitigation
Remove the extra _SCALER.
function _getUnlockingPercentage(uint128 unlockPeriod) internal pure returns (uint256 unlockingPercentageBP) {
unlockingPercentageBP = (
_PERCENT_TO_BP
* (
((80 * _SCALER) / Math.sqrt(540 * _SCALER)) * (Math.sqrt(unlockPeriod * _SCALER))
+ 20 * _SCALER
)
) / _SCALER;
}