以太坊開發(fā)(十三)代幣示例及講解

原文:Go-Ethereum 1.7.2 結(jié)合 Mist 0.9.2 實現(xiàn)代幣智能合約的實例
作者:迦壹

(注:本文是在原文的基礎(chǔ)上,根據(jù)個人的理解,修改部分內(nèi)容并添加了一些注釋)

基礎(chǔ)版的代幣合約

下面是一個最簡單的代幣合約代碼,主要介紹可以看注釋:

pragma solidity 0.4.20;

/**
 * @title 基礎(chǔ)版的代幣合約
 */
contract token {
    
    /* 公共變量 */
    string public standard = "https://mshk.top";
    
    /*記錄所有余額的映射*/
    mapping (address => uint256) public balanceOf;
    
    /* 初始化合約,并且把初始的所有代幣都給這合約的創(chuàng)建者
     * @param initialSupply 代幣的總數(shù)
     */
    function token (uint256 initialSupply) public {
        balanceOf[msg.sender] = initialSupply;
    }
    
    /**
     * 私有方法從一個帳戶發(fā)送給另一個帳戶代幣
     * @param  from address 發(fā)送代幣的地址
     * @param  to address 接受代幣的地址
     * @param  value uint256 接受代幣的數(shù)量
     */
    function _transfer (address from, address to, uint256 value) internal {
        
        //避免轉(zhuǎn)帳的地址是0x0
        require(to != 0x0);
        
        //檢查發(fā)送者是否擁有足夠余額
        require(balanceOf[from] >= value);
        
        //檢查是否溢出
        require(balanceOf[to] + value > balanceOf[to]);
        
        //保存數(shù)據(jù)用于后面的判斷
        uint previousBalances = balanceOf[from] + balanceOf[to];
        
        //從發(fā)送者減掉發(fā)送額
        balanceOf[from] -= value;
        
        //給接收者加上相同的量
        balanceOf[to] += value;
        
        //判斷買、賣雙方的數(shù)據(jù)是否和轉(zhuǎn)換前一致
        assert(balanceOf[from] + balanceOf[to] == previousBalances);
    }
    
    /**
     * 從主帳戶合約調(diào)用者發(fā)送給別人代幣
     * @param  to address 接受代幣的地址
     * @param  value uint256 接受代幣的數(shù)量
     */
    function transfer (address to, uint256 value) public {
        _transfer(msg.sender, to, value);
    }
}

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

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

然后如下面的兩張圖,選擇創(chuàng)建合約的帳戶,將上面的代碼貼到SOLIDITY合約原始代碼處,在右側(cè)選擇要部署的合約token,在token的下面Initial Supply處,輸入我們要初始化的金額5000用于獎勵合約創(chuàng)建者,然后在頁面的最下面,點擊部署,的彈出的層中,輸入創(chuàng)建合約這個帳號的密碼,輸入正確以后,合約創(chuàng)建成功。

創(chuàng)建一個合約以后,再點擊 Mist 右側(cè)的合約,然后在 定制化合約 的下面,可以看到我們剛剛創(chuàng)建的合約 TOKEN,如下圖:

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

點擊 TOKEN ,進(jìn)入到合約里。在下面的 Balance Of 處,輸入剛才創(chuàng)建合約帳戶的地址0xa18e688326ab13b6147ce3ca2213db143a4ec2ee,可以看到是有5000個代幣在里面,如下圖:

在代幣創(chuàng)建的時候,初始值我們設(shè)置的是5000,所以只有創(chuàng)建帳戶的地址有代幣,而輸入其他帳戶的地址,如0xc81896af13449a82f22699311df4ec4b48c07718,是沒有值的。

接下來,我們向0xc81896af13449a82f22699311df4ec4b48c07718這個帳戶地址,轉(zhuǎn)入一些代幣。點擊右側(cè)選擇函數(shù)->選擇Transfer,在_to中輸入0xc81896af13449a82f22699311df4ec4b48c07718,在_value中輸入500,然后點擊執(zhí)行,在彈出的層中輸入調(diào)用合約帳戶的密碼,確認(rèn)操作。

我們能夠看到實際調(diào)用合約的過程中,會花費一定的gasgas和以太幣會根據(jù)區(qū)塊的算力有一個計算公式,gas一般用于獎勵給挖礦者。

transfer 方法中,我們設(shè)定了,只有合約的調(diào)用者msg.sender才能向指定地址轉(zhuǎn)移代幣。

/**
 * 從主帳戶合約調(diào)用者發(fā)送給別人代幣
 * @param  _to address 接受代幣的地址
 * @param  _value uint256 接受代幣的數(shù)量
 */
