-
Notifications
You must be signed in to change notification settings - Fork 13
Description
From audit:
When an operator modifies the payment rate on a terminated rail before finalization, the lockupUsage tracking in the operator's approval becomes incorrect. The finalizeTerminatedRail() function calculates the lockup to release using rail.paymentRate * rail.lockupPeriod, which assumes a constant rate for the entire lockup period. However, when the rate is modified on a terminated rail, different rates apply to different portions of the period. The rate modification correctly uses remainingEpochs for adjustments, but finalization uses the full lockupPeriod with only the final rate, causing a mismatch that leaves phantom lockupUsage that is never released.
Vulnerable scenario
Operator creates a rail with rate=100 and lockupPeriod=10, resulting in lockupUsage=1000.
Operator terminates the rail at block N, setting endEpoch=N+10. The lockupUsage remains 1000.
At block N+2 (8 epochs remaining), operator reduces rate to 50. The lockupUsage is correctly adjusted: 1000 - (100×8) + (50×8) = 600.
At block N+11, the rail is settled and finalized.
During finalization, oldLockup is calculated as 50 × 10 = 500 (using current rate for full period).
The lockupUsage becomes 600 - 500 = 100 instead of the expected 0.
I find this image helpful:
The red region is the final 500 of lockup usage removed during finalization, the blue is the 400 lockup usage reduced during modifyRailPayment and the pink region in the corner is the unaccounted for 100 lockup usage left in the operators approval.
I think the correct approach is to remove each "pink region" in each call to modifyRailPayment along with removing the blue region, i.e. instead of
updateOperatorLockupUsage(operatorApproval, oldRate * effectiveLockupPeriod, newRate * effectiveLockupPeriod)
we should also have a call
if isTermianted {
updateOperatorLockupUsage(operatorApproval, oldRate * rail.LockupPeriod , newRate * effectiveLockupPeriod)
}
This should be safe because we don't need the lockupUsage for the already-passed epochs of the termination period as those funds have already streamed through the rail to make payment. And it should be correct because it maintains the invariant that the remaining lockupUsage left on the rail is simply rail.PaymentRate * rail.LockupPeriod so finalization code is now correct.