Go-Ethereum 1.7.2 結合 Mist 0.9.2 實現代幣智慧合約的例項

Lion發表於2017-11-09

目錄

1、什麼是 Mist

  Mist是以太坊官方的線上錢包管理工具。通過 Mist 我們可以很方便的連線上我們的私有網路,從而更好的開發、除錯、測試我們的智慧合約。既可以連線生產網路、測試網路,更加可以通過設定引數的方式,連線我們自己的私有網路。

  Mist 在通過 geth.ipc 檔案連線後,就和 Geth 所建立的網路完全契合在一起了,在 Mist 上部署的合約,實際上也就是部署在了 Geth 網路上。Geth 網路上新建賬號,也可以在 Mist 這個工具上看到。

  通過 Mist,我們向大家更詳細的講解了以太坊的核心概念,包括:區塊、Transaction、Gas、賬戶、合約、合約中的建構函式,變數以及方法。

  

2、Mist 在哪裡下載?

  開發版的 Mist 在這裡下載:https://github.com/ethereum/mist

  如果要在生產環境上操作,可以直接在以太坊官網下載錢包工具:https://ethereum.org/

  

3、Mist 有哪些依賴?

  需要有以下元件:

  • Node.js v7.x (use the prefered installation method for your OS)
  • Meteor javascript app framework
  • Yarn package manager
  • Electron v1.7.9 cross platform desktop app framework
  • Gulp build and automation system

  

4、如何安裝 Mist?

  在安裝 Mist 前,要先安裝依賴的工具包。
  

4.1、安裝 Mist 依賴工具包

➜ /Users/lion >curl https://install.meteor.com/
➜ /Users/lion >brew install yarn
➜ /Users/lion >yarn global add electron@1.7.9
➜ /Users/lion >yarn global add gulp

yarn其他平臺的安裝方法,可以參考:https://yarnpkg.com/zh-Hans/docs/install

  

4.2、安裝 Mist

➜ /Users/lion/my_project/_eth >git clone https://github.com/ethereum/mist.git
➜ /Users/lion/my_project/_eth >cd mist
➜ /Users/lion/my_project/_eth/mist git:(develop) >git checkout v0.9.2
➜ /Users/lion/my_project/_eth/mist git:(369713b) >yarn

  

4.3、啟動 Mist,連線到 Geth

  新開一個視窗,用以下命令執行 Mist 的後臺程式:

➜ /Users/lion/my_project/_eth/mist git:(369713b) >cd interface
➜ /Users/lion/my_project/_eth/mist/interface git:(369713b) >meteor --no-release-check
[[[[[ ~/my_project/_eth/mist/interface ]]]]]

=> Started proxy.
=> Started MongoDB.
=> Started your app.

=> App running at: http://localhost:3000/
=> Client modified -- refreshing

第一次執行會慢一些,會啟動proxy、MongoDB等程式,同時下載一些依賴元件

  
  在啟動 Mist 之前,我們要先啟動 Geth,參考:使用 Go-Ethereum 1.7.2搭建以太坊私有鏈

  我們啟用以太坊私有鏈以後,在 ./chain 目錄上會建立私有鏈的一些資料,裡面有一個 geth.ipc 檔案。

➜ /Users/lion/my_project/_eth/test >ll chain
total 88
drwxr-xr-x  7 lion  staff    224 Oct 24 12:21 geth
srw-------  1 lion  staff      0 Oct 24 12:24 geth.ipc
-rw-------  1 lion  staff  43213 Oct 24 12:08 history
drwx------  4 lion  staff    128 Oct 22 14:57 keystore

  
  在原來的視窗中執行以下命令,用 Mist 連線我們用 Geth 啟動的私有鏈:

➜ /Users/lion/my_project/_eth/mist git:(369713b) >yarn dev:electron --rpc /Users/lion/my_project/_eth/test/chain/geth.ipc

如果正常交易以太坊的以太幣,可以在官網直接下載以太坊錢包使用:https://ethereum.org/

  
  如果在另一臺機器是使用RPC方式執行,也可以使用下面的方法連線到 Geth

➜ /Users/lion/my_project/_eth/mist git:(369713b) >yarn dev:electron --rpc http://localhost:8545

  
  執行完以後,會開啟一個比較像App的網頁,如下圖:

<embed>

在之前的文章中我們建立的帳戶,經過 Mist 連線後,也可以在視覺化的介面中看到

  
  在 Mist 的介面中,點選傳送,可以從一個帳戶地址,向另一個帳戶地址,轉移以太幣。

  

5、使用 Mist 部署一個簡單的智慧合約

  在 Mist 上點選右側的合約。首先要選擇一個帳戶來生成合約,用於支付部署合約的費用,以後是誰呼叫誰來支付費用。(如果在公有鏈上部署智慧合約,需要花費一定的以太幣)。

  下面是一個最簡單的合約程式碼,主要介紹可以看註釋:  

pragma solidity 0.4.16;

/**
 * @title 基礎版的代幣合約
 */
