One can create a flow stream without any upfront deposit, so the initial stream balance begins at zero. The sender can
later deposit any amount into the stream at any time. To improve the experience, a createAndDeposit
function has also
been implemented to allow both create and deposit in a single transaction.
One can also start a stream without setting an rps. If rps is set to non-zero at the beginning, it begins streaming as soon as the transaction is confirmed on the blockchain. These streams have no end date, but it allows the sender to pause it or void it at a later date.
A stream is represented by a struct, which can be found in
DataTypes.sol
.
The debt is tracked using "snapshot debt" and "snapshot time". At snapshot, the following events are taking place:
- snapshot debt is incremented by ongoing debt where
$\text{ongoing debt} = rps \cdot (\text{block timestamp} - \text{snapshot time})$ . - snapshot time is updated to block timestamp.
The recipient can withdraw the streamed amount at any point. However, if there aren't sufficient funds, the recipient can only withdraw the available balance.
Terms | Abbreviations |
---|---|
Block Timestamp | now |
Covered Debt | cd |
Ongoing Debt | od |
Rate per second | rps |
Refundable Amount | ra |
Scale Factor | sf |
Snapshot Debt | sd |
Snapshot Time | st |
Stream Balance | bal |
Time elapsed since snapshot | elt |
Total Debt | td |
Uncovered Debt | ud |
Witdrawable Amount | wa |
Action | Sender | Recipient | Operator(s) | Unknown User |
---|---|---|---|---|
AdjustRatePerSecond | ✅ | ❌ | ❌ | ❌ |
Deposit | ✅ | ✅ | ✅ | ✅ |
Pause | ✅ | ❌ | ❌ | ❌ |
Refund | ✅ | ❌ | ❌ | ❌ |
Restart | ✅ | ❌ | ❌ | ❌ |
Transfer NFT | ❌ | ✅ | ✅ | ❌ |
Void | ✅ | ✅ | ✅ | ❌ |
Withdraw | ✅ (only to Recipient) | ✅ | ✅ | ✅ (only to Recipient) |
-
for any stream,
$st \le now$ -
for a given token:
-
$\sum$ stream balances + protocol revenue = aggregate balance - token.balanceOf(SablierFlow)
$\ge \sum$ stream balances + flow.protocolRevenue(token) -
$\sum$ stream balances =$\sum$ deposited amount -$\sum$ refunded amount -$\sum$ withdrawn amount
-
-
For a given token, token.balanceOf(SablierFlow)
$\ge$ flow.aggregateBalance(token) -
snapshot time should never decrease
-
for any stream, if
$ud > 0 \implies cd = bal$ -
if
$rps \gt 0$ and no deposits are made$\implies \frac{d(ud)}{dt} \ge 0$ -
if
$rps \gt 0$ , and no withdraw is made$\implies \frac{d(td)}{dt} \ge 0$ -
for any stream, sum of deposited amounts
$\ge$ sum of withdrawn amounts + sum of refunded -
sum of all deposited amounts
$\ge$ sum of all withdrawn amounts + sum of all refunded -
next stream id = current stream id + 1
-
if
$ ud = 0 \implies cd = td$ -
$bal = ra + cd$ -
for any non-voided stream, if
$rps \gt 0 \implies isPaused = false$ and Flow.Status is either STREAMING_SOLVENT or STREAMING_INSOLVENT. -
for any non-voided stream, if
$rps = 0 \implies isPaused = true$ and Flow.Status is either PAUSED_SOLVENT or PAUSED_INSOLVENT. -
if
$isPaused = true \implies rps = 0$ -
if
$isVoided = true \implies isPaused = true$ and$ud = 0$ -
if
$isVoided = false \implies \text{expected amount streamed} = td + \text{amount withdrawn}$
- ERC-20 tokens with decimals higher than 18 are not supported.
The ongoing debt (od) is the debt accrued since the last snapshot. It is defined as the rate per second (rps) multiplied by the time elapsed since the snapshot time.
The snapshot debt (sd) is the amount that the sender owed to the recipient at the snapshot time. During a snapshot, the snapshot debt increases by the ongoing debt.
The total debt (td) is the total amount the sender owes to the recipient. It is calculated as the sum of the snapshot debt and the ongoing debt.
The part of the total debt that covered by the stream balance. This is the same as the withdrawable amount, which is an alias.
The covered debt (cd) is defined as the minimum of the total debt and the stream balance.
The part of the total debt that is not covered by the stream balance. This is what the sender owes to the stream.
The uncovered debt (ud) is defined as the difference between the total debt and the stream balance, applicable only when the total debt exceeds the balance.
Together, covered debt and uncovered debt make up the total debt.
The refundable amount (ra) is the amount that can be refunded to the sender. It is defined as the difference between the stream balance and the total debt.
The rps
introduces a precision problem for tokens with fewer decimals (e.g.
USDC, which has 6 decimals).
Let's consider an example: if a user wants to stream 10 USDC per day, the rps should be:
But since USDC only has 6 decimals, the rps would be limited to
In the contracts, we scale the rate per second to 18 decimals. While this doesn't completely solve the issue, it significantly minimizes it.
Using the same example (streaming 10 USDC per day), if rps has 18 decimals, the end-of-day result would be:
The difference would be:
This is an improvement by
Currently, it's not possible to address this precision problem entirely.
Important
The issues described in this section, as well as those discussed below, will not lead to a loss of funds but may affect the streaming experience for users.
From the previous section, we can define the Relative Delay as the minimum period (in seconds) that a N-decimal
rps
system would require to stream the same amount of tokens that the 18-decimal rps
system would.
In a 6-decimal rps
system, for the rps
values provided in the example above, we can calculate
the relative delay over a one-day period as follows:
Similarly, relative delays for other time intervals can be calculated:
- 7 days: ~1 hour, 5 minutes
- 30 days: ~4 hours, 38 minutes
- 1 year: ~2 days, 8 hours
Minimum Transferable Value (MVT) is defined as the smallest amount of tokens that can be transferred. In an
N-decimal rps
system, the MVT cannot be less than 1 token. For example, in case of USDC, the MVT is 0.000001e6
,
which is would to stream 0.0864e6
USDC per day. If we were to stream a high priced token, such as a wrapped Bitcoin
with 6 decimals, then such system could not allow users to stream less than 0.0864e6 WBTC = $5184
per day (price taken
at $60,000 per BTC).
By using an 18-decimal rps
system, we can allow streaming of amount less than Minimum Transferable Value.
The above issues are inherent to all decimal systems, and get worse as the number of decimals used to represent
rps
decreases. Therefore, we took the decision to define rps
as an 18-decimal number so that it can minimize, if not
rectify, the above two problems. Along with this, we also need to consider the following:
- Store snapshot debt in 18-decimals fixed point number.
- Calculate ongoing debt in 18-decimals fixed point number.
- Convert the total debt from 18-decimals to the token's decimals before calculating the withdrawable amount.