ethereum / EIPs

The Ethereum Improvement Proposal repository

Home Page:https://eips.ethereum.org/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Bonded Fungible Tokens

lsaether opened this issue · comments

Bonded Fungible Tokens

---
eip: <to be assigned>
title: Bonded Fungible Tokens
author: lsaether <logan@convergent.cx>
discussions-to: <URL>
status: Draft
type: Standards Track
category: Interface
created: 2018-12-23
---

Simple Summary

A bonded fungible token is an ERC20 standard compliant token with a price and supply controlled by an automated market maker known as a bonding curve.

The bonding curve is a smart contract which holds a reserve asset and increases or decreases the supply of a token, always determining the price based on the total supply and the amount held of the reserve asset.

From a user perspective, it is a method of buying or selling a token directly from a smart contract and which the price of purchase or reward for sells follows a pre-defined and determined price path.

Abstract

Although the concepts of an automated market maker and bonding curve to control the price and supply of tokens is not new, it has been used most notably in the Bancor protocol, it has yet to be given a standard interface that developers can follow in implementations.

Motivation

Bonding curves are a promising mechanism for the tokenized future. By standardizing the interface, we can begin to allow community tools such as Etherscan and other block explorers to integrate information about these tokens automatically. Since the token is traded through the contract, we should be able to pull details such as the market cap of the token or the current price to display on UIs. A standard interface will further allow developers to build bonding curve tokens into more complex systems such as nested curves.

Specification

pragma solidity ^0.4.24;

/**
 * @title Bonded Fungible Token Interface
 * @dev A bonded fungible token is a token "bonded" to a reserve asset and
 *      continuously generated or destroyed by staking or withdrawing this
 *      asset.
 */
interface IBondedFungibleToken {

    // Logs when `purchaser` buys `amount` of tokens for `paid` in reserve asset.
    event CurveBuy(uint256 amount, uint256 paid, address indexed purchaser);

    // Logs when `seller` sells `amount` of tokens for `rewarded` in reserve asset.
    event CurveSell(uint256 amount, uint256 rewarded, address indexed seller);

    // Returns the price for buying `forTokens` amount of bonded tokens.
    function price(uint256 forTokens) public view returns (uint256 thePrice);

    // Returns the reward for selling `forTokens` amount of bonded tokens.
    function reward(uint256 forTokens) public view returns (uint256 theReward);

    // Buys `tokens` amount of bonded tokens and returns how much `paid` in reserve.
    function buy(uint256 tokens) external returns (uint256 paid);

    // Sells `tokens` amount of bonded tokens and returns how much `rewarded` in reserve.
    function sell(uint256 tokens) external returns (uint256 rewarded);

    // Returns the current price of the token. Mostly useful for reference.
    function currentPrice() public view returns (uint256 theCurrentPrice);

    // Returns the address of the asset smart contract or 0x0 for ether.
    function reserveAsset() public view returns (address asset);

    // Returns the amount of `reserveAsset` held in reserve in contract. 
    function reserve() public view returns (uint256 amount);
}

Rationale

Imagine using Dai as the reserve asset for an example bonded fungible token, EXP. Community tools such as block explorers could include the current price of EXP straight in their interface by keeping up to date with the return value of the currentPrice function.

Besides front-end conveniences, there are also low-level interoperability constraints that we should keep in mind as more developers start to build systems which use bonded tokens and bonding curves.

Test Cases

Example source code and some test cases have been implemented in milky-way

Implementation

milky-way
Please suggest others.

Copyright

Copyright and related rights waived via CC0.

The function buy(uint256 tokens) will make UX really bad. If two users tries to buy tokens in the same block with the correct amount of payment only the first tx will be valid. This makes the experience of buying tokens from the bonding curve really confusing since you txs are likely to fail frequently if there is high demand.
The way around this is to either send a larger payment than you expect to pay when you buy, or don't specify then number of tokens and get whatever is caluclated by the curve (both which opens up to front running attacks).
Other interesting approaches include batch auctions (see gnosis dutchX) but I'm not sure that is safe in this context, and commit reveal schemes.

I wonder if there is value in using #777 as a base... the operator functionality could be very useful.

I have a few thoughts on the spec:

  1. The buy function should not accept the # of tokens as a parameter. The number of tokens purchased should be determined by the amount of ether sent in the transaction and the current buy price, i.e. set by the msg.sender value inside the buy function and the current price at the time of execution. This will solve @oed 's objection

  2. There should be two functions for the buy price and sell price instead of one function returning a single value in 'currentPrice'. It's possible that the curve will have a spread between its buy price and sell price that makes the buy and sell price have different values. It's also possible that the buy price and sell price could be defined using different curves, for example if you wanted to make something like a "Continuous Organization" or DAO that has the sell curve use a lower slope than the buy price to accumulate a reserve of excess capital in the contract that the organization can decide to spend as it pleases. In either case, the point is that the buy price and sell price can be different, even wildly different.

  3. It's not clear to me what 'reward' is doing or what it's purpose is.

