第九课 如何调试以太坊官网的智能合约众筹案例

1. 文章摘要

【本文目标】 发布并执行通ETH官网的众筹合约代码。 【前置条件】 参考《第七课 技术小白如何在45分钟内发行通证(TOKEN)并上线交易》完成了ColorBay的发行。 【技术收获】 1). 调试成功以太坊官网的智能合约众筹代码 2). REMIX和myetherwallet配合的智能合约代码调试 【实操课程列表】 第一课 如何在WINDOWS环境下搭建以太坊开发环境 第二课 如何实现以太坊最简智能合约“Hello World”的运行 第四课 以太坊开发框架Truffle从入门到实战 第六课 技术小白如何开发一个DAPP区块链应用(以宠物商店为例) 第七课 技术小白如何在45分钟内发行通证(TOKEN)并上线交易 第八课 如何调试以太坊官网的智能合约众筹案例 【说明】未列出的课程为知识普及的非实操类课程,全部区块链文章参考“区块链入口”专栏。git

2.众筹和代币(TOKEN)的投资逻辑

**ICO(Initial Crypto-Token Offering,首次代币众筹)**被认为是区块链生态内生的一种新型投融资方式,概念起源于IPO,只不过募集的货币变为比特币、以太坊等通用数字货币,从而支持项目的开发成本。 目前对于ICO没有统一的定义, 通常而言,ICO指区块链初创项目在区块链平台上发行项目独有的加密代币,投资者经过使用指定的数字货币(如比特币、以太币)购买代币的方式为项目进行众筹融资的行为。代币依项目不一样表明了对项目将来的使用权、投票权等。随着项目成果得到承认,使用人数增长,代币做为交易媒介或权益的价值得到不断提高。 2013年7月募集了5000个比特币的Mastercoin(现名为 Omni)是首个有记录的ICO,而以太坊在2014年7月超过1500万美圆的ICO则开启了ICO快速发展的进程。2015 年,The DAO实现高达1.5亿美圆融资,但后因受黑客攻击而失败。2016年以来,ICO众筹速度快、募集金额不断升高,常出现哄抢一空的状况。github

众筹列表

ICO的流程及关键元素 对于ICO的流程没有统一的概述,通常认为ICO的流程整体能够分红准备期、窗口期、测试期和项目运行四个阶段。这四个阶段的主要内容以下:json

ICO众筹流程图

在ICO中有众多参与者与关键要素,可能包括 ICO 项目发起者、ICO 众筹平台、代币、代币钱包(部分直接就是平台或项目运行平台中的功能)等。 ICO 风险评估方法 针对 ICO 投资的高风险情况,知名的区块链网站 Smith+Crown 在其ICO 手册中给出了几点投资参考,其首要剔提出的投资建议就是关注项目团队和项目执行力。而《财经》杂志也在6月5日的文章中也给出了鉴别风险的参考建议:浏览器

九大问题.jpg

做为技术派,本文再也不探讨技术使用背后的是是非非,只聚焦在众筹代码技术的实现和调试。bash

#3,官网智能合约众筹代码分析 以太坊官网有一段关于ICO众筹的代码和运行介绍,可是其提供的测试环境跟不少人的测试环境不一样,对测试步骤也介绍不全,不少人没法正常运行该智能合约,对其中的功能也不剩了解。 本文对这段代码增长了中文注释,而且对部分不适合的代码作了微调修改,并在下一章节提供了详细的调试步骤说明,供技术小白傻瓜式入门学习。微信

pragma solidity ^0.4.16;

interface token {
    function transfer(address receiver, uint amount);
}

