Go-Ethereum 1.7.2 結合 Mist 0.9.2 實現眾籌合約的例項

Lion發表於2017-11-09

目錄

1、什麼是ICO?

  ICO是以初始產生的數字加密貨幣作為投資回報的一種籌措資金的方式,它的概念源自證券界的Initial Public Offering(IPO,首次公開發行)。

  相較於傳統意義上的IPO,ICO具有可以縮短投融資鏈、降低投融資門檻、流動性佳、全球性投資等優勢。常見的ICO裡,數字貨幣和區塊鏈專案向早期愛好者出售專案代幣。專案團隊通過ICO獲取技術開發和市場擴充資金;而專案愛好者通過ICO支援專案,同時也可在對應代幣進入交易市場後選擇交易退出。

  當你有一個好的想法,需要大家的資金來資助你。你可以使用眾籌合約來發起請求捐款。眾籌合約的基本思路是,你設定一個眾籌目標,在達到目標的最後期限時,如果沒有完成眾籌,所有的捐款將被退回,因此減少了捐贈者的風險。由於程式碼是開放的,可被審計的,也就不需要一個集中的、可信的平臺來擔保,每個捐款的人,只需要支付一定的gas

2、眾籌的獎勵-代幣

  一般來說,那些籌集資金的人在資金籌集和資金管理不善之後,根本就不能說這筆錢是如何使用的,這常常導致專案根本無法交付任何東西。這時我們可以使用智慧合適中投票的方式來做決定,這樣對所有人都是公平的。(這個例子不在本文中介紹,可以參考連結)

  在下面的例子裡,我們在眾籌中,主要解決兩個重要的問題:如何管理和儲存用於獎勵的代幣;籌集獎金後如何使用。

  傳統的眾籌或獎勵記錄通常有一箇中央資料庫,來儲存、跟蹤所有捐助者的過程:誰錯過了眾籌的最後期限了,誰在眾籌過程中捐贈了多少等。與之相反,在區塊鏈中我們將以分散的方式來做這件事,只需建立一個標記來記錄眾籌的每一條記錄、獎勵了多少代幣,後面每個捐贈者都可以得到一個他們可以交易、出售或保留的代幣。如果要給予實物獎勵,生產者只需要交換實物產品的代幣。捐贈者也可以將代幣做為紀念品保留,不管這個眾籌專案有沒有達到它的目標,都可以收藏。

3、眾籌合約的完善

3.1、設定眾籌合約中使用的代幣

  下面是一段簡單的 代幣程式碼,用於發行給捐贈者,注意我們沒有設定代幣的總量,而是一直在增發,在實際使用過程中,可以根據需求自行做限制:

pragma solidity 0.4.16;
/**
 * 一個簡單的代幣合約。
 */
contract token {

    string public standard = 'https://mshk.top';
    string public name; //代幣名稱
    string public symbol; //代幣符號比如'$'
    uint8 public decimals = 2;  //代幣單位,展示的小數點後面多少個0,和以太幣一樣後面是是18個0
    uint256 public totalSupply; //代幣總量
    /* This creates an array with all balances */
    mapping (address => uint256) public balanceOf;

    event Transfer(address indexed from, address indexed to, uint256 value);  //轉帳通知事件


    /* 初始化合約,並且把初始的所有代幣都給這合約的建立者
     * @param _owned 合約的管理者
     * @param tokenName 代幣名稱
     * @param tokenSymbol 代幣符號
     */
    function token(address _owned, string tokenName, string tokenSymbol) {
        //合約的管理者獲得的代幣總量
        balanceOf[_owned] = totalSupply;

        name = tokenName;
        symbol = tokenSymbol;

    }

    /**
     * 轉帳,具體可以根據自己的需求來實現
     * @param  _to address 接受代幣的地址
     * @param  _value uint256 接受代幣的數量
     */
    function transfer(address _to, uint256 _value){
      //從傳送者減掉髮送額
      balanceOf[msg.sender] -= _value;

      //給接收者加上相同的量
      balanceOf[_to] += _value;

      //通知任何監聽該交易的客戶端
      Transfer(msg.sender, _to, _value);
    }

    /**
     * 增加代幣,並將代幣傳送給捐贈新使用者
     * @param  _to address 接受代幣的地址
     * @param  _amount uint256 接受代幣的數量
     */
    function issue(address _to, uint256 _amount) public{
        totalSupply = totalSupply + _amount;
        balanceOf[_to] += _amount;

        //通知任何監聽該交易的客戶端
        Transfer(this, _to, _amount);
    }
 }

  
  上面的程式碼,在Mist中是執行的效果如下:

<embed>

  

3.2、眾籌合約的基本設定

  
  在眾籌合約中,下面幾個變數可以用於設定眾籌以太幣總量、眾籌截止時間、以太幣和代幣的兌換比例,如果不使用單位進行宣告換算,預設在以太坊中,所有的單位都是wei1 ether=10^18 wei

fundingGoal = fundingGoalInEthers * 1 ether;  //眾籌以太幣總量
deadline = now + durationInMinutes * 1 minutes; //眾籌截止時間,單位是分鐘
price = 500 finney; //1個以太幣可以買 2 個代幣

  
  在初始化眾籌合約建構函式的時候,我們會將眾籌合約的帳戶地址,傳遞給代幣做為管理地址,這裡使用的是關鍵字this表示當前合約的地址,也可以傳遞給某個人,初始建立時獎勵給這個人指定量的代幣。

function Crowdsale(
    uint fundingGoalInEthers,
    uint durationInMinutes,
    string tokenName,
    string tokenSymbol
) token(this, tokenName, tokenSymbol)

  

3.3、讓眾籌合約接收以太幣

  在 solidity 中,有一個未命名函式當合約收到以太幣的時候會預設執行。我們加上 payable 關鍵字,就可以給這個合約打款(存入以太幣),然後我們可以根據之前的以太幣與代幣的兌換比例,獎勵給某人一定量的代幣,並進行統計。

/**
 * 預設函式
 *
 * 預設函式,可以向合約直接打款
 */
function () payable {

    //判斷是否關閉眾籌
    require(!crowdsaleClosed);
    uint amount = msg.value;

    //捐款人的金額累加
    balance[msg.sender] += amount;

    //捐款總額累加
    amountRaised += amount;

    //轉帳操作,轉多少代幣給捐款人
    issue(msg.sender, amount / price * 10 ** uint256(decimals));
    FundTransfer(msg.sender, amount, true);
}

  
  上面的程式碼中如果眾籌合約關閉(crowdsaleClosed = true),則不繼續執行,無法交易,這樣做的原因是,如果眾籌合約無論是成功或是已經結束,避免其他人賠錢打款,造成後期的不必要糾紛。

<embed>

  

3.4、檢測眾籌合約是否完成

  檢測眾籌合約是否達到的程式碼如下,如果已籌集的資金額,達到了眾籌的目標值,則結束眾籌,並通知客戶端做記錄

/**
 * 檢測眾籌目標是否已經達到
 */
function checkGoalReached() afterDeadline {
    if (amountRaised >= fundingGoal){
        //達成眾籌目標
        fundingGoalReached = true;
        GoalReached(beneficiary, amountRaised);
    }

    //關閉眾籌
    crowdsaleClosed = true;
}

  

3.5、眾籌結束後的操作

  同時我們還提供了另一個方法,當眾籌失敗的時候,捐贈者可以取回自己的捐助,如果眾籌成功,受益人可以獲得眾籌到的以太幣。

/**
 * 收回資金
 *
 * 檢查是否達到了目標或時間限制,如果有,並且達到了資金目標,
 * 將全部金額傳送給受益人。如果沒有達到目標,每個貢獻者都可以退出
 * 他們貢獻的金額
 */
function safeWithdrawal() afterDeadline {

    //如果沒有達成眾籌目標
    if (!fundingGoalReached) {
        //獲取合約呼叫者已捐款餘額
        uint amount = balance[msg.sender];

        if (amount > 0) {
            //返回合約發起者所有餘額
            msg.sender.transfer(amount);
            FundTransfer(msg.sender, amount, false);
            balance[msg.sender] = 0;
        }
    }

    //如果達成眾籌目標,並且合約呼叫者是受益人
    if (fundingGoalReached && beneficiary == msg.sender) {

        //將所有捐款從合約中給受益人
        beneficiary.transfer(amountRaised);

        FundTransfer(beneficiary, amount, false);
    }
}

  

4、如何使用眾籌合約

  示例中所使用的眾籌合約程式碼:

pragma solidity 0.4.16;
/**
 * 一個簡單的代幣合約。
 */
 contract token {

     string public standard = 'https://mshk.top';
     string public name; //代幣名稱
     string public symbol; //代幣符號比如'$'
     uint8 public decimals = 2;  //代幣單位,展示的小數點後面多少個0,和以太幣一樣後面是是18個0
     uint256 public totalSupply; //代幣總量
     /* This creates an array with all balances */
     mapping (address => uint256) public balanceOf;

     event Transfer(address indexed from, address indexed to, uint256 value);  //轉帳通知事件


     /* 初始化合約,並且把初始的所有代幣都給這合約的建立者
      * @param _owned 合約的管理者
      * @param tokenName 代幣名稱
      * @param tokenSymbol 代幣符號
      */
     function token(address _owned, string tokenName, string tokenSymbol) {
         //合約的管理者獲得的代幣總量
         balanceOf[_owned] = totalSupply;

         name = tokenName;
         symbol = tokenSymbol;

     }

     /**
      * 轉帳,具體可以根據自己的需求來實現
      * @param  _to address 接受代幣的地址
      * @param  _value uint256 接受代幣的數量
      */
     function transfer(address _to, uint256 _value){
       //從傳送者減掉髮送額
       balanceOf[msg.sender] -= _value;

       //給接收者加上相同的量
       balanceOf[_to] += _value;

       //通知任何監聽該交易的客戶端
       Transfer(msg.sender, _to, _value);
     }

     /**
      * 增加代幣,並將代幣傳送給捐贈新使用者
      * @param  _to address 接受代幣的地址
      * @param  _amount uint256 接受代幣的數量
      */
     function issue(address _to, uint256 _amount) public{
         totalSupply = totalSupply + _amount;
         balanceOf[_to] += _amount;

         //通知任何監聽該交易的客戶端
         Transfer(this, _to, _amount);
     }
  }

/**
 * 眾籌合約
 */
contract Crowdsale is token {
    address public beneficiary = msg.sender; //受益人地址,測試時為合約建立者
    uint public fundingGoal;  //眾籌目標,單位是ether
    uint public amountRaised; //已籌集金額數量, 單位是wei
    uint public deadline; //截止時間
    uint public price;  //代幣價格
    bool public fundingGoalReached = false;  //達成眾籌目標
    bool public crowdsaleClosed = false; //眾籌關閉


    mapping(address => uint256) public balance; //儲存眾籌地址

    //記錄已接收的ether通知
    event GoalReached(address _beneficiary, uint _amountRaised);

    //轉帳通知
    event FundTransfer(address _backer, uint _amount, bool _isContribution);

    /**
     * 初始化建構函式
     *
     * @param fundingGoalInEthers 眾籌以太幣總量
     * @param durationInMinutes 眾籌截止,單位是分鐘
     * @param tokenName 代幣名稱
     * @param tokenSymbol 代幣符號
     */
    function Crowdsale(
        uint fundingGoalInEthers,
        uint durationInMinutes,
        string tokenName,
        string tokenSymbol
    ) token(this, tokenName, tokenSymbol){
        fundingGoal = fundingGoalInEthers * 1 ether;
        deadline = now + durationInMinutes * 1 minutes;
        price = 500 finney; //1個以太幣可以買 2 個代幣
    }


    /**
     * 預設函式
     *
     * 預設函式,可以向合約直接打款
     */
    function () payable {

        //判斷是否關閉眾籌
        require(!crowdsaleClosed);
        uint amount = msg.value;

        //捐款人的金額累加
        balance[msg.sender] += amount;

        //捐款總額累加
        amountRaised += amount;

        //轉帳操作,轉多少代幣給捐款人
        issue(msg.sender, amount / price * 10 ** uint256(decimals));
        FundTransfer(msg.sender, amount, true);
    }

    /**
     * 判斷是否已經過了眾籌截止限期
     */
    modifier afterDeadline() { if (now >= deadline) _; }

    /**
     * 檢測眾籌目標是否已經達到
     */
    function checkGoalReached() afterDeadline {
        if (amountRaised >= fundingGoal){
            //達成眾籌目標
            fundingGoalReached = true;
            GoalReached(beneficiary, amountRaised);
        }

        //關閉眾籌
        crowdsaleClosed = true;
    }


    /**
     * 收回資金
     *
     * 檢查是否達到了目標或時間限制,如果有,並且達到了資金目標,
     * 將全部金額傳送給受益人。如果沒有達到目標,每個貢獻者都可以退出
     * 他們貢獻的金額
     */
    function safeWithdrawal() afterDeadline {

        //如果沒有達成眾籌目標
        if (!fundingGoalReached) {
            //獲取合約呼叫者已捐款餘額
            uint amount = balance[msg.sender];

            if (amount > 0) {
                //返回合約發起者所有餘額
                msg.sender.transfer(amount);
                FundTransfer(msg.sender, amount, false);
                balance[msg.sender] = 0;
            }
        }

        //如果達成眾籌目標,並且合約呼叫者是受益人
        if (fundingGoalReached && beneficiary == msg.sender) {

            //將所有捐款從合約中給受益人
            beneficiary.transfer(amountRaised);

            FundTransfer(beneficiary, amount, false);
        }
    }
}

  使用之前的教程《Go-Ethereum 1.7.2 結合 Mist 0.9.2 實現代幣智慧合約的例項》,建立四個帳號,分別是為 主帳號張三李四王五,然後使用已經通過 挖礦 得到以太幣的 主帳號 分別給 張三李四王五每人 100以太幣.