contract token {
    /* 公共變數 */
    string public standard = 'https://mshk.top';

    /*記錄所有餘額的對映*/
    mapping (address => uint256) public balanceOf;

    /* 初始化合約,並且把初始的所有代幣都給這合約的建立者
     * @param initialSupply 代幣的總數
     */
    function token(uint256 initialSupply) {
        //給指定帳戶初始化代幣總量,初始化用於獎勵合約建立者
        balanceOf[msg.sender] = initialSupply;
    }

    /**
     * 私有方法從一個帳戶傳送給另一個帳戶代幣
     * @param  _from address 傳送代幣的地址
     * @param  _to address 接受代幣的地址
     * @param  _value uint256 接受代幣的數量
     */
    function _transfer(address _from, address _to, uint256 _value) internal {

      //避免轉帳的地址是0x0
      require(_to != 0x0);

      //檢查傳送者是否擁有足夠餘額
      require(balanceOf[_from] >= _value);

      //檢查是否溢位
      require(balanceOf[_to] + _value > balanceOf[_to]);

      //儲存資料用於後面的判斷
      uint previousBalances = balanceOf[_from] + balanceOf[_to];

      //從傳送者減掉髮送額
      balanceOf[_from] -= _value;

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

      //判斷買、賣雙方的資料是否和轉換前一致
      assert(balanceOf[_from] + balanceOf[_to] == previousBalances);

    }

    /**
     * 從主帳戶合約呼叫者傳送給別人代幣
     * @param  _to address 接受代幣的地址
     * @param  _value uint256 接受代幣的數量
     */
    function transfer(address _to, uint256 _value) public {
        _transfer(msg.sender, _to, _value);
    }
}

  
  接下來我們將上面的合約程式碼,通過Mist部署到我們的私有鏈的。

  首先如下圖中,點選合約->部署新合約

<embed>

  
  然後如下面的兩張圖,選擇建立合約的帳戶,將上面的程式碼貼到SOLIDITY合約原始程式碼處,在右側選擇要部署的合約token,在token的下面Initial Supply處,輸入我們要初始化的金額5000用於獎勵合約建立者,然後在頁面的最下面,點選部署,的彈出的層中,輸入建立合約這個帳號的密碼,輸入正確以後,合約建立成功。

<embed>

<embed>

  
  建立一個合約以後,再點選 Mist 右側的合約,然後在 定製化合約 的下面,可以看到我們剛剛建立的合約 TOKEN ,如下圖:

<embed>

注意,在合約下面也有一串0x開頭的地址,這個就相當於一個錢包的地址。在以太坊中,合約也相當於一個帳戶

  
  點選 TOKEN ,進入到合約裡。在下面的 Balance Of 處,輸入剛才建立合約帳戶的地址0xa18e688326ab13b6147ce3ca2213db143a4ec2ee,可以看到是有5000個代幣在裡面,如下圖:

<embed>

  
  在代幣建立的時候,初始值我們設定的是5000,所以只有建立帳戶的地址有代幣,而輸入其他帳戶的地址,如0xc81896af13449a82f22699311df4ec4b48c07718,是沒有值的。

  接下來,我們向0xc81896af13449a82f22699311df4ec4b48c07718這個帳戶地址,轉入一些代幣。點選右側選擇函式->選擇Transfer,在_to中輸入0xc81896af13449a82f22699311df4ec4b48c07718,在_value中輸入500,然後點選執行,在彈出的層中輸入呼叫合約帳戶的密碼,確認操作。

<embed>

<embed>

我們能夠看到實際呼叫合約的過程中,會花費一定的gasgas和以太幣會根據區塊的算力有一個計算公式,gas一般用於獎勵給挖礦者

  
  在 transfer 方法中,我們設定了,只有合約的呼叫者msg.sender才能向指定地址轉移代幣。

/**
 * 從主帳戶合約呼叫者傳送給別人代幣
 * @param  _to address 接受代幣的地址
 * @param  _value uint256 接受代幣的數量
 */
function transfer(address _to, uint256 _value) public {
    _transfer(msg.sender, _to, _value);
}

  
  這時,再次進入合約,在Balance Of處,輸入兩個帳戶的地址,可以看到,餘額都發生的變化,如下圖:

<embed>

  

6、改善代幣

  通過上面的操作,我們已經可以將合約程式碼,通過 Mist 部署到我們建立的私有鏈中,同樣如果部署到生產環境,只需要連上以太坊的網路,同樣的方法也可以將你的合約,部署到生產環境中,不過要根據程式碼的大小,花費一些以太幣。

  實際使用過程中,交易的過程,需要通知到客戶端,並且記錄到區塊中,我們可以使用event事件來指定,如下程式碼進行宣告:

//在區塊鏈上建立一個事件,用以通知客戶端
event Transfer(address indexed from, address indexed to, uint256 value);

  
  設定一些代幣的基本資訊