contract Crowdsale {
    address public beneficiary;  // 募资成功后的收款方
    uint public fundingGoal;   // 募资额度
    uint public amountRaised;   // 参与数量
    uint public deadline;      // 募资截止期

    uint public price;    //  token 与以太坊的汇率 , token卖多少钱
    token public tokenReward;   // 要卖的token

    mapping(address => uint256) public balanceOf;

    bool public fundingGoalReached = false;  // 众筹是否达到目标
    bool public crowdsaleClosed = false;   //  众筹是否结束

    /**
    * 事件能够用来跟踪信息
    **/
    event GoalReached(address recipient, uint totalAmountRaised);
    event FundTransfer(address backer, uint amount, bool isContribution);
    event LogAmount(uint amount);

    /**
     * 构造函数, 设置相关属性
     */
    function Crowdsale(
        address ifSuccessfulSendTo,
        uint fundingGoalInEthers,
        uint durationInMinutes,
        uint weiCostOfEachToken,
        address addressOfTokenUsedAsReward) {
            beneficiary = ifSuccessfulSendTo;
            fundingGoal = fundingGoalInEthers * 1 ether;
            deadline = now + durationInMinutes * 1 minutes;
            /*一个TOKEN等同于1个以太坊ETH太贵了,修改官网代码,变为一个TOKEN等同于1个wei*/
            /*price = etherCostOfEachToken * 1 ether;*/
            price = weiCostOfEachToken * 1 wei;
            tokenReward = token(addressOfTokenUsedAsReward);   // 传入已发布的 token 合约的地址来建立实例
    }

    /**
     * 无函数名的Fallback函数,
     * 在向合约转帐时,这个函数会被调用
     */
    function () payable {
        require(!crowdsaleClosed);
        uint amount = msg.value;
        balanceOf[msg.sender] += amount;
        amountRaised += amount;
        LogAmount(amount);/*打款3个ETH,判断此处是3仍是3*10^18*/
        /*官网这个代码有问题,致使打回的币的数量会很是小,此处*1000倍,表示
          1个ETH等于1000个TOKEN/
        /*tokenReward.transfer(msg.sender, amount / price);*/
        tokenReward.transfer(msg.sender, 1000 * (amount / price));
        /*msg.sender对应的是当前运行的外部帐号的地址*/
        FundTransfer(msg.sender, amount, true);
    }

    /**
    *  定义函数修改器modifier(做用和Python的装饰器很类似)
    * 用于在函数执行前检查某种前置条件(判断经过以后才会继续执行该方法)
    * _ 表示继续执行以后的代码
    **/
    modifier afterDeadline() { if (now >= deadline) _; }

    /**
     * 判断众筹是否完成融资目标, 这个方法使用了afterDeadline函数修改器
     * 此段代码不会在deadline后自动运行,而是须要在deadline时间到后人工点击执行
     * 若是在deadline时间前人工点击,会中断,也不会执行函数体代码;
     */
    function checkGoalReached() afterDeadline {
        if (amountRaised >= fundingGoal) {
            fundingGoalReached = true;
            GoalReached(beneficiary, amountRaised);
        }
        crowdsaleClosed = true;
    }


    /**
     * 完成融资目标时,融资款发送到收款方
     * 未完成融资目标时,执行退款
     * 此段代码不会在deadline后自动运行,而是在deadline时间到后人工点击执行
     * 若是在deadline时间前人工点击,会中断,也不会执行函数体代码;
     */
    function safeWithdrawal() afterDeadline {
        /*众筹截止时间后,若是众筹目标没有达到,则执行退款到当前外部帐号*/
        /*官网的这段代码的健壮性不够,要使合约的执行逻辑合理,则须要须要保持当前帐号为众筹打ETH的帐号*/
        if (!fundingGoalReached) {
            uint amount = balanceOf[msg.sender];
            balanceOf[msg.sender] = 0;
            if (amount > 0) {
                if (msg.sender.send(amount)) {
                    FundTransfer(msg.sender, amount, false);
                } else {
                    balanceOf[msg.sender] = amount;
                }
            }
        }
        /*若是众筹目标达到了,而且受益帐号等同于当前帐号,则把众筹到的ETH打给当前帐号*/
        if (fundingGoalReached && beneficiary == msg.sender) {
            if (beneficiary.send(amountRaised)) {
                FundTransfer(beneficiary, amountRaised, false);/**/
            } else {
                //If we fail to send the funds to beneficiary, unlock funders balance
                fundingGoalReached = false;
            }
        }
    }
}
复制代码

函数说明 1,Crowdsale: 众筹合约的构造函数 ifSuccessfulSendTo: 募资成功后的收款方(本案例固定为合约建立者) fundingGoalInEthers: 募资额度, 为了方便咱们仅募3个ether durationInMinutes: 募资时间,为了测试,案例时间设置为10分钟 weiCostOfEachToken:每一个代币的价格, 案例在函数内部放大1000倍,设置为1,实际表示1个ETH须要发放1000个代币; addressOfTokenUsedAsReward: 代币合约地址,案例发布的彩贝币(CB)的地址为"0x5eeec41dc08d7caece17c4a349635934637036f1";app

2,function () payablepayable: 回调函数 没有函数名的payalbe函数为回调函数,意思是往智能合约地址打ETH的时候,则会自动调用该函数执行。 该函数的做用是收到ETH时,给众筹帐号返回1000*n个ETH的彩贝CB代币。这儿代码健壮性不够,无论众筹是否成功,众筹帐号都收到了CB代币。框架

3. checkGoalReached:检查众筹目标是否达到 该调用函数修改器modifier的函数afterDeadline,只是表示截止时间前执行这个代码,实际不会checkGoalReached执行函数体的代码,只会执行afterDeadline的代码后就返回。 该函数的功能是设置fundingGoalReached为true表示众筹目标达到,设置crowdsaleClosed为true表示众筹可关闭。 该函数在截止时间到后要人工执行的,不会自动调用。less

4. safeWithdrawal: 众筹结束执行代码 该调用函数修改器modifier的函数afterDeadline,只是表示截止时间前执行这个代码,实际不会checkGoalReached执行函数体的代码,只会执行afterDeadline的代码后就返回。 若是众筹目标没有达到,则在当前执行帐号为众筹帐号的状况下,把募集的ETH打回给众筹发送的帐号。 若是众筹目标帐号达到,则把募集的ETH打给智能合约建立的帐号。编辑器

4,智能合约众筹代码调试

智能合约执行的代码的坑较多,本文经过一步步的演示,给你们说明在REMIX+MetaMASK的环境下,如何完成该众筹合约代码的成功执行。 ###目标和整体步骤 **目标:**在10分钟内众筹3个ETH,返回3个ColorBay代币 **前提条件:**参考《第七课 技术小白如何在45分钟内发行通证(TOKEN)并上线交易》的实现,代币已经建立成功。 具体步骤: [1] 在ACCOUNT 8上建立众筹智能合约,内容为ACCOUNT8在10分钟内众筹3个ETH,代币为ColorBay。 [2] ACCOUNT 8打3个ColorBay TOKEN给众筹智能合约 [3] ACCOUNT 1打3个ETH给众筹智能合约,同事收到3000个ColorBay [4] 10分钟时间到后,人工执行checkGoalReached翻转众筹智能合约状态 [5] 10分钟时间到后,人工执行safeWithdrawal把众筹ETH打给收益帐户ACCOUNT8

[1] 在ACCOUNT 8上建立众筹智能合约