function transfer(address _to, uint256 _value) public {
    _transfer(msg.sender, _to, _value);
}

這時,再次進(jìn)入合約,在Balance Of處,輸入兩個帳戶的地址,可以看到,余額都發(fā)生的變化,如下圖:

改善代幣

通過上面的操作,我們已經(jīng)可以將合約代碼,通過 Mist 部署到我們創(chuàng)建的私有鏈中,同樣如果部署到生產(chǎn)環(huán)境,只需要連上以太坊的網(wǎng)絡(luò),同樣的方法也可以將你的合約,部署到生產(chǎn)環(huán)境中,不過要根據(jù)代碼的大小,花費一些以太幣。

實際使用過程中,交易的過程,需要通知到客戶端,并且記錄到區(qū)塊中,我們可以使用event事件來指定,如下代碼進(jìn)行聲明:

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

設(shè)置一些代幣的基本信息

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

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

注:根據(jù)個人的理解,approve是其他合約調(diào)用此合約,或者是代理,例如交易所,使用你的賬戶時,你可以設(shè)置一個代幣數(shù)量。他們只能對你設(shè)置的數(shù)量的代幣進(jìn)行操作,不知道這樣理解是否準(zhǔn)確。下面是一個對approve、allowance,以及后面的transferFrom的解釋:

注:approve、transferFrom及allowance解釋:
賬戶A有1000個ETH,想允許B賬戶隨意調(diào)用100個ETH。A賬戶按照以下形式調(diào)用approve函數(shù)approve(B,100)。當(dāng)B賬戶想用這100個ETH中的10個ETH給C賬戶時,則調(diào)用transferFrom(A, C, 10)。這時調(diào)用allowance[A][B]可以查看B賬戶還能夠調(diào)用A賬戶多少個token。

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

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

同樣在 solidity 中,合約之間也可以相互調(diào)用,我們可以增加一個 approveAndCall 方法,用于在設(shè)置帳戶最大支出金額后,可以做一些其他操作。

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

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

/**
 * 設(shè)置帳戶允許支付的最大金額
 *
 * 一般在智能合約的時候,避免支付過多,造成風(fēng)險,加入時間參數(shù),可以在 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 方法,用于管理員減去指定帳戶的指定金額。進(jìn)行該方法操作時,通知客戶端記錄到區(qū)塊鏈中。

注:代碼并沒有判斷調(diào)用者是否為管理員。

注:代理B在調(diào)用burnFrom刪除A賬戶余額時,也需要刪除A授權(quán)給自己的可調(diào)用余額數(shù)。

//減去用戶余額事件
event Burn(address indexed from, uint256 value);  

/**
 * 減少代幣調(diào)用者的余額
 *
 * 操作以后是不可逆的
 *
 * @param _value 要刪除的數(shù)量
 */
