<!-- canonical: https://0xsimao.com/findings/superfluid-locker-system-unlock-instantly-staking-tax -->

# A malicious user may unlock instantly all the funds from the `FluidLocker` when no one is staking in the Tax pool

Medium · Sherlock · Streaming · 20th November 2024

Finding M-3 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/24

---

### Summary

The missing check in [FluidLocker::_instantUnlock()](https://github.com/sherlock-audit/2024-11-superfluid-locking-contract/blob/main/fluid/packages/contracts/src/FluidLocker.sol#L336) for stakers in the tax pool allows users to unlock all their funds without paying any tax instantly. This happens because whenever there are 0 stakers in the tax pool, the flow rate is set to 0 and it does not revert, so the user can loop unlocking until all funds are withdrawn.

Note: `FluidEPProgramManager::stopFunding()` also has issues related to 0 stakers in the program or tax pools.

Note2: the mentioned assumptions in the readme about assuming there are stakers before calls only refer to `FluidEPProgramManager::startFunding()` and `Fountaine::initialize()`, not these 2 flows mentioned here.

### Root Cause

In `FluidLocker::_instantUnlock()` there is a missing check for 0 stakers in the tax pool.

### Internal pre-conditions

1. There are 0 stakers in the tax pool.

### External pre-conditions

None.

### Attack Path

1. User loops `FluidLocker::unlock()` with a null unlocking period and instantly withdraws all their funds.

### Impact

The user is able to unlock all their funds without paying any tax.

### PoC

Add the following test to `FluidLocker.t.sol`.
```solidity
function test_POC_InstantUnlock_WithoutFees() external {
    _helperFundLocker(address(aliceLocker), 10_000e18);

    assertEq(_fluidSuperToken.balanceOf(address(ALICE)), 0, "incorrect Alice bal before op");
    assertEq(_fluidSuperToken.balanceOf(address(aliceLocker)), 10_000e18, "incorrect Locker bal before op");

    _helperUpgradeLocker();

    vm.startPrank(ALICE);
    for (uint i = 0; i < 30; i++) {
        aliceLocker.unlock(0, ALICE);
    }

    assertGt(_fluidSuperToken.balanceOf(address(ALICE)), 9.98e21, "incorrect Alice bal after op");
}
```

### Mitigation

Check if the pools have 0 units and revert if so.

---

Related findings:

- [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
- [Locker owners can leverage low liquidity pools to bypass the tax mechanism](https://0xsimao.com/findings/superfluid-locker-system-ii-owners-low-bypass-mechanism): Superfluid Locker Pumponomics
- [StakingOperator does not set isUnlockWindowActive to true in the constructor if the flag passed is true](https://0xsimao.com/findings/singularity-staking-window-active-flag): Singularity
- [StakingOperator::_setUnlockWindow() checks if the times are negative, which is impossible](https://0xsimao.com/findings/singularity-staking-window-times-negative): Singularity
