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

ERC: Money Streaming #1620

Open
PaulRBerg opened this issue Nov 24, 2018 · 11 comments

Comments

Projects
None yet
5 participants
@PaulRBerg
Copy link
Contributor

commented Nov 24, 2018

eip: 1620
title: ERC-1620 Money Streaming
author: Paul Berg (@PaulRBerg) <hello@paulrberg.com>
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.

  1. A provider sets up a money streaming contract.
  2. A prospective payer can interact with the contract and start the stream right away by depositing the funds required for the chosen period.
  3. 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)
  4. The stream terms (payment rate, length, metadata) can be updated at any time if both parties pledge their signatures.
  5. The stream can be stopped at any point in time by any party without on-chain consensus.
  6. 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
  • Rent
  • Parking
  • CDPs

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
    • timeframe: as defined below
    • rate: as defined below
    • balance: the total funds left in the stream
  struct Stream {
    address sender;
    address recipient;
    address tokenAddress;
    Timeframe timeframe;
    Rate rate;
    uint256 balance;
  }
  • timeframe
    • start: the starting block number of the stream
    • stop: the stopping block number of the stream
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
struct Rate {
  uint256 payment;
  uint256 interval;
}

Methods

balanceOf

Returns available funds for the given stream id and address.

function balanceOf(uint256 _streamId, address _addr)

getStream

Returns the full stream data, if the id points to a valid stream.

function getStream(uint256 _streamId) returns (address sender, address recipient, address tokenAddress, uint256 balance, uint256 startBlock, uint256 stopBlock, uint256 payment, uint256 interval)

createStream

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: CreateStream

function createStream(address _sender, address _recipient, address _tokenAddress, uint256 _startBlock, uint256 _stopBlock, uint256 _payment, uint256 _interval)

withdrawFromStream

Withdraws all or a fraction of the available funds.

MUST allow only the recipient to perform this action.

Triggers Event: WithdrawFromStream

function withdrawFromStream(uint256 _streamId, uint256 _funds)

redeemStream

Redeems the stream by distributing the funds to the sender and the recipient.

SHOULD allow any party to redeem the stream.

Triggers Event: RedeemStream

