EIP 1620: ERC-1620 Money Streaming
Author | Paul Berg (@PaulRBerg) |
---|---|
Discussions-To | https://github.com/ethereum/EIPs/issues/1620 |
Status | Draft |
Type | Standards Track |
Category | ERC |
Created | 2018-11-24 |
Simple Summary
Money streaming represents the idea of continuous payments over a finite period of time. Block numbers are used as a proxy of time to continuously update balances.
Abstract
The following describes a standard whereby time is measured using block numbers and streams are mappings in a master contract.
- A provider sets up a money streaming contract.
- A prospective payer can interact with the contract and start the stream right away by depositing the funds required for the chosen period.
- The payee is able to withdraw money from the contract based on its ongoing solvency. That is:
payment rate * (current block height - starting block height)
- The stream terms (payment rate, length, metadata) can be updated at any time if both parties pledge their signatures.
- The stream can be stopped at any point in time by any party without on-chain consensus.
- If the stream period ended and it was not previously stopped by any party, the payee is entitled to withdraw all the deposited funds.
Motivation
This standardised interface aims to change the way we think about long-term financial commitments. Thanks to blockchains, payments need not be sent in chunks (e.g. monthly salaries), as there is much less overhead in paying-as-you-go. Money as a function of time would better align incentives in a host of scenarios.
Use Cases
This is just a preliminary list of use cases. There are other spooky ideas interesting to explore, such as time-dependent disincetivisation, but, for brevity, we have not included them here.
- Salaries
- Subscriptions
- Consultancies
- CDPs
- Rent
- Parking
Crowdsales
RICOs, or Reversible ICOs, were introduced at Devcon4 by @frozeman. The idea is to endow investors with more power and safety guarantees by allowing them to “reverse” the investment based on the evolution of the project. We previously discussed a similar concept called SICOs, or Streamable ICOs, in this research thread.
Instead of investing a lump sum and giving the money away to the project developers, funds are held in a smart contract which allocates money based on the passage of time. Project developers can withdraw funds as the stream stays active, while investors have the power to get back a significant percentage of their initial commitment if the project halts.
Specification
Expand
### Structs The structure of a `stream` should be as follows: - `stream` - `sender`: the `address` of the entity funding the stream - `recipient`: the `address` where the money is being delivered to - `tokenAddress`: the `address` of the ERC20 token used as payment asset - `balance`: the total funds left in the stream - `timeframe`: as defined below - `rate`: as defined below ```solidity struct Stream { address sender; address recipient; address tokenAddress; uint256 balance; Timeframe timeframe; Rate rate; } ``` - `timeframe` - `start`: the starting block number of the stream - `stop`: the stopping block number of the stream ```solidity struct Timeframe { uint256 start; uint256 stop; } ``` - `rate` - `payment`: how much money moves from `sender` to `recipient` - `interval`: how often `payment` moves from `sender` to `recipient` ```solidity struct Rate { uint256 payment; uint256 interval; } ``` --- ### Methods #### balanceOf Returns available funds for the given stream id and address. ```solidity function balanceOf(uint256 _streamId, address _addr) ``` #### getStream Returns the full stream data, if the id points to a valid stream. ```solidity function getStream(uint256 _streamId) returns (address sender, address recipient, address tokenAddress, uint256 balance, uint256 startBlock, uint256 stopBlock, uint256 payment, uint256 interval) ``` #### create Creates a new stream between `msg.sender` and `_recipient`. MUST allow senders to create multiple streams in parallel. SHOULD not accept Ether and only use ERC20-compatible tokens. **Triggers Event**: [LogCreate](#log-create) ```solidity function create(address _recipient, address _tokenAddress, uint256 _startBlock, uint256 _stopBlock, uint256 _payment, uint256 _interval) ``` #### withdraw Withdraws all or a fraction of the available funds. MUST allow only the recipient to perform this action. **Triggers Event**: [LogWithdraw](#log-withdraw) ```solidity function withdraw(uint256 _streamId, uint256 _funds) ``` #### redeem Redeems the stream by distributing the funds to the sender and the recipient. SHOULD allow any party to redeem the stream. **Triggers Event**: [LogRedeem](#log-redeem) ```solidity function redeem(uint256 _streamId) ``` #### confirmUpdate Signals one party's willingness to update the stream SHOULD allow any party to do this but MUST NOT be executed without consent from all involved parties. **Triggers Event**: [LogConfirmUpdate](#log-confirm-update) **Triggers Event**: [LogExecuteUpdate](#log-execute-update) when the last involved party calls this function ```solidity function update(uint256 _streamId, address _tokenAddress, uint256 _stopBlock, uint256 _payment, uint256 _interval) ``` #### revokeUpdate Revokes an update proposed by one of the involved parties. MUST allow any party to do this. **Triggers Event**: [LogRevokeUpdate](#log-revoke-update) ```solidity function confirmUpdate(uint256 _streamId, address _tokenAddress, uint256 _stopBlock, uint256 _payment, uint256 _interval) ``` --- ### Events #### LogCreate MUST be triggered when `create` is successfully called. ```solidity event LogCreate(uint256 indexed _streamId, address indexed _sender, address indexed _recipient, address _tokenAddress, uint256 _startBlock, uint256 _stopBlock, uint256 _payment, uint256 _interval) ``` #### LogWithdraw MUST be triggered when `withdraw` is successfully called. ```solidity event LogWithdraw(uint256 indexed _streamId, address indexed _recipient, uint256 _funds) ``` #### LogRedeem MUST be triggered when `redeem` is successfully called. ```solidity event LogRedeem(uint256 indexed _streamId, address indexed _sender, address indexed _recipient, uint256 _senderBalance, uint256 _recipientBalance) ``` #### LogConfirmUpdate MUST be triggered when `confirmUpdate` is successfully called. ```solidity event LogConfirmUpdate(uint256 indexed _streamId, address indexed _confirmer, address _newTokenAddress, uint256 _newStopBlock, uint256 _newPayment, uint256 _newInterval); ``` #### LogRevokeUpdate MUST be triggered when `revokeUpdate` is successfully called. ```solidity event LogRevokeUpdate(uint256 indexed _streamId, address indexed revoker, address _newTokenAddress, uint256 _newStopBlock, uint256 _newPayment, uint256 _newInterval) ``` #### LogExecuteUpdate MUST be triggered when an update is approved by all involved parties. ```solidity event LogExecuteUpdate(uint256 indexed _newStreamId, address indexed _sender, address indexed _recipient, address _newTokenAddress, uint256 _newStopBlock, uint256 _newPayment, uint256 _newInterval) ```Rationale
This specification was designed to serve as an entry point to the quirky concept of money as a function of time and it is definitely not set in stone. Several other designs, including payment channels and Plasma chains were also considered, but they were eventually deemed dense in assumptions unnecessary for an initial version.
Block times are a reasonable, trustless proxy for time on the blockchain. Between 2016 and 2018, the Ethereum block time average value hovered around 14 seconds, excluding the last two quarters of 2017. Mathematically speaking, it would be ideal to have a standard deviation as close to 0 as possible, but that is not how things work in the real world. This has huge implications on the feasibility of this ERC which we shall investigate below.
GCD
When setting up a stream, a payer and a payee may want to make the total streaming duration a multiple of the “greatest common denominator” (GCD) of the chain they operate on; that is, the average block time. This is not imperative in the smart contracts per se, but there needs to be an off-chain process to map streams to real world time units in order to create a sound and fair payment mechanism.
Block Times
Because there is uncertainty regarding block times, streams may not be settled on the blockchain as initially planned. Let $d
be the total streaming duration measured in seconds, $t
the average block time before the stream started and $t'
the actual average block time over $d
after the stream started. We distinguish two undesirable scenarios:
-
$t
<$t'
: the payee will get their funds later than expected -
$t
>$t'
: the payee will get their funds sooner than expected
If the combined error delta is smaller than the payment rate (fifth parameter of the create
method, measured in wei), there is no problem at all. Conversely, we stumble upon trust issues because real-world time frames do not correspond to the stream terms. For instance, if an employee is normally entitled to withdraw all the funds from the stream at the end of the month, but block times cause case 1 from above to occur, the employee is in a financial disadvantage because their continuous effort is not compensated as promised.
Limiting the problem scope only to Ethereum, we propose two remedies:
-
Consensus on calling the
update
function to correct the stream terms. This might sound preposterous, but in most cases the stakes are low and stream participants are involved in long-term financial commitments. There is a high disincentive to refuse to cooperate. -
Autonomously fix significant error deltas. In theory, we could achieve this using previous blocks’ timestamps, “checkpointing” the stream once in a predefined number of blocks. This is still an area of active research because of potentially high overheads in gas costs.
Nonetheless, it is important to note that this is still a major improvement on the traditional model where absolute trust is required.
Sidechains
It could be more efficient to implement this standard on independent sidechains like POA Network or xDai - thanks to their rather predictable nature. Admittedly, security is traded for scalability, but proper cryptoeconomic stakes could alleviate potential problems.
Furthermore, it is intriguing to explore the prospect of stream-specific sidechains.
Oracles
The proposed specification uses block numbers to proxy time, but this need not be the only method. Albeit it would imply different trust assumptions, oracles could be used to provide a feed of timestamps. Coupled with the aforementioned idea of stream-specific sidechains, oracles could efficiently solve the problems outlined in Block Times.
Multi-Hop Streams
Future or upgraded versions of this standard may describe “multi-hop” streams. If:
- There is a stream between A and B
- There is another stream between B and C
There could be a way to avoid running two different streams in parallel. That is, a fraction or all of the funds being streamed from A to B could be automatically wired to C. An interesting use case for this is taxes. Instead of manually moving money around, proactively calculating how much you owe and then transfer it, a stream could atomically perform those operations for you.
Implementation
Additional References
- Chronos Protocol Ethresear.ch Plasma Proposal
- Chronos Protocol White Paper
- Flipper: Streaming Salaries @ CryptoLife Hackathon
- SICOs or Streamed ICOs
- RICOs or Reversible ICOs
- Andreas Antonopoulos’ Keynote on Bitcoin, Lightning and Money Streaming
Final Notes
Many thanks to @mmilton41 for countless brainstorming sessions. We have been doing research on the topic of money streaming for quite a while within the context of @ChronosProtocol. In August this year, we published the first version of our white paper describing a Plasma approach. However, in the meantime, we realised that it would be much more fun and easier to start small on Ethereum itself and sidechains like xDai.
Copyright
Copyright and related rights waived via CC0.