/* 公共變數 */
string public standard = 'https://mshk.top';
string public name; //代幣名稱
string public symbol; //代幣符號比如'$'
uint8 public decimals = 18;  //代幣單位,展示的小數點後面多少個0,和以太幣一樣後面是是18個0
uint256 public totalSupply; //代幣總量

  
  某些特定的場景中,不允許某個帳戶花費超過指定的上限,避免大額支出,我們可以新增一個 approve 方法,來設定一個允許支出最大金額的列表。

mapping (address => mapping (address => uint256)) public allowance;

/**
 * 設定帳戶允許支付的最大金額
 *
 * 一般在智慧合約的時候,避免支付過多,造成風險
 *
 * @param _spender 帳戶地址
 * @param _value 金額
 */
function approve(address _spender, uint256 _value) public returns (bool success) {
    allowance[msg.sender][_spender] = _value;
    return true;
}

  
  同樣在 solidity 中,合約之間也可以相互呼叫,我們可以增加一個 approveAndCall 方法,用於在設定帳戶最大支出金額後,可以做一些其他操作。


interface tokenRecipient { function receiveApproval(address _from, uint256 _value, address _token, bytes _extraData) public; }

/**
 * 設定帳戶允許支付的最大金額
 *
 * 一般在智慧合約的時候,避免支付過多,造成風險
 *
 * @param _spender 帳戶地址
 * @param _value 金額
 */
function approve(address _spender, uint256 _value) public returns (bool success) {
    allowance[msg.sender][_spender] = _value;
    return true;
}

/**
 * 設定帳戶允許支付的最大金額
 *
 * 一般在智慧合約的時候,避免支付過多,造成風險,加入時間引數,可以在 tokenRecipient 中做其他操作
 *
 * @param _spender 帳戶地址
 * @param _value 金額
 * @param _extraData 操作的時間
 */
function approveAndCall(address _spender, uint256 _value, bytes _extraData) public returns (bool success) {
    tokenRecipient spender = tokenRecipient(_spender);
    if (approve(_spender, _value)) {
        spender.receiveApproval(msg.sender, _value, this, _extraData);
        return true;
    }
}

  
  我們可以增加一個 burn 方法,用於管理員減去指定帳戶的指定金額。進行該方法操作時,通知客戶端記錄到區塊鏈中。


//減去使用者餘額事件
event Burn(address indexed from, uint256 value);  

/**
 * 減少代幣呼叫者的餘額
 *
 * 操作以後是不可逆的
 *
 * @param _value 要刪除的數量
 */
function burn(uint256 _value) public returns (bool success) {
    //檢查帳戶餘額是否大於要減去的值
    require(balanceOf[msg.sender] >= _value);   // Check if the sender has enough

    //給指定帳戶減去餘額
    balanceOf[msg.sender] -= _value;

    //代幣問題做相應扣除
    totalSupply -= _value;

    Burn(msg.sender, _value);
    return true;
}

/**
 * 刪除帳戶的餘額(含其他帳戶)
 *
 * 刪除以後是不可逆的
 *
 * @param _from 要操作的帳戶地址
 * @param _value 要減去的數量
 */
function burnFrom(address _from, uint256 _value) public returns (bool success) {

    //檢查帳戶餘額是否大於要減去的值
    require(balanceOf[_from] >= _value);

    //檢查 其他帳戶 的餘額是否夠使用
    require(_value <= allowance[_from][msg.sender]);

    //減掉代幣
    balanceOf[_from] -= _value;
    allowance[_from][msg.sender] -= _value;

    //更新總量
    totalSupply -= _value;
    Burn(_from, _value);
    return true;
}

  
  完整的程式碼如下:

pragma solidity 0.4.16;

interface tokenRecipient { function receiveApproval(address _from, uint256 _value, address _token, bytes _extraData) public; }


/**
 * @title 基礎版的代幣合約
 */
