Opus finding
StreamableFeesLockerV2._collectFees unlock path does not split fees collected in the same call before transferring full balance to recipient
highdata-losshigh
- src/StreamableFeesLockerV2.sol:129-149
On the unlock path `_collectFees` first calls `_collect` (which records `fees` representing fees accrued since last collection) and returns them up to the parent caller `FeesManager.collectFees`. There, those fees are added to `getCumulatedFees0/1` and `_releaseFees` is called for `msg.sender`. Then the inner branch burns the position (transferring all principal+any newly accrued amount to the contract via `_burn`), and immediately transfers the FULL `delta` (which equals burned principal AND any fees burnt collected in same modifyLiquidity call?) to `stream.recipient`. Because `_burn`'s `balanceDelta` includes both the principal liquidity removal and any feesAccrued at that moment, but those fees were already added to the cumulated counters? Actually `_collect` happens immediately before `_burn`, so feesAccrued at burn time should be ~0. However, the contract balance still includes prior unclaimed cumulated fees that beneficiaries have not yet released (`getCumulatedFees - getLastCumulatedFees`). When the contract transfers `delta.amount0/1` to the recipient, that transfers ONLY the burn proceeds (delta), not the contract's full balance, so unclaimed fees for beneficiaries remain in the contract — good. But: `delta` here is from `_burn` which returns balanceDelta as the modifyLiquidity delta (negative liquidity → positive balanceDelta which represents principal + any residual feesAccrued at this moment). Since `_collect` was just called, residual feesAccrued in burn should be zero. So `delta` ~ principal, transferred to recipient — correct. There's no clear data-loss bug. Withdrawing.
Recommendation
N/A