Fair Token Sales: Low Caps, No Lockouts

Token sales have a quandary: if they don't set a cap, or set a very high cap, then people might go overboard and send way more money than required, even enough to be a global risk like TheDAO. But if a sale sets a low cap, the tokens can sell out fast and people get locked out.

I think I have a solution. Nobody gets locked out and everyone gets tokens in proportion to their contribution, but the cap can be set as low as desired.

All you do is let people contribute as much as they like, then give refunds in proportion to how much you exceed the cap. E.g. if your cap is $1 million and you collect $2 million, then everybody gets half their contribution back. If you collect $3 million then everyone gets 2/3 back, and so on.

You still might temporarily collect a lot of ether, which isn't ideal. But the contract is simple enough to be thoroughly reviewed and tested. I haven't yet done that for the following code but here's how simple it is:

pragma solidity 0.4.2;

contract Capfund {
    address public owner;
    uint public finalblock;
    uint public target;
    uint public raised;
    bool funded;
    mapping(address => uint) public balances;
    mapping(address => bool) public refunded;

    function Capfund(uint _blocks, uint _target) {
        owner = msg.sender;
        finalblock = block.number + _blocks;
        target = _target;
    }

    function _deposit() private {
        if (block.number > finalblock) throw;
        balances[msg.sender] += msg.value;
        raised += msg.value;
    }

    function deposit() payable {
        _deposit();
    }

    function() payable {
        _deposit();
    }

    function withdrawRefund() {
        if (block.number <= finalblock) throw;
        if (raised <= target) throw;
        if (refunded[msg.sender]) throw;

        uint deposit = balances[msg.sender];
        uint keep = (deposit * target) / raised;
        uint refund = deposit - keep;
        if (refund > this.balance) refund = this.balance;
        
        refunded[msg.sender] = true;
        if (!msg.sender.call.value(refund)()) throw;
    }

    function fundOwner() {
        if (block.number <= finalblock) throw;
        if (funded) throw;
        funded = true;
        if (raised < target) {
            if (!owner.call.value(raised)()) throw;
        } else {
            if (!owner.call.value(target)()) throw;
        }
    }
}

It'd be good to add some events too, of course.

If you really want to make sure you don't collect excess ether even temporarily, it's possible, but there are tradeoffs. An idea I came up with is to have people just pledge, do the calculations based on the pledges, and let them donate the calculated amount. But then anyone who doesn't follow through can lower the total amount raised, even if there's plenty of excess demand. You can mitigate this a bit by at least checking that pledger addresses have the funds available, and setting the cap a little high to compensate for losses, but an angry whale could still do damage.

Along with a bunch of other interesting ideas, reddit user Dunning_Krugerrands suggested a neat compromise: require a deposit of, say, 5% of the pledge. If someone doesn't follow through before a deadline, they at least lose the deposit. The deposit percentage could be any amount, for whatever balance of risks you prefer.