contract token {
    /* 公共變數 */
    string public standard = 'https://mshk.top';
    string public name; //代幣名稱
    string public symbol; //代幣符號比如'$'
    uint8 public decimals = 18;  //代幣單位,展示的小數點後面多少個0,和以太幣一樣後面是是18個0
    uint256 public totalSupply; //代幣總量

    /*記錄所有餘額的對映*/
    mapping (address => uint256) public balanceOf;
    mapping (address => mapping (address => uint256)) public allowance;

    /* 在區塊鏈上建立一個事件,用以通知客戶端*/
    event Transfer(address indexed from, address indexed to, uint256 value);  //轉帳通知事件
    event Burn(address indexed from, uint256 value);  //減去使用者餘額事件

    /* 初始化合約,並且把初始的所有代幣都給這合約的建立者
     * @param initialSupply 代幣的總數
     * @param tokenName 代幣名稱
     * @param tokenSymbol 代幣符號
     */
    function token(uint256 initialSupply, string tokenName, string tokenSymbol) {

        //初始化總量
        totalSupply = initialSupply * 10 ** uint256(decimals);    //以太幣是10^18,後面18個0,所以預設decimals是18

        //給指定帳戶初始化代幣總量,初始化用於獎勵合約建立者
        balanceOf[msg.sender] = totalSupply;

        name = tokenName;
        symbol = tokenSymbol;

    }


    /**
     * 私有方法從一個帳戶傳送給另一個帳戶代幣
     * @param  _from address 傳送代幣的地址
     * @param  _to address 接受代幣的地址
     * @param  _value uint256 接受代幣的數量
     */
    function _transfer(address _from, address _to, uint256 _value) internal {

      //避免轉帳的地址是0x0
      require(_to != 0x0);

      //檢查傳送者是否擁有足夠餘額
      require(balanceOf[_from] >= _value);

      //檢查是否溢位
      require(balanceOf[_to] + _value > balanceOf[_to]);

      //儲存資料用於後面的判斷
      uint previousBalances = balanceOf[_from] + balanceOf[_to];

      //從傳送者減掉髮送額
      balanceOf[_from] -= _value;

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

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

      //判斷買、賣雙方的資料是否和轉換前一致
      assert(balanceOf[_from] + balanceOf[_to] == previousBalances);

    }

    /**
     * 從主帳戶合約呼叫者傳送給別人代幣
     * @param  _to address 接受代幣的地址
     * @param  _value uint256 接受代幣的數量
     */
    function transfer(address _to, uint256 _value) public {
        _transfer(msg.sender, _to, _value);
    }

    /**
     * 從某個指定的帳戶中,向另一個帳戶傳送代幣
     *
     * 呼叫過程,會檢查設定的允許最大交易額
     *
     * @param  _from address 傳送者地址
     * @param  _to address 接受者地址
     * @param  _value uint256 要轉移的代幣數量
     * @return success        是否交易成功
     */
    function transferFrom(address _from, address _to, uint256 _value) returns (bool success) {
        //檢查傳送者是否擁有足夠餘額
        require(_value <= allowance[_from][msg.sender]);   // Check allowance

        allowance[_from][msg.sender] -= _value;

        _transfer(_from, _to, _value);

        return true;
    }

    /**
     * 設定帳戶允許支付的最大金額
     *
     * 一般在智慧合約的時候,避免支付過多,造成風險
     *
     * @param _spender 帳戶地址
     * @param _value 金額
     */
    function approve(address _spender, uint256 _value) public returns (bool success) {
        allowance[msg.sender][_spender] = _value;
        return true;
    }

    /**
     * 設定帳戶允許支付的最大金額
     *
     * 一般在智慧合約的時候,避免支付過多,造成風險,加入時間引數,可以在 tokenRecipient 中做其他操作
     *
     * @param _spender 帳戶地址
     * @param _value 金額
     * @param _extraData 操作的時間
     */
    function approveAndCall(address _spender, uint256 _value, bytes _extraData) public returns (bool success) {
        tokenRecipient spender = tokenRecipient(_spender);
        if (approve(_spender, _value)) {
            spender.receiveApproval(msg.sender, _value, this, _extraData);
            return true;
        }
    }

    /**
     * 減少代幣呼叫者的餘額
     *
     * 操作以後是不可逆的
     *
     * @param _value 要刪除的數量
     */
    function burn(uint256 _value) public returns (bool success) {
        //檢查帳戶餘額是否大於要減去的值
        require(balanceOf[msg.sender] >= _value);   // Check if the sender has enough

        //給指定帳戶減去餘額
        balanceOf[msg.sender] -= _value;

        //代幣問題做相應扣除
        totalSupply -= _value;

        Burn(msg.sender, _value);
        return true;
    }

    /**
     * 刪除帳戶的餘額(含其他帳戶)
     *
     * 刪除以後是不可逆的
     *
     * @param _from 要操作的帳戶地址
     * @param _value 要減去的數量
     */
    function burnFrom(address _from, uint256 _value) public returns (bool success) {

        //檢查帳戶餘額是否大於要減去的值
        require(balanceOf[_from] >= _value);

        //檢查 其他帳戶 的餘額是否夠使用
        require(_value <= allowance[_from][msg.sender]);

        //減掉代幣
        balanceOf[_from] -= _value;
        allowance[_from][msg.sender] -= _value;

        //更新總量
        totalSupply -= _value;
        Burn(_from, _value);
        return true;
    }

}

  

6.1、如何部署

  如上面的部署中,我們將完整的程式碼,貼到 Mistsolidity合約原始程式碼 處,在右側選擇 tokenInitial Supply 輸入初始金額5000Token name 輸入我們的代幣名稱 陌上花開Token symbol 代幣符號我們輸入 $$,然後點選 部署,輸入部署帳戶的密碼。

<embed>

  
  部署合約以後,我們能夠在合約頁面看到剛才建立的合約。

<embed>

  
  點選合約名稱,可以看到合約的一些基本資訊,以及合約和操作函式

<embed>

  
  我們能夠在 Mist 上方的 錢包 中的主帳號這裡看到有個小圖示,說明主帳戶已經有了代幣,其他帳戶是沒有這個圖示的

