第九課 如何除錯以太坊官網的智慧合約眾籌案例

筆名輝哥發表於2018-11-15

第九課 如何除錯以太坊官網的智慧合約眾籌案例

1. 文章摘要

【本文目標】 釋出並執行通ETH官網的眾籌合約程式碼。 【前置條件】 參考《第七課 技術小白如何在45分鐘內發行通證(TOKEN)並上線交易》完成了ColorBay的發行。 【技術收穫】 1). 除錯成功以太坊官網的智慧合約眾籌程式碼 2). REMIX和myetherwallet配合的智慧合約程式碼除錯 【實操課程列表】 第一課 如何在WINDOWS環境下搭建以太坊開發環境 第二課 如何實現以太坊最簡智慧合約“Hello World”的執行 第四課 以太坊開發框架Truffle從入門到實戰 第六課 技術小白如何開發一個DAPP區塊鏈應用(以寵物商店為例) 第七課 技術小白如何在45分鐘內發行通證(TOKEN)並上線交易 第八課 如何除錯以太坊官網的智慧合約眾籌案例 【說明】未列出的課程為知識普及的非實操類課程,所有區塊鏈文章參考“區塊鏈入口”專欄。

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眾籌速度快、募集金額不斷升高,常出現哄搶一空的情況。

眾籌列表

ICO的流程及關鍵元素 對於ICO的流程沒有統一的概述,一般認為ICO的流程總體可以分成準備期、視窗期、測試期和專案執行四個階段。這四個階段的主要內容如下:

ICO眾籌流程圖

在ICO中有眾多參與者與關鍵要素,可能包括 ICO 專案發起者、ICO 眾籌平臺、代幣、代幣錢包(部分直接就是平臺或專案執行平臺中的功能)等。 ICO 風險評估方法 針對 ICO 投資的高風險狀況,知名的區塊鏈網站 Smith+Crown 在其ICO 手冊中給出了幾點投資參考,其首要剔提出的投資建議就是關注專案團隊和專案執行力。而《財經》雜誌也在6月5日的文章中也給出了鑑別風險的參考建議:

九大問題.jpg

作為技術派,本文不再探討技術使用背後的是是非非,只聚焦在眾籌程式碼技術的實現和除錯。

#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";

2,function () payablepayable: 回撥函式 沒有函式名的payalbe函式為回撥函式,意思是往智慧合約地址打ETH的時候,則會自動呼叫該函式執行。 該函式的作用是收到ETH時,給眾籌賬號返回1000*n個ETH的彩貝CB代幣。這兒程式碼健壯性不夠,不管眾籌是否成功,眾籌賬號都收到了CB代幣。

3. checkGoalReached:檢查眾籌目標是否達到 該呼叫函式修改器modifier的函式afterDeadline,只是表示截止時間前執行這個程式碼,實際不會checkGoalReached執行函式體的程式碼,只會執行afterDeadline的程式碼後就返回。 該函式的功能是設定fundingGoalReached為true表示眾籌目標達到,設定crowdsaleClosed為true表示眾籌可關閉。 該函式在截止時間到後要人工執行的,不會自動呼叫。

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

參考


我們在知識星球開通了區塊鏈入門專欄,用於存放課程專案的工程原始碼等內容,並建立專項微信群用於技術交流,歡迎加入。

第九課 如何除錯以太坊官網的智慧合約眾籌案例


相關文章