直接调用REMIX官网编辑器地址便可调用Remix SOLIDITY 编辑器,若是该连接不能打开的话,你可使用国内的小编专用Remix SOLIDITY编辑器 ,把上面的智能合约代码COPY后完成编译。

编译成功
设置MetaMASK的外部帐户为ACCOUNT8(只要是里面有必定的ETH和代币的帐户就行),
image.png

在ACCOUNT 8上建立众筹智能合约,内容为ACCOUNT8在10分钟内众筹3个ETH,代币为ColorBay。每一个代币的价格1wei,代币合约地址为CB彩贝代币智能合约地址"0x5eeec41dc08d7caece17c4a349635934637036f1".不知道这个地址来由的参考第七课的“MetaMask加载TOKEN”章节描述。 “Create”按钮的输入框代码为

"0x3D7DfB80E71096F2c4Ee63C42C4D849F2CBBE363",1, 10, 1,"0x5eeec41dc08d7caece17c4a349635934637036f1"
复制代码

【说明】在remix中输入地址必定要加英文""表示。 具体的配置和操做步骤参考下图:

建立众筹智能合约

智能合约建立成功,点击Remix输出框的对应函数的"Detail"按钮,能够看到一些信息。

智能合约建立成功
咱们能够得到该众筹智能合约的地址为

0x58103623f9ebd9b6a0518160c257e3884ddf0d08

[2] ACCOUNT 8打3个和3000个ColorBay TOKEN给众筹智能合约

转帐3个Color Bay步骤 进入Network Ropsten(infura.io)的转帐环境(因为你懂的缘由误伤,国内有些互联网环境下没法打开时,请使用手机移动热点的方式访问便可打开)

转帐CB设置步骤
确认交易
弹出后,点击支付交易费用
以后,点击网页下方的“Verify Transaction”,能够看到交易信息的区块进度,最终交易成功。 这次交易块的地址为 ropsten.etherscan.io/tx/0xc9013c…
交易信息查看
转帐3000个Color Bay步骤 按照咱们的规划,转让3个Color Bay(CB)是不够的,咱们要相同步骤再转帐一次3000个CB的。 这次交易块的地址为 ropsten.etherscan.io/tx/0xaaf04f… 下面截图介绍一下交易信息内容描述:
交易概述页
跟踪事件页,下面EventLogs对应的是事件函数" event Transfer(address indexed from, address indexed to, uint256 value);" 【说明】事件,用来通知客户端交易发生,不会定义函数体,仅仅记录输入参数。
跟踪事件页

[3]ACCOUNT 1打3个ETH给众筹智能合约,同时收到3000个ColorBay

确保测试帐号ACCOUNT 1有3个ETH测试币,没有的话采用BUY按钮免费买几个。

MetaMASK切换到ACCOUNT 1
浏览器切换到https://www.myetherwallet.com/#send-transaction网站,转帐3个ETH给众筹智能合约地址
转帐3个ETH
转帐确认
提交交易费用

本次交易对应的交易信息网址为https://ropsten.etherscan.io/tx/0x485087d6bdbb92b292349694dd99c3ec698d4e9ddb0d573dda84395a59257ef7#eventlog。可见,已经把3000个代币打回给ACCOUNT1帐号了。参考下图解释下事件描述:

打3个ETH,收到3000个CB
此时查看ACCOUNT1的帐号信息,能够发现增长了3000个CB地址: ropsten.etherscan.io/token/0x5ee… 截图说明:
代币流转信息
【说明】官网的这个代码不够智能,即便没有众筹成功,代币也已经发给众筹者了,这个逻辑不够严谨。 ACCOUNT1减小了3个ETH,查看地址: ropsten.etherscan.io/address/0xd…
ETH的变化
[4] 10分钟时间到后,Meta帐户切换为Account 8,人工执行checkGoalReached翻转众筹智能合约状态

[4] 10分钟时间到后,Meta帐户切换为Account 8,人工执行checkGoalReached翻转众筹智能合约状态

此处的操做做者曾经踩了个大坑。没有认真阅读代码,把时间设置为100分钟,而后在100分钟内点击checkGoalReached函数,发现没有任何变化。

10分钟时间到达后,检查众筹状态

众筹状态结果

[5] 10分钟时间到后,人工执行safeWithdrawal把众筹ETH打给收益帐户ACCOUNT8

众筹结束打币
众筹结束事件查看图

众筹的ETH已到帐

整个交易流程的代币转移能够查看代币合约的连接信息,看看事件能够看到全部的交易记录和帐号: ropsten.etherscan.io/address/0x5…

5,一个具备商用价值的众筹智能合约代码

我的以为官网的这个智能合约不够好。理想中符合逻辑的智能合约应该是时间到后自动去检查众筹金额,到达目标时则自动执行合约,未到达目标时则代币和ETH均返回原始的帐号。 画了业务流程图,可是尚未精力实现这段代码。待有兴趣的码友实现后交流。

商用的众筹智能合约流程

欧阳哥哥实现了该智能合约代码,并给出了详细的测试步骤,有兴趣的同窗可本身分析。《【以太坊开发】众筹智能合约改进》

pragma solidity ^0.4.23;

