<!-- canonical: https://0xsimao.com/findings/superfluid-locker-system-unlocking-components-formula-penalty -->

# `FluidLocker::_getUnlockingPercentage()` incorrectly divides one of the components of the formula by `S`, leading to always having `80%` penalty

Crit/High · Sherlock · Streaming · 20th November 2024

Finding H-1 of the Superfluid Locker System competition.

- Protocol: https://audits.sherlock.xyz/contests/648
- Report: /reports/superfluid-locker-system
- Codebase: https://github.com/0xsimao/2024-11-superfluid-locking-contract/tree/1fa5f86024be5f269e1a0898b1f939f1d4cce149
- Source: https://github.com/sherlock-audit/2024-11-superfluid-locking-contract-judging/issues/20

---

### Summary

[FluidLocker::_getUnlockingPercentage()](https://github.com/sherlock-audit/2024-11-superfluid-locking-contract/blob/main/fluid/packages/contracts/src/FluidLocker.sol#L384) 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`:
```solidity
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

1. 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`.
```solidity
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;
}
```

---

Related findings:

- [RsaVerifyOptimized::pkcs1Sha256() modified the original code incorrectly in one instance](https://0xsimao.com/findings/keyring-ii-pkcs1-sha256-modified-original): Keyring Credentials
- [Pumponomics can be skipped when using FluidLocker::provideLiquidity](https://0xsimao.com/findings/superfluid-locker-system-ii-pumponomics-skipped-fluid-provide): Superfluid Locker Pumponomics
- [Staked tokens inside FluidLocker can be withdrawn without calling Unstake](https://0xsimao.com/findings/superfluid-locker-system-ii-staked-inside-withdrawn-unstake): Superfluid Locker Pumponomics
- [Fluid (SUP) can be withdrawn from the Locker while the unlock flag is false](https://0xsimao.com/findings/superfluid-locker-system-ii-sup-withdrawn-flag-false): Superfluid Locker Pumponomics