function redeemStream(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: ConfirmUpdate

Triggers Event: ExecuteUpdate when the last involved party calls this function

function confirmUpdate(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: RevokeUpdate

function revokeUpdate(uint256 _streamId, address _tokenAddress, uint256 _stopBlock, uint256 _payment, uint256 _interval)

Events

CreateStream

MUST be triggered when create is successfully called.

event CreateStream(uint256 indexed streamId, address indexed sender, address indexed recipient, address tokenAddress, uint256 startBlock, uint256 stopBlock, uint256 payment, uint256 interval)

WithdrawFromStream

MUST be triggered when withdraw is successfully called.

event WithdrawFromStream(uint256 indexed streamId, address indexed recipient, uint256 amount)

RedeemStream

MUST be triggered when redeem is successfully called.

event RedeemStream(uint256 indexed streamId, address indexed sender, address indexed recipient, uint256 senderAmount, uint256 recipientAmount)

ConfirmUpdate

MUST be triggered when confirmUpdate is successfully called.

event ConfirmUpdate(uint256 indexed streamId, address indexed confirmer, address newTokenAddress, uint256 newStopBlock, uint256 newPayment, uint256 newInterval);

RevokeUpdate

MUST be triggered when revokeUpdate is successfully called.

event RevokeUpdate(uint256 indexed streamId, address indexed revoker, address newTokenAddress, uint256 newStopBlock, uint256 newPayment, uint256 newInterval)

ExecuteUpdate

MUST be triggered when an update is approved by all involved parties.

event ExecuteUpdate(uint256 indexed streamId, 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:

  1. $t < $t': the payee will get their funds later than expected

  2. $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:

  1. 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.

  2. 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 crypto-economic 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:

  1. There is a stream between A and B
  2. 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

Copyright

Copyright and related rights waived via CC0.

@frozeman

This comment has been minimized.

Copy link
Member

commented Nov 24, 2018

Thanks for this. Very interesting concept.
Why only allowing ERC20 tokens and not ether?

@PaulRBerg

This comment has been minimized.

Copy link
Contributor Author

commented Nov 24, 2018

Thank you! The feedback we got so far is that people would love to use this with stablecoins like DAI, TrueUSD etc. Indeed, in the case of ICOs, Ether is the main currency used but a lot of people have been implementing WETH lately. The rationale is to avoid writing duplicate logic and I said to make the ERC strict because of that. Nevertheless, on a second thought, it doesn't hurt to just make it a little bit looser and accept empty _tokenAddress params.

@frozeman

This comment has been minimized.

Copy link
Member

commented Nov 24, 2018

In the RICO model this streaming of money is based on ETH.

In your idea the way to map streamer with the committed funds is through a streaming ID and an address, correct?

In the RICO idea you get a token back. The advantage is that the token can be transferred and so the commitment transferred and also only part of the commitment can be withdrawn rather than everything.
Also investing in a RICO van happens from any ether wallet and withdrawing your funds as well, by simply sending back the commit tokens.
Though your idea works probably better for subscriptions etc.
the disadvantage of the token is that you either have to have a locked balance if you already refunded (for the part if the tone. That is not refundable anymore), or two tokens: one refundable and one not.
Either token can layer be swapped for whatever was bought in the RICO.

@PaulRBerg

This comment has been minimized.

Copy link
Contributor Author

commented Nov 24, 2018

In your idea the way to map streamer with the committed funds is through a streaming ID and an address, correct?

Exactly, just an uint256 id incremented whenever a new stream is added. Each struct contains the addresses of both the payer and the payee.

In the RICO idea you get a token back.

I found this ERC and RICOs at least tangential, hence why I added a reference in the first place. Of course, the RICO proposal has more crowdsale-specific details insofar there is an extra set of instructions needed to be executed (i.e. receiving the ICO tokens), rather than just depositing the money and calculating balances based upon block heights.

I'd love to keep exploring refundable crowdsales because I think they're a massive improvement on the previous iterations - and it's one of the things which inspired me to continue with this ERC.

@frozeman

This comment has been minimized.

Copy link
Member

commented Nov 24, 2018

Yes it’s a great idea.
I have not yet planned to make an ERC for the RICO, we first will try it out with our project LUKSO and then see if it makes sense to standardize.

@jschiarizzi

This comment has been minimized.

Copy link

commented Nov 25, 2018

Seems pretty well thought out. Popular current time tracking programs systematically commit wage theft against the working class. I find it inevitable that we eventually get to paying people as the work for the exact amount of time they work. It's an idea a lot of folks have talked about, but it's nice to see the first technical spec for something that would enable this. Potentially take some thoughts from #1337 on subscriptions? Awesome job on this!

@PaulRBerg

This comment has been minimized.

Copy link
Contributor Author

commented Nov 25, 2018

Hey @jschiarizzi interesting points, wasn't aware there's that much unfairness. I totally love #1337, what the @gitcoinco and @8x-protocol teams are doing and the cool thing is that the "subscriptions" EIP can be used in coalition with money streaming.

The current spec requires people to deposit funds beforehand, hence for longer periods it may not work for everyone due to liquidity problems. But fear not, because you can run a subscription layer and schedule to pay "streaming deposits" every month.

@ipetrovic11

This comment has been minimized.

Copy link

commented Dec 11, 2018

@PaulRBerg Awesome to see this, you can take a look at Technical Paper that we wrote a while back making Streaming Money for salaries case:
https://drive.google.com/file/d/1o34Cbm6tAPNHNvjxR9IuWVlQCmeshHNL/view

Our wallet has already implemented this.

Would be great to have a live chat and discuss how we can bring this further to life, you can contact me at: ivan@workchain.io

@jtremback

This comment has been minimized.

Copy link

commented Dec 18, 2018

We've implemented something like this proposal here: https://github.com/althea-mesh/aragon-node-list/blob/master/contracts/Althea.sol#L86

  function addBill() public payable {
    require(msg.value > perBlockFee, "Message value not enough");

    if (billMapping[msg.sender].lastUpdated == 0) {
      billMapping[msg.sender] = Bill(msg.value, perBlockFee, block.number);
      emit NewBill(msg.sender, vault);
    } else {
      billMapping[msg.sender].balance = billMapping[msg.sender].balance.add(msg.value);
      emit BillUpdated(msg.sender);
    }
  }

  function collectBills() external {
    uint transferValue = 0;
    for (uint i = 0; i < subnetSubscribers.length; i++) {
      transferValue = transferValue.add(
        processBills(userMapping[subnetSubscribers[i]].ethAddr)
      );
    }
    vault.deposit.value(transferValue)(ETH, transferValue);
  }

  function payMyBills() public {
    uint amount = processBills(msg.sender);
    vault.deposit.value(amount)(ETH, amount);
  }

  function withdrawFromBill() external {
    payMyBills();
    uint amount = billMapping[msg.sender].balance;
    require(amount > 0, "Amount to payout is no more than zero, aborting");
    billMapping[msg.sender].balance = 0;
    address(msg.sender).transfer(amount);
    emit BillUpdated(msg.sender);
  }

  function processBills(address _subscriber) internal returns(uint) {
    uint transferValue;
    Bill memory bill = billMapping[_subscriber];
    uint amountOwed = block.number.sub(bill.lastUpdated).mul(bill.perBlock);

    if (amountOwed <= bill.balance) {
      billMapping[_subscriber].balance= bill.balance.sub(amountOwed);
      transferValue = amountOwed;
    } else {
      transferValue = bill.balance;
      billMapping[_subscriber].balance = 0;
    }
    billMapping[_subscriber].lastUpdated = block.number;
    emit BillUpdated(_subscriber);
    return transferValue;
  }

Can you see any benefits that this proposal has over our system? Or is it just a standardization of this concept?

@PaulRBerg

This comment has been minimized.

Copy link
Contributor Author

commented Dec 20, 2018

Hey @jtremback, good stuff. Imho, standardisation is a very powerful tool: there would've been no ICO craze without ERC20.

For one, accounting software working with Ethereum may expect the function names defined in this spec when integrating continuous payments, ergo they could have a hard time working with different functions like processBills.

Excluding coordination benefits, ERC1620 may not be helpful for specific apps where complex behaviour is not necessary (e.g. the ability to make an update to the streaming terms). If you know you'll not make significant updates in the future using something like ZeppelinOS, go without it. Otherwise, feel free to ping me on Twitter or email if you have any enquiries :)

@PaulRBerg

This comment has been minimized.

Copy link
Contributor Author

commented Jun 2, 2019

Happy to announce that now there's a dApp built on top of ERC-1620! It's called @SablierApp and you can check it out at https://sablier.app

For more information, check out this Twitter thread.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.