function burn(uint256 _value) public returns (bool success) {
    //檢查帳戶余額是否大于要減去的值
    require(balanceOf[msg.sender] >= _value);   // Check if the sender has enough

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

    //代幣問題做相應(yīng)扣除
    totalSupply -= _value;

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

/**
 * 刪除帳戶的余額(含其他帳戶)
 *
 * 刪除以后是不可逆的
 *
 * @param _from 要操作的帳戶地址
 * @param _value 要減去的數(shù)量
 */
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.20;

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


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

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

    /* 在區(qū)塊鏈上創(chuàng)建一個事件,用以通知客戶端*/
    event Transfer(address indexed from, address indexed to, uint256 value);  //轉(zhuǎn)帳通知事件
    event Burn(address indexed from, uint256 value);  //減去用戶余額事件

    /* 初始化合約,并且把初始的所有代幣都給這合約的創(chuàng)建者
     * @param initialSupply 代幣的總數(shù)
     * @param tokenName 代幣名稱
     * @param tokenSymbol 代幣符號
     */
    function token(uint256 initialSupply, string tokenName, string tokenSymbol) public {

        //初始化總量
        totalSupply = initialSupply * 10 ** uint256(decimals);    //以太幣是10^18,后面18個0,所以默認(rèn)decimals是18

        //給指定帳戶初始化代幣總量,初始化用于獎勵合約創(chuàng)建者
        balanceOf[msg.sender] = totalSupply;

        name = tokenName;
        symbol = tokenSymbol;

    }


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

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

      //檢查發(fā)送者是否擁有足夠余額
      require(balanceOf[_from] >= _value);

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

      //保存數(shù)據(jù)用于后面的判斷
      uint previousBalances = balanceOf[_from] + balanceOf[_to];

      //從發(fā)送者減掉發(fā)送額
      balanceOf[_from] -= _value;

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

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

      //判斷買、賣雙方的數(shù)據(jù)是否和轉(zhuǎn)換前一致
      assert(balanceOf[_from] + balanceOf[_to] == previousBalances);

    }

    /**
     * 從主帳戶合約調(diào)用者發(fā)送給別人代幣
     * @param  _to address 接受代幣的地址
     * @param  _value uint256 接受代幣的數(shù)量
     */
    function transfer(address _to, uint256 _value) public {
        _transfer(msg.sender, _to, _value);
    }

    /**
     * 從某個指定的帳戶中,向另一個帳戶發(fā)送代幣
     *
     * 調(diào)用過程,會檢查設(shè)置的允許最大交易額
     *
     * @param  _from address 發(fā)送者地址
     * @param  _to address 接受者地址
     * @param  _value uint256 要轉(zhuǎn)移的代幣數(shù)量
     * @return success        是否交易成功
     */
    function transferFrom(address _from, address _to, uint256 _value) public returns (bool success){
        //檢查發(fā)送者是否擁有足夠余額
        require(_value <= allowance[_from][msg.sender]);   // Check allowance

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

        _transfer(_from, _to, _value);

        return true;
    }

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

    /**
     * 設(shè)置帳戶允許支付的最大金額
     *
     * 一般在智能合約的時候,避免支付過多,造成風(fēng)險,加入時間參數(shù),可以在 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;
        }
    }

    /**
     * 減少代幣調(diào)用者的余額
     *
     * 操作以后是不可逆的
     *
     * @param _value 要刪除的數(shù)量
     */
    function burn(uint256 _value) public returns (bool success) {
        //檢查帳戶余額是否大于要減去的值
        require(balanceOf[msg.sender] >= _value);   // Check if the sender has enough

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

        //代幣問題做相應(yīng)扣除
        totalSupply -= _value;

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

    /**
     * 刪除帳戶的余額(含其他帳戶)
     *
     * 刪除以后是不可逆的
     *
     * @param _from 要操作的帳戶地址
     * @param _value 要減去的數(shù)量
     */
    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;
    }
}

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

部署合約以后,我們能夠在合約頁面看到剛才創(chuàng)建的合約。

點擊合約名稱,可以看到合約的一些基本信息,以及合約和操作函數(shù)

我們能夠在 Mist 上方的 錢包 中的主帳號這里看到有個小圖標(biāo),說明主帳戶已經(jīng)有了代幣,其他帳戶是沒有這個圖標(biāo)的

點擊進(jìn)入主帳號以后,我們就可以看到主帳戶已經(jīng)擁有的代幣和以太幣的數(shù)量,因為我們是參考以太幣進(jìn)行設(shè)置,最小單位是wei,所以小數(shù)點后面有18個0。

接下來,我們向另一個帳戶發(fā)送一些 陌上花開 幣,點擊 Mist 上方的發(fā)送,輸入發(fā)送的帳戶地址,輸入數(shù)量 500 ,選擇發(fā)送的是 陌上花開 幣,點擊發(fā)送,如下圖

再次回到錢包中,我們可以看到,另一個帳戶也有了一個代幣的圖標(biāo),說明代幣已經(jīng)轉(zhuǎn)入成功。

現(xiàn)在你擁有了自己的代幣,也可以做轉(zhuǎn)入轉(zhuǎn)出操作??梢员挥糜?a target="_blank" rel="nofollow">價值交換,或者工作時間追蹤或者其他項目。

高級版的代幣功能

雖然區(qū)塊鏈?zhǔn)侨ブ行幕模菍崿F(xiàn)對代幣(合約)的管理,也在許多應(yīng)用中有需求,為了對代幣進(jìn)行管理,首先需要給合約添加一個管理者。

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

    /**
     * 初臺化構(gòu)造函數(shù)
     */
    function owned() {
        owner = msg.sender;
    }

    /**
     * 判斷當(dāng)前合約調(diào)用者是否是管理員
     */
    modifier onlyOwner {
        require (msg.sender == owner);
        _;
    }

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

上面的代碼是一個非常簡單的合約,我們可以在后面的代碼中,使用 繼承 來實現(xiàn)后續(xù)的功能。

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

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

去中心化的管理者

我們也可以在構(gòu)造函數(shù)中設(shè)置是否需要一個去中心化的管理者。

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

代幣增發(fā)

實現(xiàn)代幣增發(fā),代幣增發(fā)就如同央行印鈔票一樣,想必很多人都需要這樣的功能。

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

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

在方法的最后有一個 onlyOwner,說明 mintToken 是繼承了 onlyOwner方法,會先調(diào)用 modifier onlyOwner 方法,然后將 mintToken 方法的內(nèi)容,插入到下劃線 _ 處調(diào)用。

凍結(jié)資產(chǎn)

有的場景中,某些用戶違反了規(guī)定,需要凍結(jié)/解凍帳戶,不想讓他使用已經(jīng)擁有的代幣.可以增加以下代碼來控制:

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

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

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

代幣買賣(兌換)

可以自己的貨幣中實現(xiàn)代幣與其他數(shù)字貨幣(ether 或其他tokens)的兌換機制。有了這個功能,我們的合約就可以在一買一賣中賺利潤了。

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

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

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

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

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

當(dāng)創(chuàng)建合約的時候,發(fā)送足夠多的 ether 作為代幣的背書,否則你的合約就是破產(chǎn)的,你的用戶就不能夠賣掉他們的代幣。

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

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

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

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

    _transfer(msg.sender, this, amount);

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

實現(xiàn)Gas的自動補充

以太坊中的交易時需要gas(支付給礦工的費用,費用以ether來支付)。而如果用戶沒有以太幣,只有代幣的情況(或者我們想向用戶隱藏以太坊的細(xì)節(jié)),就需要自動補充gas的功能。這個功能將使我們代幣更加好用。

自動補充的邏輯是這樣了,在執(zhí)行交易之前,我們判斷用戶的余額(用來支付礦工的費用),如果用戶的余額非常少(低于某個閾值時)可能影響到交易進(jìn)行,合約自動售出一部分代幣來補充余額,以幫助用戶順利完成交易。

先來設(shè)定余額閾值:

uint minBalanceForAccounts;

    function setMinBalance(uint minimumBalanceInFinney) onlyOwner {
         minBalanceForAccounts = minimumBalanceInFinney * 1 finney;
    }

finney 是貨幣單位 1 finney = 0.001eth
然后交易中加入對用戶的余額的判斷。

function transfer(address _to, uint256 _value) {
    ...
    if(msg.sender.balance < minBalanceForAccounts)
        sell((minBalanceForAccounts - msg.sender.balance) / sellPrice);
    if(_to.balance<minBalanceForAccounts)   // 可選,讓接受者也補充余額,以便接受者使用代幣。
        _to.send(sell((minBalanceForAccounts - _to.balance) / sellPrice));
}

全部代碼