<embed>

  
  開啟 Mist,連線上 geth 以後,點選 合約 -> 部署合約 ,選擇 張三 來建立這個合約,將程式碼貼入,然後右側選擇 Crowdsale,Funding goal in ethers 眾籌以太幣的總數我們設定為 100Duration in minutes 眾籌截止時間,我們設定為 20 分鐘,Token name 代幣的名稱設定為 陌上花開,Token Symbol 代幣符號設定為 $

<embed>

  
  合約建立成功以後,點選右上方的 合約,然後點選 陌上花開 眾籌合約,進行詳情頁,點選右側的 存入以太幣,我們分別使用 李四 購買 70 ether 的代幣、王五 購買 50 ether 的代幣.

<embed>

  
  購買成功以後,可以看到,在 錢包 頁面 李四王五 和帳戶上都多了兩個圖示,說明他們購買的代幣已經到帳,並且相關的餘額也發生了變化。而 張三 的比特幣是 99,98 ether,因為只要執行合約就需要花費一些 gas

<embed>

  
  之前我們設定的代幣小數後面 2個0,而1個以太幣可以購買2個代幣,看下圖 李四王五 兩個人購買的數量上是對的。

<embed>

  
  在 眾籌合約截止時間以後,我們呼叫 afterDeadline 方法,來判斷眾籌目標是否達成。如果嚴謹一些應該只允許管理員來操作這個方法,因為是測試,寫的隨意一些,任何人都可以呼叫。呼叫成功後,在眾籌合約的詳情頁,Funding goal reachedCrowdsale closed 的值都為Yes

<embed>

  
  如果眾籌失敗或者是在眾籌過程中,捐贈人反悔了,想要收回捐贈的以太幣,可以呼叫 safeWithdrawal 方法,取回之前捐贈的以太幣。如果眾籌成功後,是無法取回的。

  張三 的比特幣是99,98 ether,在眾籌結束後。呼叫 safeWithdrawal 方法,會將眾籌合約中的所有比特幣,轉到 張三 的帳戶地址下。
  
<embed>

  
  一個簡單的眾籌合約做完了。通過這個例子,可以發散思維做其他很多智慧合約,比如實名/匿名投票、淘寶交易合約等。

5、擴充套件閱讀

ico眾籌合約程式碼分析


博文作者:迦壹
部落格地址:Go-Ethereum 1.7.2 結合 Mist 0.9.2 實現眾籌合約的例項
轉載宣告:可以轉載, 但必須以超連結形式標明文章原始出處和作者資訊及版權宣告,謝謝合作!


相關文章