Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove amount parameter in withdraw function #4

Merged
merged 9 commits into from
Nov 27, 2023

Conversation

andreivladbrg
Copy link
Member

@andreivladbrg andreivladbrg commented Nov 7, 2023

The Issue

In the current version of the contracts, I wanted to mirror the withdraw function signature from the Lockup contracts.

This doesn't work for open-ended streams due to the amount parameter. More specific, when the amount passed to the function is less than the maximum withdrawable amount, it can lead to lost funds for the recipient—not completely frozen, since the sender can still perform a refund—but unable to withdraw them as the recipient, this is due to updating the stream.lastTimeUpdate to block.timestamp and how the streamed amount is calculated.

We calculate the streamed amount by multiplying the rate per second (rps) by the delta between lastTimeUpdate and block.timestamp.

https://github.com/sablier-labs/v2-open-ended/blob/34cab8914430ea22845a5e9c98104a11720ae662/src/SablierV2OpenEnded.sol#L382-L386

Consider this example: Alice sets up a stream at time $\ t_0 $, with an rps corresponding to 1000 assets per month, i.e. $\ rps \approx 0.00038580246 $, and deposits sufficient assets for several months. After one month, at $\ t_1 $, the recipient decides to withdraw 500 assets. Here's what happens:

  • The function first calculates the maximum withdrawable amount as $\ (t_1 - t_0)\times rps = 1000$ .
  • It then checks if the provided amount (500) is less than or equal to the calculated amount (1000).
  • lastTimeUpdate is set to the current block.timestamp, i.e. $\ t_1 $.
  • The stream balance is decreased by 500.
  • And ERC20 transfer of 500 assets is performed.

If the recipient tries to withdraw the remaining 500 "streamed" assets just one second later, $\ t_2 $, the transaction will fail at step two because $(t_2 - t_1) \times rps = 1 \times rps < 500 $, resulting in a loss for the recipient.

The Fix

To address this, I have removed the amount parameter from withdraw function and added a time one in both streamedAmountOf and withdraw functions. @IaroslavMazur, please lmk if you have suggestions for a better name.

This change fixes the issue, as the streamed amount will now be calculated by multiplying rps with the delta between the provided time parameter and lastTimeUpdate. Ofc, additional checks are necessary to validate the time parameter:

https://github.com/sablier-labs/v2-open-ended/blob/75d9f7d12de3d3024dffb5a95421c147e6a7c63a/src/SablierV2OpenEnded.sol#L693-L701

Furthermore, lastTimeUpdate will now be set to the given time parameter, not block.timestamp.

Following the above example, instead of specifying an amount of 500 for withdrawal, the recipient would now input ( $\dfrac{t_1}{2} $, basically saying "I want to withdraw assets equivalent to two weeks' worth of streaming."

feat: add streamedAmountOf with time parameter
feat: add withdrawMax function
README.md Outdated Show resolved Hide resolved
README.md Outdated Show resolved Hide resolved
src/SablierV2OpenEnded.sol Show resolved Hide resolved
@andreivladbrg andreivladbrg force-pushed the fix/withdraw-function branch 11 times, most recently from 7670267 to bf488aa Compare November 13, 2023 22:35
docs: add more latex
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants