ethereum / EIPs

The Ethereum Improvement Proposal repository
https://eips.ethereum.org/
Creative Commons Zero v1.0 Universal
12.89k stars 5.29k forks source link

ERC: Fairer token crowdsale with proportional token allocation #642

Closed hiddentao closed 2 years ago

hiddentao commented 7 years ago

Preamble

EIP: <to be assigned>
Title: Fair Token Crowdsale
Authors: Ramesh Nair (hiddentao)
Type: Informational
Category: ERC 
Status: Draft
Created: 2017-06-01
Requires: 20

Simple Summary

A contract which allows for a fairer crowdsale through proportional allocation rather than first-come-first-served.

Abstract

This ERC-20 derived token contract aims to avoid a recurring problem in popular ICO/crowdsales, whereby a small no. of participants can effectively obtain all available tokens via a combination of extremely high transaction fees (unaffordable to most participants) and "getting there first". An acute example of this problem is the recent BAT tokensale, where all tokens were allocated within 24 seconds.

Although the ICO holder's goals of raising funding are met in such situations, the fact that the majority of tokens are in the hands of relatively few players is less than ideal when considered from the perspective of decentralisation. The recurring nature of this issue has led to a race-to-be-the-fastest, causing massive network congestion due to the large volume of transactions which are created in such a short space of time.

Some solutions have been proposed, including:

This ERC proposes a token contract to solve these issues without limiting how much Ether a participant can contribute. Specifically:

Drawbacks of this approach:

Example

Target ETH to raise = 100 ETH Total supply of tokens = 100 tokens

Scenario 1

Scenario 2

Specification

Methods

Events

Example Implementation

This uses StandardContract.sol and SafeMath.sol.

pragma solidity ^0.4.10;

import "./StandardToken.sol";
import "./SafeMath.sol";

contract FairToken is StandardToken, SafeMath {

    // metadata
    string public name = 'FairToken';
    string public symbol = 'FAIRTOKEN';
    uint256 public constant decimals = 18;
    string public version = "1.0";

    // important addresses
    address public depositAddress;      // deposit address for ETH for ICO owner

    // crowdsale params
    bool public isFinalized;            // true when ICO finalized and successful
    uint256 public targetEth;           // target ETH to raise
    uint256 public fundingStartBlock;   // when to start allowing funding
    uint256 public fundingEndBlock;     // when to stop allowing funding

    // events
    event CreateFairToken(string _name);
    event Contribute(address _sender, uint256 _value);
    event FinalizeSale(address _sender);
    event RefundContribution(address _sender, uint256 _value);
    event ClaimTokens(address _sender, uint256 _value);

    // calculated values
    mapping (address => uint256) contributions;    // ETH contributed per address
    uint256 contributed;      // total ETH contributed

    // constructor
    function FairToken(
        uint256 _totalSupply,
        uint256 _minEth,
        address _depositAddress,
        uint256 _fundingStartBlock,
        uint256 _fundingEndBlock)
    {
        isFinalized = false;
        totalSupply = _totalSupply * 10**decimals;
        targetEth = _minEth;
        depositAddress = _depositAddress;
        fundingStartBlock = _fundingStartBlock;
        fundingEndBlock = _fundingEndBlock;
        // log
        CreateFairToken(name);
    }

    /// Accepts ETH from a contributor
    function contribute() payable external {
        if (block.number < fundingStartBlock) throw;    // not yet begun?
        if (block.number > fundingEndBlock) throw;      // already ended?
        if (msg.value == 0) throw;                  // no ETH sent in?

        // Add to contributions
        contributions[msg.sender] += msg.value;
        contributed += msg.value;

        // log
        Contribute(msg.sender, msg.value);  // logs contribution
    }

    /// Finalizes the funding and sends the ETH to deposit address
    function finalizeFunding() external {
        if (isFinalized) throw;                       // already succeeded?
        if (msg.sender != depositAddress) throw;      // wrong sender?
        if (block.number <= fundingEndBlock) throw;   // not yet finished?
        if (contributed < targetEth) throw;             // not enough raised?

        isFinalized = true;

        // send to deposit address
        if (!depositAddress.send(targetEth)) throw;

        // log
        FinalizeSale(msg.sender);
    }

    /// Allows contributors to claim their tokens and/or a refund. If funding failed then they get back all their Ether, otherwise they get back any excess Ether
    function claimTokensAndRefund() external {
        if (0 == contributions[msg.sender]) throw;    // must have previously contributed
        if (block.number < fundingEndBlock) throw;    // not yet done?

        // if not enough funding
        if (contributed < targetEth) {
            // refund my full contribution
            if (!msg.sender.send(contributions[msg.sender])) throw;
            RefundContribution(msg.sender, contributions[msg.sender]);
        } else {
            // calculate how many tokens I get
            balances[msg.sender] = safeMult(totalSupply, contributions[msg.sender]) / contributed;
            // refund excess ETH
            if (!msg.sender.send(contributions[msg.sender] - (safeMult(targetEth, contributions[msg.sender]) / contributed))) throw;
            ClaimTokens(msg.sender, balances[msg.sender]);
      }

      contributions[msg.sender] = 0;
    }
}

Initially I didn't think the price-per-token was going to be the same for every user which is why I didn't store the tokens per ETH in the contract but just the target ETH funding level instead. But the contract could be rewritten to store tokens per ETH instead and base the calculations on that.

Copyright

Copyright and related rights waived via CC0.

_(Credit to u/pa7x1 for the initial idea behind this approach)._

hiddentao commented 7 years ago

I could do with help on the calculations, particularly the divisions, since I'm not an expert and I know that floating point isn't really supported.

joelcan commented 7 years ago

Would it be possible to submit a refund address at the time you make your contribution? Or, would that make things too unwieldy/expensive?

plutoegg commented 7 years ago

The other drawback of this approach is that it increases the concentration of funds until the refund is issued, and so increases incentive to attack any vulnerabilities of the contract, but given the typical sizes of these crowdsales the incentive is high already.

However in my view it is essential and no reason for all sales not to use this or something similar once battle tested.

pmbauer commented 7 years ago

Instead of refunding 100%, burn a percentage of the returned funds to discourage monopoly-sized bids. In Scenario 2, A monopolizes the supply, but their risk exposure is capped by the ICO; they put in a big bid, own 95% of tokens with half their bid back.

Burning a percentage of the refund proportional to their percent ICO ownership would only encourage a large volume of small bids, indicating a fixed burn rate might be apropos?

taoeffect commented 7 years ago

Instead of refunding 100%, burn a percentage of the returned funds to discourage monopoly-sized bids. In Scenario 2, A monopolizes the supply, but their risk exposure is capped by the ICO; they put in a big bid, own 95% of tokens with half their bid back.

Burning a percentage of the refund proportional to their percent ICO ownership would only encourage a large volume of small bids, indicating a fixed burn rate might be apropos?

+1

Let's:

EDIT to add:

EDIT again to add:

taoeffect commented 7 years ago

For email-only readers, edited my previous comment to add:

hiddentao commented 7 years ago

@joelcan That could be possible, though what additional advantage does that give?

@plutoegg Point noted, though additional measures to deal with that will ironically increase code complexity again.

@pmbauer @taoeffect I like the idea of penalizing excess contributions in some way, though if we opt for burning then even small contributors will lose their ETH, which one may argue unfairly penalizes them. Of course, perhaps a minimum threshold can be set, i.e. only if you contribute over a certain level of ETH will some of your excess get burned.

hiddentao commented 7 years ago

Another thought I had was to implement logarithmic scaling of contributions. For example:

1 eth = 1 token, for example. 9 eth = 1 token 10 eth = 1 token 11 eth = (10/10) + 1 = 2 tokens 21 eth = (20/10) + 1 = 3 tokens 99 eth = (90/10) + 1 = 10 tokens 154 eth = (100/100) + (50/10) + 4 = 10 tokens 5234 eth = (5 + 2 + 3 + 1) = 11 tokens

The idea would be to lessen the impact of larger contributions.

The whale still wins big, but the impact has been lessened. In the non-log contract above the whale would get 99.999%, leaving the small buyer with virtually nothing.

Edit: one concern is the cost of doing the log-scaling calculations.

tawaren commented 7 years ago

@hiddentao The logarithmic scaling does not work because contributors could again create multiple addresses and contribute a small amount with each and thus get more tokens as if when they would contribute all at once. Same goes for the threshold burning approach (multiple addresses all contributing under threshold)

Arachnid commented 7 years ago

Is this EIP material? EIPs are relevant for standardising things that multiple implementations need to interoperate with, which doesn't seem the case here. I'd highly recommend creating a repo for this, with implementation, tests, and discussion there.

hiddentao commented 7 years ago

@tawaren The idea is that it's far more expensive for a contributor to put in large amounts. Without a way of knowing who actually owns a given address it's not possible to have a scheme where each person/entity is restricted to a certain amount of tokens.

@Arachnid Agreed, I've actually made a start over at https://github.com/hiddentao/ethereum-token-sales.

chrisfranko commented 7 years ago

this might be fair but fair isnt what drives the tokensale frenzy

JosefJ commented 7 years ago

@hiddentao I'm currently working on something similar with stretch-goals. The leftover (above certain amount) gets send to charity address (hard coded).

Ad calculations: I'm currently using this:

function toPercentage (uint256 total, uint256 part) internal returns (uint256) {
        return (part*100)/total;
    }

    function fromPercentage(uint256 value, uint256 percentage) internal returns (uint256) {
        return (value*percentage)/100;
    }

100 as dividing on full percent seems fair to me, but you could go event further by just adding more zeros to both functions.

I'm using two functions instead of just one because I'm recalculating the rate in USD (using Oraclize) instead of ethers as it seems to be easier for participants to read.

gaitchs commented 6 years ago

can u merge it to one file so i can launch in mist wallet ?

hiddentao commented 6 years ago

@gaitchs That's pretty easy to do, just replace the following lines:

import "./StandardToken.sol";
import "./SafeMath.sol";

With the actual contents of those files.

AnthonyAkentiev commented 6 years ago

Is there any known ICO that used/is using this approach?

gaitchs commented 6 years ago

What do u mean

On 06-Jan-2018 6:26 PM, "Anthony Akentiev" notifications@github.com wrote:

Is there any known ICO that used/is using this approach?

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/ethereum/EIPs/issues/642#issuecomment-355745297, or mute the thread https://github.com/notifications/unsubscribe-auth/AVeKiq2_1UBFsJpwY9QB9ZOUdEHp4-crks5tH22QgaJpZM4Ns6MC .

AnthonyAkentiev commented 6 years ago

@gaitchs I mean that is there any project already that wants to use this approach (that is of course still "in development")?

I am categorising ICO models in this doc -https://docs.google.com/document/d/1hnMjwaaYUZGch-rprvAtqay9e_ivePCpezBY5ywrrKE/edit?usp=sharing

I've already added the "Proportional Refund Model" to it.

@hiddentao Do you want me to mention you in my doc? Btw, you can help me and add your text/comments to the document! Your feedback will be highly appreciated. Thx

github-actions[bot] commented 2 years ago

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.

github-actions[bot] commented 2 years ago

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.