I agree with the previous comments. Some other things to consider:

  • Similar to Bancor, the buy/sell function should have a 'maxSlippage' parameter which can revert a buy/sell during front running attacks or if the price slips too far from what the user wants. This also helps resolve @oed 's point.
  • Consider a 'sweep' function, where any remaining reserveAsset can be transferred to a different address. The use case for this is if the price function results in some left over reserveAssets when all tokens have been sold/burned.
  • This draft assumes 1 dimensional bonding curves. It may be worth considering if/how higher dimensional bonding curves can also fit into the standard. A problem in this case would be getting the price for say, dynamic bonding curves where the price is different per token holder. Other higher dimensional bonding curves may require other parameters to determine the price function.

P.S. nice work on kick-starting this EIP!

Thanks for the comments! It's good to see more people here :)

@GriffGreen

I wonder if there is value in using #777 as a base... the operator functionality could be very useful.

I have thought about this! One point this brings up is whether the bonding curve should be separate from the token. That is, should a bonded fungible token be permanently bonded, or can it be unbonded? If it was ERC777 operator then it would imply that one could remove the operator and "unbond" the token.

@miller46 @oed

The buy function should not accept the # of tokens as a parameter.

This is a good point.. but one issue @acrdlph and I have discussed is the UX hurdle of sending value without knowing the amount of tokens that they are getting back for it. From a technical perspective it can be more "fair" to the user (especially in the case of batch transactions) but how do we communicate this to the end user?

@miller46

There should be two functions for the buy price and sell price instead of one function returning a single value in 'currentPrice'.

I agree about this. Do you think buyPrice and sellPrice are good names for these functions?

@miller46

It's not clear to me what 'reward' is doing or what it's purpose is.

I intended the 'reward' to be the amount of the reserve asset sent back to the user when they burn tokens during a sell. It may not be the best name. Some other ones that could be substituted instead are return, or withdrawAmount. I'm open to other suggestions as well.

@lsaether Yeah, that's a fair point and I'm unsure how to best solve it. The reason I'm very opinionated in this is that I've implemented it in the way you are suggested and played around with it in a UI that allowed you to specify the # tokens you buy. It happened frequently that two people would buy some tokens in the same block and for one of the users the tx would fail. This is really bad UX and only one buy tx per block is not going to work for popular tokens.

I'm actually leaning towards not talking about the number of tokens you have in the bonding curve to the user. Instead the UX would be something like "Invest n DAI in this curve" then you'd only be shown your investments worth in DAI when the total amount of reserve changes. Then maybe you could optionally show the number of tokens you have for power users?
I guess this all depends on the use case of your bonding curve though.

This is a good point.. but one issue @acrdlph and I have discussed is the UX hurdle of sending value without knowing the amount of tokens that they are getting back for it.

I just think specs should contain the bare minimum that is required. There are a number of ways this could be implemented based on the use case. I just don't think this is so critical to the definition of a bonding curve that it must be hardcoded in to the spec for every implementation.

Alternative ways to solve the slippage issue would be have the user optionally set some maxSlippage value like the approve mechanism in ERC-20 (except set the default to be MAX_INT instead of 0) or have the contract hardcode some absolute max slippage, among others.

I also think for many use cases that users would rather just accept some slippage than have their transaction fail. If the transaction fails, it's like they lost their spot in line and they might have to try again at an even higher price than they would have if they just took the slippage on the first transaction, if the price is going up. In the reverse case, of one or more sells happening right before them, they likely would not mind receiving more tokens than they expected.

Do you think buyPrice and sellPrice are good names for these functions?

That's what I'd use.

I intended the 'reward' to be the amount of the reserve asset sent back to the user when they burn tokens during a sell.

I can't think of a concise name for this. When I come across this situation in my own code, I usually jam many words together, for better or for worse. So, my inclination is something like calculateReserveAssetReceived but not everyone likes that type of convention.

There has been no activity on this issue for two months. It will be closed in a week if no further activity occurs. If you would like to move this EIP forward, please respond to any outstanding feedback or add a comment indicating that you have addressed all required feedback and are ready for a review.

This issue was closed due to inactivity. If you are still pursuing it, feel free to reopen it and respond to any feedback or request a review in a comment.