pragma solidity 0.4.20;

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

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

    /**
     * 初臺化構(gòu)造函數(shù)
     */
    function owned () public {
        owner = msg.sender;
    }

    /**
     * 判斷當(dāng)前合約調(diào)用者是否是管理員
     */
    modifier onlyOwner {
        require (msg.sender == owner);
        _;
    }

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

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

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

    /* 在區(qū)塊鏈上創(chuàng)建一個事件,用以通知客戶端*/
    event Transfer(address indexed from, address indexed to, uint256 value);  //轉(zhuǎn)帳通知事件
    event Burn(address indexed from, uint256 value);  //減去用戶余額事件

    /* 初始化合約,并且把初始的所有代幣都給這合約的創(chuàng)建者
     * @param initialSupply 代幣的總數(shù)
     * @param tokenName 代幣名稱
     * @param tokenSymbol 代幣符號
     */
    function token(uint256 initialSupply, string tokenName, string tokenSymbol) public {

        //初始化總量
        totalSupply = initialSupply * 10 ** uint256(decimals);    //以太幣是10^18,后面18個0,所以默認(rèn)decimals是18

        //給指定帳戶初始化代幣總量,初始化用于獎勵合約創(chuàng)建者
        //balanceOf[msg.sender] = totalSupply;
        balanceOf[this] = totalSupply;

        name = tokenName;
        symbol = tokenSymbol;

    }


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

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

      //檢查發(fā)送者是否擁有足夠余額
      require(balanceOf[_from] >= _value);

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

      //保存數(shù)據(jù)用于后面的判斷
      uint previousBalances = balanceOf[_from] + balanceOf[_to];

      //從發(fā)送者減掉發(fā)送額
      balanceOf[_from] -= _value;

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

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

      //判斷買、賣雙方的數(shù)據(jù)是否和轉(zhuǎn)換前一致
      assert(balanceOf[_from] + balanceOf[_to] == previousBalances);

    }

    /**
     * 從主帳戶合約調(diào)用者發(fā)送給別人代幣
     * @param  _to address 接受代幣的地址
     * @param  _value uint256 接受代幣的數(shù)量
     */
    function transfer(address _to, uint256 _value) public {
        _transfer(msg.sender, _to, _value);
    }

    /**
     * 從某個指定的帳戶中,向另一個帳戶發(fā)送代幣
     *
     * 調(diào)用過程,會檢查設(shè)置的允許最大交易額
     *
     * @param  _from address 發(fā)送者地址
     * @param  _to address 接受者地址
     * @param  _value uint256 要轉(zhuǎn)移的代幣數(shù)量
     * @return success        是否交易成功
     */
    function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) {
        //檢查發(fā)送者是否擁有足夠余額
        require(_value <= allowance[_from][msg.sender]);   // Check allowance

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

        _transfer(_from, _to, _value);

        return true;
    }

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

    /**
     * 設(shè)置帳戶允許支付的最大金額
     *
     * 一般在智能合約的時候,避免支付過多,造成風(fēng)險,加入時間參數(shù),可以在 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;
        }
    }

    /**
     * 減少代幣調(diào)用者的余額
     *
     * 操作以后是不可逆的
     *
     * @param _value 要刪除的數(shù)量
     */
    function burn(uint256 _value) public returns (bool success) {
        //檢查帳戶余額是否大于要減去的值
        require(balanceOf[msg.sender] >= _value);   // Check if the sender has enough

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

        //代幣問題做相應(yīng)扣除
        totalSupply -= _value;

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

    /**
     * 刪除帳戶的余額(含其他帳戶)
     *
     * 刪除以后是不可逆的
     *
     * @param _from 要操作的帳戶地址
     * @param _value 要減去的數(shù)量
     */
    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;
    }



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

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

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

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

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

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


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

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

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


    /**
     * 私有方法,從指定帳戶轉(zhuǎn)出余額
     * @param  _from address 發(fā)送代幣的地址
     * @param  _to address 接受代幣的地址
     * @param  _value uint256 接受代幣的數(shù)量
     */
    function _transfer(address _from, address _to, uint _value) internal {

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

        //檢查發(fā)送者是否擁有足夠余額
        require (balanceOf[_from] > _value);

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

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



        //從發(fā)送者減掉發(fā)送額
        balanceOf[_from] -= _value;

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

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

    }

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

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


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

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

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

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

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

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

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

        _transfer(msg.sender, this, amount);

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

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

創(chuàng)建成功以后,我們在合約列表頁,可以看到剛才創(chuàng)建的新合約陌上花開A。

點擊 Mist 上面的發(fā)送,我們先給帳戶0xd29adaadf3a40fd0b68c83c222c10d3ea637dce0轉(zhuǎn)入100個以太幣。

操作成功以后,我們能夠在錢包頁面看到Account 4已經(jīng)有了100以太幣。

使用以太幣購買代幣

接下來,我們進(jìn)入合約頁面,使用以太幣購買 陌上花開A 代幣,進(jìn)入合約界面后,我們能夠看到代幣上的以太幣是 0 ether,在右側(cè)選擇 Buy 方法,Execut from 選擇 Account 4,在 Send ether 輸入 10 個以太幣,點擊 執(zhí)行。

執(zhí)行成功以后,能夠看到當(dāng)前頁面自動刷新,合約中已經(jīng)有了10 ether,代幣的總量不變

再次回到 錢包 頁面,可以看到 Account 4 已經(jīng)從 100 ether 變成了 90 ether,并且多了一個代幣圖標(biāo)。

點擊 Account 4 帳號進(jìn)去,可以看到一些詳細(xì)信息,ether的總量是 89,999081514 而不是 90,是因為執(zhí)行合約的時候,我們會消費一定的 gas。我們設(shè)置的費率是1:4,所以 10 ether,只可以購買 2.5陌上花開A 代幣,最小單位也是wei,所以是 2,500000000000000000

賣出代幣

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

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

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

注:Account4使用10個以太幣購買了2.5個代幣,如果交易機制沒有手續(xù)費,Account4賣出2.5個代幣仍然會獲得10個以太幣。而這里通過設(shè)置買價4和賣價2,Account4賣出2.5個代幣只會獲得5個以太幣。代幣合約利用手續(xù)費賺取了5個以太幣。

擴(kuò)展閱讀

以太坊官方代幣說明

參考:實現(xiàn)一個可管理、增發(fā)、兌換、凍結(jié)等高級功能的代幣
作者:Tiny熊

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容