<embed>

  
  點選進入主帳號以後,我們就可以看到主帳戶已經擁有的代幣和以太幣的數量,因為我們是參考以太幣進行設定,最小單位是wei,所以小數點後面有18個0。

<embed>

  
  接下來,我們向另一個帳戶傳送一些 陌上花開 幣,點選 Mist 上方的傳送,輸入傳送的帳戶地址,輸入數量 500,選擇傳送的是 陌上花開 幣,點選傳送,如下圖

<embed>

  
  再次回到錢包中,我們可以看到,另一個帳戶也有了一個代幣的圖示,說明代幣已經轉入成功。

<embed>

  
  現在你擁有了自己的代幣,也可以做轉入轉出操作。可以被用於價值交換,或者工作時間追蹤或者其他專案。

  

7、高階版的代幣功能

  一般的代幣可以不設定管理者,就是所謂的去中心化。實際使用過程中,可能需要給予挖礦等功能,讓別人能夠購買你的代幣,那麼我們就需要設定一個帳戶地址做為這個代幣合約的管理者。

**
 * owned 是一個管理者
 */
contract owned {
    address public owner;

    /**
     * 初臺化建構函式
     */
    function owned() {
        owner = msg.sender;
    }

    /**
     * 判斷當前合約呼叫者是否是管理員
     */
    modifier onlyOwner {
        require (msg.sender == owner);
        _;
    }

    /**
     * 指派一個新的管理員
     * @param  newOwner address 新的管理員帳戶地址
     */
    function transferOwnership(address newOwner) onlyOwner {
        owner = newOwner;
    }
}

  
  上面的程式碼是一個非常簡單的合約,我們可以在後面的程式碼中,使用 繼承 來實現後續的功能。

/**
 * @title 高階版代幣
 * 增加凍結使用者、挖礦、根據指定匯率購買(售出)代幣價格的功能
 */
contract MyAdvancedToken is owned{}

  在 MyAdvancedToken 的所有方法中,可以使用 owned 的變數 ownermodifier onlyOwner

  

7.1、去中心化的管理者

  我們也可以在建構函式中設定是否需要一個去中心化的管理者。

/*初始化合約,並且把初始的所有的令牌都給這合約的建立者
 * @param initialSupply 所有幣的總數
 * @param tokenName 代幣名稱
 * @param tokenSymbol 代幣符號
 * @param centralMinter 是否指定其他帳戶為合約所有者,為0是去中心化
 */
function MyAdvancedToken(
  uint256 initialSupply,
  string tokenName,
  string tokenSymbol,
  address centralMinter
)  {
    //設定合約的管理者
    if(centralMinter != 0 ) owner = centralMinter;
}

  

7.2、挖礦

  有的時候需要更多的代幣流通,可以增加 mintToken 方法,創造更多的代幣。

/**
 * 合約擁有者,可以為指定帳戶創造一些代幣
 * @param  target address 帳戶地址
 * @param  mintedAmount uint256 增加的金額(單位是wei)
 */
function mintToken(address target, uint256 mintedAmount) onlyOwner {

    //給指定地址增加代幣,同時總量也相加
    balanceOf[target] += mintedAmount;
    totalSupply += mintedAmount;
}

  
  在方法的最後有一個 onlyOwner,說明 mintToken 是繼承了 onlyOwner方法,會先呼叫 modifier onlyOwner 方法,然後將 mintToken 方法的內容,插入到下劃線 _ 處呼叫。

  

7.3、凍結資產

  有的場景中,某些使用者違反了規定,需要凍結/解凍帳戶,不想讓他使用已經擁有的代幣.可以增加以下程式碼來控制:

//是否凍結帳戶的列表
mapping (address => bool) public frozenAccount;

//定義一個事件,當有資產被凍結的時候,通知正在監聽事件的客戶端
event FrozenFunds(address target, bool frozen);

/**
 * 增加凍結帳戶名稱
 *
 * 你可能需要監管功能以便你能控制誰可以/誰不可以使用你建立的代幣合約
 *
 * @param  target address 帳戶地址
 * @param  freeze bool    是否凍結
 */
function freezeAccount(address target, bool freeze) onlyOwner {
    frozenAccount[target] = freeze;
    FrozenFunds(target, freeze);
}

  

7.4、自動交易

  到了現在,代幣的功能很完善,大家也相信你的代幣是有價值的,但你想要使用以太幣 ether 或者其他代幣來購買,讓代幣市場化,可以真實的交易,我們可以設定一個價格

//賣出的匯率,一個代幣,可以賣出多少個以太幣,單位是wei
uint256 public sellPrice;

//買入的匯率,1個以太幣,可以買幾個代幣
uint256 public buyPrice;

/**
 * 設定買賣價格
 *
 * 如果你想讓ether(或其他代幣)為你的代幣進行背書,以便可以市場價自動化買賣代幣,我們可以這麼做。如果要使用浮動的價格,也可以在這裡設定
 *
 * @param newSellPrice 新的賣出價格
 * @param newBuyPrice 新的買入價格
 */