library SafeMath {
  function mul(uint256 a, uint256 b) internal pure returns (uint256) {
    if (a == 0) {
      return 0;
    }
    uint256 c = a * b;
    assert(c / a == b);
    return c;
  }

  function div(uint256 a, uint256 b) internal pure returns (uint256) {
    // assert(b > 0); // Solidity automatically throws when dividing by 0
    uint256 c = a / b;
    // assert(a == b * c + a % b); // There is no case in which this doesn't hold return c; } function sub(uint256 a, uint256 b) internal pure returns (uint256) { assert(b <= a); return a - b; } function add(uint256 a, uint256 b) internal pure returns (uint256) { uint256 c = a + b; assert(c >= a); return c; } } contract Ownable { address public owner; event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); /** * @dev The Ownable constructor sets the original `owner` of the contract to the sender * account. */ function Ownable() public { owner = msg.sender; } /** * @dev Throws if called by any account other than the owner. */ modifier onlyOwner() { require(msg.sender == owner); _; } /** * @dev Allows the current owner to transfer control of the contract to a newOwner. * @param newOwner The address to transfer ownership to. */ function transferOwnership(address newOwner) public onlyOwner { require(newOwner != address(0)); OwnershipTransferred(owner, newOwner); owner = newOwner; } } contract Pausable is Ownable { event Pause(); event Unpause(); bool public paused = false; /** * @dev Modifier to make a function callable only when the contract is not paused. */ modifier whenNotPaused() { require(!paused); _; } /** * @dev Modifier to make a function callable only when the contract is paused. */ modifier whenPaused() { require(paused); _; } /** * @dev called by the owner to pause, triggers stopped state */ function pause() onlyOwner whenNotPaused public { paused = true; Pause(); } /** * @dev called by the owner to unpause, returns to normal state */ function unpause() onlyOwner whenPaused public { paused = false; Unpause(); } } contract ERC20Basic { uint256 public totalSupply; function balanceOf(address who) public view returns (uint256); function transfer(address to, uint256 value) public returns (bool); event Transfer(address indexed from, address indexed to, uint256 value); } contract ERC20 is ERC20Basic { function allowance(address owner, address spender) public view returns (uint256); function transferFrom(address from, address to, uint256 value) public returns (bool); function approve(address spender, uint256 value) public returns (bool); event Approval(address indexed owner, address indexed spender, uint256 value); } contract BasicToken is ERC20Basic { using SafeMath for uint256; mapping(address => uint256) balances; /** * @dev transfer token for a specified address * @param _to The address to transfer to. * @param _value The amount to be transferred. */ function transfer(address _to, uint256 _value) public returns (bool) { require(_to != address(0)); require(_value <= balances[msg.sender]); // SafeMath.sub will throw if there is not enough balance. balances[msg.sender] = balances[msg.sender].sub(_value); balances[_to] = balances[_to].add(_value); Transfer(msg.sender, _to, _value); return true; } /** * @dev Gets the balance of the specified address. * @param _owner The address to query the the balance of. * @return An uint256 representing the amount owned by the passed address. */ function balanceOf(address _owner) public view returns (uint256 balance) { return balances[_owner]; } } contract StandardToken is ERC20, BasicToken { mapping (address => mapping (address => uint256)) internal allowed; /** * @dev Transfer tokens from one address to another * @param _from address The address which you want to send tokens from * @param _to address The address which you want to transfer to * @param _value uint256 the amount of tokens to be transferred */ function transferFrom(address _from, address _to, uint256 _value) public returns (bool) { require(_to != address(0)); require(_value <= balances[_from]); require(_value <= allowed[_from][msg.sender]); balances[_from] = balances[_from].sub(_value); balances[_to] = balances[_to].add(_value); allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_value); Transfer(_from, _to, _value); return true; } /** * @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender. * * Beware that changing an allowance with this method brings the risk that someone may use both the old * and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this * race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards:
   * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
   * @param _spender The address which will spend the funds.
   * @param _value The amount of tokens to be spent.
   */
  function approve(address _spender, uint256 _value) public returns (bool) {
    allowed[msg.sender][_spender] = _value;
    Approval(msg.sender, _spender, _value);
    return true;
  }

  /**
   * @dev Function to check the amount of tokens that an owner allowed to a spender.
   * @param _owner address The address which owns the funds.
   * @param _spender address The address which will spend the funds.
   * @return A uint256 specifying the amount of tokens still available for the spender.
   */
  function allowance(address _owner, address _spender) public view returns (uint256) {
    return allowed[_owner][_spender];
  }

  /**
   * @dev Increase the amount of tokens that an owner allowed to a spender.
   *
   * approve should be called when allowed[_spender] == 0. To increment
   * allowed value is better to use this function to avoid 2 calls (and wait until
   * the first transaction is mined)
   * From MonolithDAO Token.sol
   * @param _spender The address which will spend the funds.
   * @param _addedValue The amount of tokens to increase the allowance by.
   */
  function increaseApproval(address _spender, uint _addedValue) public returns (bool) {
    allowed[msg.sender][_spender] = allowed[msg.sender][_spender].add(_addedValue);
    Approval(msg.sender, _spender, allowed[msg.sender][_spender]);
    return true;
  }

  /**
   * @dev Decrease the amount of tokens that an owner allowed to a spender.
   *
   * approve should be called when allowed[_spender] == 0. To decrement
   * allowed value is better to use this function to avoid 2 calls (and wait until
   * the first transaction is mined)
   * From MonolithDAO Token.sol
   * @param _spender The address which will spend the funds.
   * @param _subtractedValue The amount of tokens to decrease the allowance by.
   */
  function decreaseApproval(address _spender, uint _subtractedValue) public returns (bool) {
    uint oldValue = allowed[msg.sender][_spender];
    if (_subtractedValue > oldValue) {
      allowed[msg.sender][_spender] = 0;
    } else {
      allowed[msg.sender][_spender] = oldValue.sub(_subtractedValue);
    }
    Approval(msg.sender, _spender, allowed[msg.sender][_spender]);
    return true;
  }

}

contract PausableToken is StandardToken, Pausable {

  function transfer(address _to, uint256 _value) public whenNotPaused returns (bool) {
    return super.transfer(_to, _value);
  }

  function transferFrom(address _from, address _to, uint256 _value) public whenNotPaused returns (bool) {
    return super.transferFrom(_from, _to, _value);
  }

  function approve(address _spender, uint256 _value) public whenNotPaused returns (bool) {
    return super.approve(_spender, _value);
  }

  function increaseApproval(address _spender, uint _addedValue) public whenNotPaused returns (bool success) {
    return super.increaseApproval(_spender, _addedValue);
  }

  function decreaseApproval(address _spender, uint _subtractedValue) public whenNotPaused returns (bool success) {
    return super.decreaseApproval(_spender, _subtractedValue);
  }
}

contract ColorBayTestToken is PausableToken {
    string public name;
    string public symbol;
    uint256 public decimals = 18;

    function ColorBayTestToken(uint256 initialSupply, string tokenName, string tokenSymbol) public {
        totalSupply = initialSupply * 10 ** uint256(decimals);
        balances[msg.sender] = totalSupply;
        name = tokenName;
        symbol = tokenSymbol;
    }
}



//-----------------------------------------------------------------------------




interface token {
    function transfer(address receiver, uint amount);
}

contract Crowdsale is Ownable {
    using SafeMath for uint256;
    address public beneficiary;
    uint public fundingGoal;
    uint public amountRaised;
    uint public deadline;
    uint public price;
    token public tokenReward;
    mapping(address => uint256) public balanceOf;
    bool public fundingGoalReached = false;
    bool public crowdsaleClosed = false;
    
    event GoalReached(address recipient, uint totalAmountRaised);
    event FundTransfer(address backer, uint amount, bool isContribution);
    
    
    address[] public funder;
    
    modifier afterDeadline() { if (now >= deadline) _; }
    
    function Crowdsale(
        address ifSuccessfulSendTo,
        uint fundingGoalInEthers,
        uint durationInMinutes,
        uint finneyCostOfEachToken,
        address addressOfTokenUsedAsReward) public {
            beneficiary = ifSuccessfulSendTo;
            fundingGoal = fundingGoalInEthers.mul(1 ether);
            deadline = now + durationInMinutes.mul(1 minutes);
            price = finneyCostOfEachToken.mul(1 finney);
            tokenReward = token(addressOfTokenUsedAsReward);
    }
    
    event LogPay(address sender, uint value, uint blance, uint amount, bool isClosed);
    function () public payable {
        require(!crowdsaleClosed);
        funder.push(msg.sender);
        balanceOf[msg.sender] = balanceOf[msg.sender].add(msg.value);
        amountRaised = amountRaised.add(msg.value);
        if(amountRaised >= fundingGoal) {
            crowdsaleClosed = true;
            fundingGoalReached = true;
        }
        emit LogPay(msg.sender, msg.value, balanceOf[msg.sender], amountRaised, crowdsaleClosed);
    }
    
    function getThisBalance() public constant returns (uint) {
        return this.balance;
    }
    
    function getNow() public constant returns (uint, uint) {
        return (now, deadline);
    }
    
    function setDeadline(uint minute) public onlyOwner {
        deadline = minute.mul(1 minutes).add(now);
    }
    
    function safeWithdrawal() public onlyOwner afterDeadline {
        if(amountRaised >= fundingGoal) {
            crowdsaleClosed = true;
            fundingGoalReached = true;
            emit GoalReached(beneficiary, amountRaised);
        } else {
            crowdsaleClosed = false;
            fundingGoalReached = false;
        }
        uint i;
        if(fundingGoalReached) {
            if(amountRaised > fundingGoal && funder.length>0) {
                address returnFunder = funder[funder.length.sub(1)];
                uint overFund = amountRaised.sub(fundingGoal);
                if(returnFunder.send(overFund)) {
                    balanceOf[returnFunder] = balanceOf[returnFunder].sub(overFund);
                    amountRaised = fundingGoal;
                }
            }
            for(i = 0; i < funder.length; i++) {
                tokenReward.transfer(funder[i], balanceOf[funder[i]].mul(1 ether).div(price));
                balanceOf[funder[i]] = 0;
            }
            if (beneficiary.send(amountRaised)) {
                emit FundTransfer(beneficiary, amountRaised, false);
            } else {
                fundingGoalReached = false;
            }
            
        } else {
            for(i = 0; i < funder.length; i++) {
                if (balanceOf[funder[i]] > 0 && funder[i].send(balanceOf[funder[i]])) {
                    amountRaised = 0;
                    balanceOf[funder[i]] = 0;
                    emit FundTransfer(funder[i], balanceOf[funder[i]], false);
                }
            }
        }
    }
    
}

复制代码

6,惟链的众筹智能合约

小编由于参与过惟链VeChain的众筹,了解惟链的众筹智能合约代码,这个是纯商用的代码价值。 惟链众筹智能合约地址点击查看 他们的代码和交易信息以下:

pragma solidity ^0.4.11;

contract Owned {

    address public owner;

    function Owned() {
        owner = msg.sender;
    }

    modifier onlyOwner() {
        require(msg.sender == owner);
        _;
    }

    function setOwner(address _newOwner) onlyOwner {
        owner = _newOwner;
    }
}

/**
 * @title SafeMath
 * @dev Math operations with safety checks that throw on error
 */
library SafeMath {
  function mul(uint256 a, uint256 b) internal constant returns (uint256) {
    uint256 c = a * b;
    assert(a == 0 || c / a == b);
    return c;
  }

  function div(uint256 a, uint256 b) internal constant returns (uint256) {
    // assert(b > 0); // Solidity automatically throws when dividing by 0
    uint256 c = a / b;
    // assert(a == b * c + a % b); // There is no case in which this doesn't hold return c; } function sub(uint256 a, uint256 b) internal constant returns (uint256) { assert(b <= a); return a - b; } function add(uint256 a, uint256 b) internal constant returns (uint256) { uint256 c = a + b; assert(c >= a); return c; } function toUINT112(uint256 a) internal constant returns(uint112) { assert(uint112(a) == a); return uint112(a); } function toUINT120(uint256 a) internal constant returns(uint120) { assert(uint120(a) == a); return uint120(a); } function toUINT128(uint256 a) internal constant returns(uint128) { assert(uint128(a) == a); return uint128(a); } } // Abstract contract for the full ERC 20 Token standard // https://github.com/ethereum/EIPs/issues/20 contract Token { /* This is a slight change to the ERC20 base standard. function totalSupply() constant returns (uint256 supply); is replaced with: uint256 public totalSupply; This automatically creates a getter function for the totalSupply. This is moved to the base contract since public getter functions are not currently recognised as an implementation of the matching abstract function by the compiler. */ /// total amount of tokens //uint256 public totalSupply; function totalSupply() constant returns (uint256 supply); /// @param _owner The address from which the balance will be retrieved /// @return The balance function balanceOf(address _owner) constant returns (uint256 balance); /// @notice send `_value` token to `_to` from `msg.sender` /// @param _to The address of the recipient /// @param _value The amount of token to be transferred /// @return Whether the transfer was successful or not function transfer(address _to, uint256 _value) returns (bool success); /// @notice send `_value` token to `_to` from `_from` on the condition it is approved by `_from` /// @param _from The address of the sender /// @param _to The address of the recipient /// @param _value The amount of token to be transferred /// @return Whether the transfer was successful or not function transferFrom(address _from, address _to, uint256 _value) returns (bool success); /// @notice `msg.sender` approves `_addr` to spend `_value` tokens /// @param _spender The address of the account able to transfer the tokens /// @param _value The amount of wei to be approved for transfer /// @return Whether the approval was successful or not function approve(address _spender, uint256 _value) returns (bool success); /// @param _owner The address of the account owning tokens /// @param _spender The address of the account able to transfer the tokens /// @return Amount of remaining tokens allowed to spent function allowance(address _owner, address _spender) constant returns (uint256 remaining); event Transfer(address indexed _from, address indexed _to, uint256 _value); event Approval(address indexed _owner, address indexed _spender, uint256 _value); } /// VEN token, ERC20 compliant contract VEN is Token, Owned { using SafeMath for uint256; string public constant name = "VeChain Token"; //The Token's name
    uint8 public constant decimals = 18;               //Number of decimals of the smallest unit
    string public constant symbol  = "VEN";            //An identifier    

    // packed to 256bit to save gas usage.
    struct Supplies {
        // uint128's max value is about 3e38. // it's enough to present amount of tokens
        uint128 total;
        uint128 rawTokens;
    }

    Supplies supplies;

    // Packed to 256bit to save gas usage.    
    struct Account {
        // uint112's max value is about 5e33. // it's enough to present amount of tokens
        uint112 balance;

        // raw token can be transformed into balance with bonus        
        uint112 rawTokens;

        // safe to store timestamp
        uint32 lastMintedTimestamp;
    }

    // Balances for each account
    mapping(address => Account) accounts;

    // Owner of account approves the transfer of an amount to another account
    mapping(address => mapping(address => uint256)) allowed;

    // bonus that can be shared by raw tokens
    uint256 bonusOffered;

    // Constructor
    function VEN() {
    }

    function totalSupply() constant returns (uint256 supply){
        return supplies.total;
    }

    // Send back ether sent to me
    function () {
        revert();
    }

    // If sealed, transfer is enabled and mint is disabled
    function isSealed() constant returns (bool) {
        return owner == 0;
    }

    function lastMintedTimestamp(address _owner) constant returns(uint32) {
        return accounts[_owner].lastMintedTimestamp;
    }

    // Claim bonus by raw tokens
    function claimBonus(address _owner) internal{      
        require(isSealed());
        if (accounts[_owner].rawTokens != 0) {
            uint256 realBalance = balanceOf(_owner);
            uint256 bonus = realBalance
                .sub(accounts[_owner].balance)
                .sub(accounts[_owner].rawTokens);

            accounts[_owner].balance = realBalance.toUINT112();
            accounts[_owner].rawTokens = 0;
            if(bonus > 0){
                Transfer(this, _owner, bonus);
            }
        }
    }

    // What is the balance of a particular account?
    function balanceOf(address _owner) constant returns (uint256 balance) {
        if (accounts[_owner].rawTokens == 0)
            return accounts[_owner].balance;

        if (bonusOffered > 0) {
            uint256 bonus = bonusOffered
                 .mul(accounts[_owner].rawTokens)
                 .div(supplies.rawTokens);

            return bonus.add(accounts[_owner].balance)
                    .add(accounts[_owner].rawTokens);
        }
        
        return uint256(accounts[_owner].balance)
            .add(accounts[_owner].rawTokens);
    }

    // Transfer the balance from owner's account to another account function transfer(address _to, uint256 _amount) returns (bool success) { require(isSealed()); // implicitly claim bonus for both sender and receiver claimBonus(msg.sender); claimBonus(_to); // according to VEN's total supply, never overflow here
        if (accounts[msg.sender].balance >= _amount
            && _amount > 0) {            
            accounts[msg.sender].balance -= uint112(_amount);
            accounts[_to].balance = _amount.add(accounts[_to].balance).toUINT112();
            Transfer(msg.sender, _to, _amount);
            return true;
        } else {
            return false;
        }
    }

    // Send _value amount of tokens from address _from to address _to
    // The transferFrom method is used for a withdraw workflow, allowing contracts to send
    // tokens on your behalf, for example to "deposit" to a contract address and/or to charge
    // fees in sub-currencies; the command should fail unless the _from account has
    // deliberately authorized the sender of the message via some mechanism; we propose
    // these standardized APIs for approval:
    function transferFrom(
        address _from,
        address _to,
        uint256 _amount
    ) returns (bool success) {
        require(isSealed());

        // implicitly claim bonus for both sender and receiver
        claimBonus(_from);
        claimBonus(_to);

        // according to VEN's total supply, never overflow here if (accounts[_from].balance >= _amount && allowed[_from][msg.sender] >= _amount && _amount > 0) { accounts[_from].balance -= uint112(_amount); allowed[_from][msg.sender] -= _amount; accounts[_to].balance = _amount.add(accounts[_to].balance).toUINT112(); Transfer(_from, _to, _amount); return true; } else { return false; } } // Allow _spender to withdraw from your account, multiple times, up to the _value amount. // If this function is called again it overwrites the current allowance with _value. function approve(address _spender, uint256 _amount) returns (bool success) { allowed[msg.sender][_spender] = _amount; Approval(msg.sender, _spender, _amount); return true; } /* Approves and then calls the receiving contract */ function approveAndCall(address _spender, uint256 _value, bytes _extraData) returns (bool success) { allowed[msg.sender][_spender] = _value; Approval(msg.sender, _spender, _value); //call the receiveApproval function on the contract you want to be notified. This crafts the function signature manually so one doesn't have to include a contract in here just for this.
        //receiveApproval(address _from, uint256 _value, address _tokenContract, bytes _extraData)
        //it is assumed that when does this that the call *should* succeed, otherwise one would use vanilla approve instead.
        //if(!_spender.call(bytes4(bytes32(sha3("receiveApproval(address,uint256,address,bytes)"))), msg.sender, _value, this, _extraData)) { revert(); }
        ApprovalReceiver(_spender).receiveApproval(msg.sender, _value, this, _extraData);
        return true;
    }

    function allowance(address _owner, address _spender) constant returns (uint256 remaining) {
        return allowed[_owner][_spender];
    }

    // Mint tokens and assign to some one
    function mint(address _owner, uint256 _amount, bool _isRaw, uint32 timestamp) onlyOwner{
        if (_isRaw) {
            accounts[_owner].rawTokens = _amount.add(accounts[_owner].rawTokens).toUINT112();
            supplies.rawTokens = _amount.add(supplies.rawTokens).toUINT128();
        } else {
            accounts[_owner].balance = _amount.add(accounts[_owner].balance).toUINT112();
        }

        accounts[_owner].lastMintedTimestamp = timestamp;

        supplies.total = _amount.add(supplies.total).toUINT128();
        Transfer(0, _owner, _amount);
    }
    
    // Offer bonus to raw tokens holder
    function offerBonus(uint256 _bonus) onlyOwner { 
        bonusOffered = bonusOffered.add(_bonus);
        supplies.total = _bonus.add(supplies.total).toUINT128();
        Transfer(0, this, _bonus);
    }

    // Set owner to zero address, to disable mint, and enable token transfer
    function seal() onlyOwner {
        setOwner(0);
    }
}

contract ApprovalReceiver {
    function receiveApproval(address _from, uint256 _value, address _tokenContract, bytes _extraData);
}


// Contract to sell and distribute VEN tokens
contract VENSale is Owned{

    /// chart of stage transition 
    ///
    /// deploy   initialize      startTime                            endTime                 finalize
    ///                              | <-earlyStageLasts-> |             | <- closedStageLasts -> |
    ///  O-----------O---------------O---------------------O-------------O------------------------O------------>
    ///     Created     Initialized           Early             Normal             Closed            Finalized
    enum Stage {
        NotCreated,
        Created,
        Initialized,
        Early,
        Normal,
        Closed,
        Finalized
    }

    using SafeMath for uint256;
    
    uint256 public constant totalSupply         = (10 ** 9) * (10 ** 18); // 1 billion VEN, decimals set to 18

    uint256 constant privateSupply              = totalSupply * 9 / 100;  // 9% for private ICO
    uint256 constant commercialPlan             = totalSupply * 23 / 100; // 23% for commercial plan
    uint256 constant reservedForTeam            = totalSupply * 5 / 100;  // 5% for team
    uint256 constant reservedForOperations      = totalSupply * 22 / 100; // 22 for operations

    // 59%
    uint256 public constant nonPublicSupply     = privateSupply + commercialPlan + reservedForTeam + reservedForOperations;
    // 41%
    uint256 public constant publicSupply = totalSupply - nonPublicSupply;


    uint256 public constant officialLimit = 64371825 * (10 ** 18);
    uint256 public constant channelsLimit = publicSupply - officialLimit;

    // packed to 256bit
    struct SoldOut {
        uint16 placeholder; // placeholder to make struct pre-alloced

        // amount of tokens officially sold out.
        // max value of 120bit is about 1e36, it's enough for token amount uint120 official; uint120 channels; // amount of tokens sold out via channels } SoldOut soldOut; uint256 constant venPerEth = 3500; // normal exchange rate uint256 constant venPerEthEarlyStage = venPerEth + venPerEth * 15 / 100; // early stage has 15% reward uint constant minBuyInterval = 30 minutes; // each account can buy once in 30 minutes uint constant maxBuyEthAmount = 30 ether; VEN ven; // VEN token contract follows ERC20 standard address ethVault; // the account to keep received ether address venVault; // the account to keep non-public offered VEN tokens uint public constant startTime = 1503057600; // time to start sale uint public constant endTime = 1504180800; // tiem to close sale uint public constant earlyStageLasts = 3 days; // early bird stage lasts in seconds bool initialized; bool finalized; function VENSale() { soldOut.placeholder = 1; } /// @notice calculte exchange rate according to current stage /// @return exchange rate. zero if not in sale. function exchangeRate() constant returns (uint256){ if (stage() == Stage.Early) { return venPerEthEarlyStage; } if (stage() == Stage.Normal) { return venPerEth; } return 0; } /// @notice for test purpose function blockTime() constant returns (uint32) { return uint32(block.timestamp); } /// @notice estimate stage /// @return current stage function stage() constant returns (Stage) { if (finalized) { return Stage.Finalized; } if (!initialized) { // deployed but not initialized return Stage.Created; } if (blockTime() < startTime) { // not started yet return Stage.Initialized; } if (uint256(soldOut.official).add(soldOut.channels) >= publicSupply) { // all sold out return Stage.Closed; } if (blockTime() < endTime) { // in sale if (blockTime() < startTime.add(earlyStageLasts)) { // early bird stage return Stage.Early; } // normal stage return Stage.Normal; } // closed return Stage.Closed; } function isContract(address _addr) constant internal returns(bool) { uint size; if (_addr == 0) return false; assembly { size := extcodesize(_addr) } return size > 0; } /// @notice entry to buy tokens function () payable { buy(); } /// @notice entry to buy tokens function buy() payable { // reject contract buyer to avoid breaking interval limit require(!isContract(msg.sender)); require(msg.value >= 0.01 ether); uint256 rate = exchangeRate(); // here don't need to check stage. rate is only valid when in sale
        require(rate > 0);
        // each account is allowed once in minBuyInterval
        require(blockTime() >= ven.lastMintedTimestamp(msg.sender) + minBuyInterval);

        uint256 requested;
        // and limited to maxBuyEthAmount
        if (msg.value > maxBuyEthAmount) {
            requested = maxBuyEthAmount.mul(rate);
        } else {
            requested = msg.value.mul(rate);
        }

        uint256 remained = officialLimit.sub(soldOut.official);
        if (requested > remained) {
            //exceed remained
            requested = remained;
        }

        uint256 ethCost = requested.div(rate);
        if (requested > 0) {
            ven.mint(msg.sender, requested, true, blockTime());
            // transfer ETH to vault
            ethVault.transfer(ethCost);

            soldOut.official = requested.add(soldOut.official).toUINT120();
            onSold(msg.sender, requested, ethCost);        
        }

        uint256 toReturn = msg.value.sub(ethCost);
        if(toReturn > 0) {
            // return over payed ETH
            msg.sender.transfer(toReturn);
        }        
    }

    /// @notice returns tokens sold officially
    function officialSold() constant returns (uint256) {
        return soldOut.official;
    }

    /// @notice returns tokens sold via channels
    function channelsSold() constant returns (uint256) {
        return soldOut.channels;
    } 

    /// @notice manually offer tokens to channel
    function offerToChannel(address _channelAccount, uint256 _venAmount) onlyOwner {
        Stage stg = stage();
        // since the settlement may be delayed, so it's allowed in closed stage require(stg == Stage.Early || stg == Stage.Normal || stg == Stage.Closed); soldOut.channels = _venAmount.add(soldOut.channels).toUINT120(); //should not exceed limit require(soldOut.channels <= channelsLimit); ven.mint( _channelAccount, _venAmount, true, // unsold tokens can be claimed by channels portion blockTime() ); onSold(_channelAccount, _venAmount, 0); } /// @notice initialize to prepare for sale /// @param _ven The address VEN token contract following ERC20 standard /// @param _ethVault The place to store received ETH /// @param _venVault The place to store non-publicly supplied VEN tokens function initialize( VEN _ven, address _ethVault, address _venVault) onlyOwner { require(stage() == Stage.Created); // ownership of token contract should already be this require(_ven.owner() == address(this)); require(address(_ethVault) != 0); require(address(_venVault) != 0); ven = _ven; ethVault = _ethVault; venVault = _venVault; ven.mint( venVault, reservedForTeam.add(reservedForOperations), false, // team and operations reserved portion can't share unsold tokens
            blockTime()
        );

        ven.mint(
            venVault,
            privateSupply.add(commercialPlan),
            true, // private ICO and commercial plan can share unsold tokens
            blockTime()
        );

        initialized = true;
        onInitialized();
    }

    /// @notice finalize
    function finalize() onlyOwner {
        // only after closed stage
        require(stage() == Stage.Closed);       

        uint256 unsold = publicSupply.sub(soldOut.official).sub(soldOut.channels);

        if (unsold > 0) {
            // unsold VEN as bonus
            ven.offerBonus(unsold);        
        }
        ven.seal();

        finalized = true;
        onFinalized();
    }

    event onInitialized();
    event onFinalized();

    event onSold(address indexed buyer, uint256 venAmount, uint256 ethCost);
}
复制代码

参考


咱们在知识星球开通了区块链入门专栏,用于存放课程项目的工程源码等内容,并创建专项微信群用于技术交流,欢迎加入。

相关文章
相关标签/搜索