第九課 如何調試以太坊官網的智能合約衆籌案例

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);
}
複製代碼

參考


咱們在知識星球開通了區塊鏈入門專欄,用於存放課程項目的工程源碼等內容,並創建專項微信羣用於技術交流,歡迎加入。

相關文章
相關標籤/搜索