function setPrices(uint256 newSellPrice, uint256 newBuyPrice) onlyOwner {
    sellPrice = newSellPrice;
    buyPrice = newBuyPrice;
}

  
  然後增加買、賣的方法,每一次的交易,都會消耗掉一定的 ether。在 Solidity 0.4.0 之後,要接收 ether 的函式都要加一個 payable 屬性,如果你開放的合約,需要別人轉錢給你,就需要加 payable

  下面的方法,不會增加代幣,只是改變呼叫合約者的代幣數量,買、賣的價格單位不是 ether,而是 wei,這是以太幣中最小的單位(就像美元裡的美分,比特幣裡的聰)。1 ether = 1000000000000000000 wei。因此使用 ether 設定價格的時候,在最後加18個0。

  當建立合約的時候,傳送足夠多的 ether 作為代幣的背書,否則你的合約就是破產的,你的使用者就不能夠賣掉他們的代幣。

/**
 * 使用以太幣購買代幣
 */
function buy() payable public {
  uint amount = msg.value / buyPrice;

  _transfer(this, msg.sender, amount);
}

/**
 * @dev 賣出代幣
 * @return 要賣出的數量(單位是wei)
 */
function sell(uint256 amount) public {

    //檢查合約的餘額是否充足
    require(this.balance >= amount * sellPrice);

    _transfer(msg.sender, this, amount);

    msg.sender.transfer(amount * sellPrice);
}

  

7.5、全部程式碼

  把所有的特性加上,完整的程式碼如下:

pragma solidity 0.4.16;

interface tokenRecipient { function receiveApproval(address _from, uint256 _value, address _token, bytes _extraData) public; }

/**
 * owned 是一個管理者
 */
contract owned {
    address public owner;

    /**
     * 初臺化建構函式
     */
    function owned() {
        owner = msg.sender;
    }

    /**
     * 判斷當前合約呼叫者是否是管理員
     */
    modifier onlyOwner {
        require (msg.sender == owner);
        _;
    }

    /**
     * 指派一個新的管理員
     * @param  newOwner address 新的管理員帳戶地址
     */
    function transferOwnership(address newOwner) onlyOwner {
        owner = newOwner;
    }
}

/**
 * @title 基礎版的代幣合約
 */
