Because of incorrect assert
statement in SablierFlow._withdraw()
transaction would revert when recepient (or approved wallets) tries to withdraw accumulated funds.
SablierFlow.withdraw()
function allows recepient (or approved wallet) to withdraw provided amount of funds that were accumulated in stream and are available (i.e. sender have deposited enough funds). This function removes amount
of funds from balance, and to make sure all calculations are done corret (so noone under no conditions can steal funds) function have additional check:
This expression (assert) checks that the amount that was withdrawn is the same as amount that was reduced from debt. But problem arises because this amount can actually be different and because of that assert fails and call reverts not allowing user to withdraw funds even though withdraw amount is correct (enough balance to withdraw and enough amount was accumulated).
Firstly lets create test and modify SablierFlow
(add console logs) and then see why it fails.
Add this test to /tests/integration/fuzz/withdraw.t.sol
Modify SablierFlow
If you run test above it should fail you should see next logs:
[FAIL: panic: assertion failed (0x01); counterexample: calldata=0x2aa84b4d0000000000000000000000000000000068421b37d71d946a91c483bcf261496c000000000000000000000000000000000000000000000000000000000934befd args=[138582955203477823121574976868663249260 [1.385e38], 154451709 [1.544e8]]]
Logs:
depositAmount 154451709
totalDebtScaled 154451710000000008580
amountScaled 154451709000000000000
totalDebt 154451710
_totalDebtOf(streamId) 154442835
balance 154451709
_streams[streamId].balance 0
We can see right away that left part of assert calculated correctly:
balance - _streams[streamId].balance => 154451709 - 0 = 154451709 which is the same as deposit amount and amount to withdraw (user input).
Now let's see left part result:
totalDebt - _totalDebtOf(streamId) = 154451710 - 154442835 = 8875 which is not 0.
Because 8875 != 0 assert fails and call reverts.
Unfortunately I couldn't manage to investigate error further. From what I understood it's because of precision loss and (I think) mostly because rps is small (1.544e8). I tried to avoid precision loss of Helpers.descaleAmount()
for totalDebt
:
but this didn't help. I consider that small rps, descaling and _streams[streamId].snapshotDebtScaled = totalDebtScaled - amountScaled;
together make this error.
Break of protocol invariant
User can not withdraw funds
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.