contract token {
    /* 公共變數 */
    string public standard = 'https://mshk.top';
    string public name; //代幣名稱
    string public symbol; //代幣符號比如'$'
    uint8 public decimals = 18;  //代幣單位,展示的小數點後面多少個0,和以太幣一樣後面是是18個0
    uint256 public totalSupply; //代幣總量

    /*記錄所有餘額的對映*/
    mapping (address => uint256) public balanceOf;
    mapping (address => mapping (address => uint256)) public allowance;

    /* 在區塊鏈上建立一個事件,用以通知客戶端*/
    event Transfer(address indexed from, address indexed to, uint256 value);  //轉帳通知事件
    event Burn(address indexed from, uint256 value);  //減去使用者餘額事件

    /* 初始化合約,並且把初始的所有代幣都給這合約的建立者
     * @param initialSupply 代幣的總數
     * @param tokenName 代幣名稱
     * @param tokenSymbol 代幣符號
     */
    function token(uint256 initialSupply, string tokenName, string tokenSymbol) {

        //初始化總量
        totalSupply = initialSupply * 10 ** uint256(decimals);    //以太幣是10^18,後面18個0,所以預設decimals是18

        //給指定帳戶初始化代幣總量,初始化用於獎勵合約建立者
        //balanceOf[msg.sender] = totalSupply;
        balanceOf[this] = totalSupply;

        name = tokenName;
        symbol = tokenSymbol;

    }


    /**
     * 私有方法從一個帳戶傳送給另一個帳戶代幣
     * @param  _from address 傳送代幣的地址
     * @param  _to address 接受代幣的地址
     * @param  _value uint256 接受代幣的數量
     */
    function _transfer(address _from, address _to, uint256 _value) internal {

      //避免轉帳的地址是0x0
      require(_to != 0x0);

      //檢查傳送者是否擁有足夠餘額
      require(balanceOf[_from] >= _value);

      //檢查是否溢位
      require(balanceOf[_to] + _value > balanceOf[_to]);

      //儲存資料用於後面的判斷
      uint previousBalances = balanceOf[_from] + balanceOf[_to];

      //從傳送者減掉髮送額
      balanceOf[_from] -= _value;

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

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

      //判斷買、賣雙方的資料是否和轉換前一致
      assert(balanceOf[_from] + balanceOf[_to] == previousBalances);

    }

    /**
     * 從主帳戶合約呼叫者傳送給別人代幣
     * @param  _to address 接受代幣的地址
     * @param  _value uint256 接受代幣的數量
     */
    function transfer(address _to, uint256 _value) public {
        _transfer(msg.sender, _to, _value);
    }

    /**
     * 從某個指定的帳戶中,向另一個帳戶傳送代幣
     *
     * 呼叫過程,會檢查設定的允許最大交易額
     *
     * @param  _from address 傳送者地址
     * @param  _to address 接受者地址
     * @param  _value uint256 要轉移的代幣數量
     * @return success        是否交易成功
     */
    function transferFrom(address _from, address _to, uint256 _value) returns (bool success) {
        //檢查傳送者是否擁有足夠餘額
        require(_value <= allowance[_from][msg.sender]);   // Check allowance

        allowance[_from][msg.sender] -= _value;

        _transfer(_from, _to, _value);

        return true;
    }

    /**
     * 設定帳戶允許支付的最大金額
     *
     * 一般在智慧合約的時候,避免支付過多,造成風險
     *
     * @param _spender 帳戶地址
     * @param _value 金額
     */
    function approve(address _spender, uint256 _value) public returns (bool success) {
        allowance[msg.sender][_spender] = _value;
        return true;
    }

    /**
     * 設定帳戶允許支付的最大金額
     *
     * 一般在智慧合約的時候,避免支付過多,造成風險,加入時間引數,可以在 tokenRecipient 中做其他操作
     *
     * @param _spender 帳戶地址
     * @param _value 金額
     * @param _extraData 操作的時間
     */
    function approveAndCall(address _spender, uint256 _value, bytes _extraData) public returns (bool success) {
        tokenRecipient spender = tokenRecipient(_spender);
        if (approve(_spender, _value)) {
            spender.receiveApproval(msg.sender, _value, this, _extraData);
            return true;
        }
    }

    /**
     * 減少代幣呼叫者的餘額
     *
     * 操作以後是不可逆的
     *
     * @param _value 要刪除的數量
     */
    function burn(uint256 _value) public returns (bool success) {
        //檢查帳戶餘額是否大於要減去的值
        require(balanceOf[msg.sender] >= _value);   // Check if the sender has enough

        //給指定帳戶減去餘額
        balanceOf[msg.sender] -= _value;

        //代幣問題做相應扣除
        totalSupply -= _value;

        Burn(msg.sender, _value);
        return true;
    }

    /**
     * 刪除帳戶的餘額(含其他帳戶)
     *
     * 刪除以後是不可逆的
     *
     * @param _from 要操作的帳戶地址
     * @param _value 要減去的數量
     */
    function burnFrom(address _from, uint256 _value) public returns (bool success) {

        //檢查帳戶餘額是否大於要減去的值
        require(balanceOf[_from] >= _value);

        //檢查 其他帳戶 的餘額是否夠使用
        require(_value <= allowance[_from][msg.sender]);

        //減掉代幣
        balanceOf[_from] -= _value;
        allowance[_from][msg.sender] -= _value;

        //更新總量
        totalSupply -= _value;
        Burn(_from, _value);
        return true;
    }



    /**
     * 匿名方法,預防有人向這合約傳送以太幣
     */
    /*function() {
        //return;     // Prevents accidental sending of ether
    }*/
}

/**
 * @title 高階版代幣
 * 增加凍結使用者、挖礦、根據指定匯率購買(售出)代幣價格的功能
 */
contract MyAdvancedToken is owned, token {

    //賣出的匯率,一個代幣,可以賣出多少個以太幣,單位是wei
    uint256 public sellPrice;

    //買入的匯率,1個以太幣,可以買幾個代幣
    uint256 public buyPrice;

    //是否凍結帳戶的列表
    mapping (address => bool) public frozenAccount;

    //定義一個事件,當有資產被凍結的時候,通知正在監聽事件的客戶端
    event FrozenFunds(address target, bool frozen);


    /*初始化合約,並且把初始的所有的令牌都給這合約的建立者
     * @param initialSupply 所有幣的總數
     * @param tokenName 代幣名稱
     * @param tokenSymbol 代幣符號
     * @param centralMinter 是否指定其他帳戶為合約所有者,為0是去中心化
     */
    function MyAdvancedToken(
      uint256 initialSupply,
      string tokenName,
      string tokenSymbol,
      address centralMinter
    ) token (initialSupply, tokenName, tokenSymbol) {

        //設定合約的管理者
        if(centralMinter != 0 ) owner = centralMinter;

        sellPrice = 2;     //設定1個單位的代幣(單位是wei),能夠賣出2個以太幣
        buyPrice = 4;      //設定1個以太幣,可以買0.25個代幣
    }


    /**
     * 私有方法,從指定帳戶轉出餘額
     * @param  _from address 傳送代幣的地址
     * @param  _to address 接受代幣的地址
     * @param  _value uint256 接受代幣的數量
     */
    function _transfer(address _from, address _to, uint _value) internal {

        //避免轉帳的地址是0x0
        require (_to != 0x0);

        //檢查傳送者是否擁有足夠餘額
        require (balanceOf[_from] > _value);

        //檢查是否溢位
        require (balanceOf[_to] + _value > balanceOf[_to]);

        //檢查 凍結帳戶
        require(!frozenAccount[_from]);
        require(!frozenAccount[_to]);



        //從傳送者減掉髮送額
        balanceOf[_from] -= _value;

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

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

    }

    /**
     * 合約擁有者,可以為指定帳戶創造一些代幣
     * @param  target address 帳戶地址
     * @param  mintedAmount uint256 增加的金額(單位是wei)
     */
    function mintToken(address target, uint256 mintedAmount) onlyOwner {

        //給指定地址增加代幣,同時總量也相加
        balanceOf[target] += mintedAmount;
        totalSupply += mintedAmount;


        Transfer(0, this, mintedAmount);
        Transfer(this, target, mintedAmount);
    }

    /**
     * 增加凍結帳戶名稱
     *
     * 你可能需要監管功能以便你能控制誰可以/誰不可以使用你建立的代幣合約
     *
     * @param  target address 帳戶地址
     * @param  freeze bool    是否凍結
     */
    function freezeAccount(address target, bool freeze) onlyOwner {
        frozenAccount[target] = freeze;
        FrozenFunds(target, freeze);
    }

    /**
     * 設定買賣價格
     *
     * 如果你想讓ether(或其他代幣)為你的代幣進行背書,以便可以市場價自動化買賣代幣,我們可以這麼做。如果要使用浮動的價格,也可以在這裡設定
     *
     * @param newSellPrice 新的賣出價格
     * @param newBuyPrice 新的買入價格
     */
    function setPrices(uint256 newSellPrice, uint256 newBuyPrice) onlyOwner {
        sellPrice = newSellPrice;
        buyPrice = newBuyPrice;
    }

    /**
     * 使用以太幣購買代幣
     */
    function buy() payable public {
      uint amount = msg.value / buyPrice;

      _transfer(this, msg.sender, amount);
    }

    /**
     * @dev 賣出代幣
     * @return 要賣出的數量(單位是wei)
     */
    function sell(uint256 amount) public {

        //檢查合約的餘額是否充足
        require(this.balance >= amount * sellPrice);

        _transfer(msg.sender, this, amount);

        msg.sender.transfer(amount * sellPrice);
    }
}

  
  參考之前的方法,在 Mist 中重新部署合約,貼完程式碼後,在右側選擇 My Advanced TokenInitial Supply 輸入初始金額5000,Token name 輸入我們的代幣名稱 陌上花開AToken symbol 代幣符號我們輸入 #,然後點選部署,輸入部署帳戶的密碼。

<embed>

  
  建立成功以後,我們在合約列表頁,可以看到剛才建立的新合約陌上花開A

<embed>

  
  點選 Mist 上面的傳送,我們先給帳戶0xd29adaadf3a40fd0b68c83c222c10d3ea637dce0轉入100個以太幣。

<embed>

  
  操作成功以後,我們能夠在錢包頁面看到Account 4已經有了100以太幣。

<embed>

  

7.6、使用以太幣購買代幣

  接下來,我們進入合約頁面,使用以太幣購買 陌上花開A 代幣,進入合約介面後,我們能夠看到代幣上的以太幣是 0 ether,在右側選擇 Buy 方法,Execut from 選擇 Account 4,在 Send ether 輸入 10 個以太幣,點選 執行

<embed>

  
  執行成功以後,能夠看到當前頁面自動重新整理,合約中已經有了10 ether,代幣的總量不變

<embed>

  
  再次回到 錢包 頁面,可以看到 Account 4 已經從 100 ether 變成了 90 ether,並且多了一個代幣圖示。

<embed>

  
  點選 Account 4 帳號進去,可以看到一些詳細資訊,ether的總量是 89,999081514 而不是 90,是因為執行合約的時候,我們會消費一定的 gas。我們設定的費率是1:4,所以 10 ether,只可以購買 2.5陌上花開A 代幣,最小單位也是wei,所以是 2,500000000000000000

<embed>

  

7.7、賣出代幣

  進入合約介面後,我們能夠看到代幣上的以太幣是 10 ether,在右側選擇 Sell 方法,在 Amount 處輸入 2000000000000000000(因為我們剛才購買了2.5個代幣,現在賣出2個,賣出的最小單位是wei),Execut from 選擇 Account 4,點選 執行

<embed>

  
  執行以後,在代幣的詳情頁面,能夠看到從 10 ether變成了 6 ether,因為剛才 Account 4 賣出了 2陌上花開A 代幣,而我們設定的賣價是 1個代幣 能賣出 2個以太幣

<embed>

  
  再次回到 Account 4 的詳情頁面,能夠看到以太幣變成了 93,998273026,而 陌上花開A 代幣的數量,變成了 0,500000000000000000

<embed>

  

8、常見問題

8.1、在除錯Mist的過程中,建立了很多個合約,如何刪除?

  In the Ethereum Wallet (Mist) menu, click on Develop -> Toggle Developer Tools -> Wallet UI. Click on the Console tab

CustomContracts.find().fetch().map(
   function(m) { CustomContracts.remove(m._id);}
)

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


相關文章