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

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

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

基礎版的代幣合約

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

pragma solidity 0.4.20;

/**
 * @title 基礎版的代幣合約
 */
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 {
        
        //避免轉帳的地址是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ù)是否和轉換前一致
        assert(balanceOf[from] + balanceOf[to] == previousBalances);
    }
    
    /**
     * 從主帳戶合約調用者發(fā)送給別人代幣
     * @param  to address 接受代幣的地址
     * @param  value uint256 接受代幣的數(shù)量
     */
    function transfer (address to, uint256 value) public {
        _transfer(msg.sender, to, value);
    }
}

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

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

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

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

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

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

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

接下來烙如,我們向0xc81896af13449a82f22699311df4ec4b48c07718這個帳戶地址么抗,轉入一些代幣。點擊右側選擇函數(shù)->選擇Transfer亚铁,在_to中輸入0xc81896af13449a82f22699311df4ec4b48c07718蝇刀,在_value中輸入500,然后點擊執(zhí)行徘溢,在彈出的層中輸入調用合約帳戶的密碼熊泵,確認操作仰迁。

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

transfer 方法中卒蘸,我們設定了,只有合約的調用者msg.sender才能向指定地址轉移代幣翻默。

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

這時缸沃,再次進入合約,在Balance Of處修械,輸入兩個帳戶的地址趾牧,可以看到,余額都發(fā)生的變化肯污,如下圖:

改善代幣

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

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

//在區(qū)塊鏈上創(chuàng)建一個事件堪滨,用以通知客戶端
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;  //代幣單位胯陋,展示的小數(shù)點后面多少個0,和以太幣一樣后面是是18個0
uint256 public totalSupply; //代幣總量

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

注:根據(jù)個人的理解盟萨,approve是其他合約調用此合約,或者是代理了讨,例如交易所捻激,使用你的賬戶時制轰,你可以設置一個代幣數(shù)量。他們只能對你設置的數(shù)量的代幣進行操作胞谭,不知道這樣理解是否準確垃杖。下面是一個對approve、allowance丈屹,以及后面的transferFrom的解釋:

注:approve调俘、transferFrom及allowance解釋:
賬戶A有1000個ETH,想允許B賬戶隨意調用100個ETH旺垒。A賬戶按照以下形式調用approve函數(shù)approve(B,100)彩库。當B賬戶想用這100個ETH中的10個ETH給C賬戶時,則調用transferFrom(A, C, 10)先蒋。這時調用allowance[A][B]可以查看B賬戶還能夠調用A賬戶多少個token骇钦。

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;
}

/**
 * 設置帳戶允許支付的最大金額
 *
 * 一般在智能合約的時候,避免支付過多糊闽,造成風險梳玫,加入時間參數(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 方法右犹,用于管理員減去指定帳戶的指定金額提澎。進行該方法操作時,通知客戶端記錄到區(qū)塊鏈中念链。

注:代碼并沒有判斷調用者是否為管理員盼忌。

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

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

/**
 * 減少代幣調用者的余額
 *
 * 操作以后是不可逆的
 *
 * @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;

    //代幣問題做相應扣除
    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 基礎版的代幣合約
 */
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);  //轉帳通知事件
    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,所以默認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 {

      //避免轉帳的地址是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ù)是否和轉換前一致
      assert(balanceOf[_from] + balanceOf[_to] == previousBalances);

    }

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

    /**
     * 從某個指定的帳戶中梦重,向另一個帳戶發(fā)送代幣
     *
     * 調用過程,會檢查設置的允許最大交易額
     *
     * @param  _from address 發(fā)送者地址
     * @param  _to address 接受者地址
     * @param  _value uint256 要轉移的代幣數(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;
    }

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

    /**
     * 設置帳戶允許支付的最大金額
     *
     * 一般在智能合約的時候,避免支付過多寺庄,造成風險艾蓝,加入時間參數(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;
        }
    }

    /**
     * 減少代幣調用者的余額
     *
     * 操作以后是不可逆的
     *
     * @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;

        //代幣問題做相應扣除
        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合約原始代碼處亮靴,在右側選擇 token 馍盟, Initial Supply 輸入初始金額5000Token name 輸入我們的代幣名稱 陌上花開 茧吊, Token symbol代幣符號我們輸入 $$ ,然后點擊 部署 贞岭,輸入部署帳戶的密碼。

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

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

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

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

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

再次回到錢包中赋焕,我們可以看到参歹,另一個帳戶也有了一個代幣的圖標,說明代幣已經(jīng)轉入成功隆判。

現(xiàn)在你擁有了自己的代幣犬庇,也可以做轉入轉出操作僧界。可以被用于價值交換,或者工作時間追蹤或者其他項目臭挽。

高級版的代幣功能

雖然區(qū)塊鏈是去中心化的捂襟,但是實現(xiàn)對代幣(合約)的管理,也在許多應用中有需求欢峰,為了對代幣進行管理葬荷,首先需要給合約添加一個管理者。

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

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

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

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

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

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

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

去中心化的管理者

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

/*初始化合約,并且把初始的所有的令牌都給這合約的創(chuàng)建者
 * @param initialSupply 所有幣的總數(shù)
 * @param tokenName 代幣名稱
 * @param tokenSymbol 代幣符號
 * @param centralMinter 是否指定其他帳戶為合約所有者,為0是去中心化
 */
function MyAdvancedToken(
  uint256 initialSupply,
  string tokenName,
  string tokenSymbol,
  address centralMinter
)  {
    //設置合約的管理者
    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方法冬念,會先調用 modifier onlyOwner 方法趁窃,然后將 mintToken 方法的內容,插入到下劃線 _ 處調用急前。

凍結資產(chǎn)

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

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

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

/**
 * 增加凍結帳戶名稱
 *
 * 你可能需要監(jiān)管功能以便你能控制誰可以/誰不可以使用你創(chuàng)建的代幣合約
 *
 * @param  target address 帳戶地址
 * @param  freeze bool    是否凍結
 */
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;

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

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

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

當創(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來支付)扶欣。而如果用戶沒有以太幣,只有代幣的情況(或者我們想向用戶隱藏以太坊的細節(jié))千扶,就需要自動補充gas的功能料祠。這個功能將使我們代幣更加好用。

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

先來設定余額閾值:

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;

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

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

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

/**
 * @title 基礎版的代幣合約
 */
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);  //轉帳通知事件
    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卧抗,所以默認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 {

      //避免轉帳的地址是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ù)是否和轉換前一致
      assert(balanceOf[_from] + balanceOf[_to] == previousBalances);

    }

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

    /**
     * 從某個指定的帳戶中颗味,向另一個帳戶發(fā)送代幣
     *
     * 調用過程,會檢查設置的允許最大交易額
     *
     * @param  _from address 發(fā)送者地址
     * @param  _to address 接受者地址
     * @param  _value uint256 要轉移的代幣數(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;
    }

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

    /**
     * 設置帳戶允許支付的最大金額
     *
     * 一般在智能合約的時候,避免支付過多张漂,造成風險晶默,加入時間參數(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;
        }
    }

    /**
     * 減少代幣調用者的余額
     *
     * 操作以后是不可逆的
     *
     * @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;

        //代幣問題做相應扣除
        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;
    }



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

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

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

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

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

    //定義一個事件憔狞,當有資產(chǎn)被凍結的時候蝴悉,通知正在監(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 {

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

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


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

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

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

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

        //檢查 凍結帳戶
        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ān)管功能以便你能控制誰可以/誰不可以使用你創(chuàng)建的代幣合約
     *
     * @param  target address 帳戶地址
     * @param  freeze bool    是否凍結
     */
    function freezeAccount(address target, bool freeze) onlyOwner public {
        frozenAccount[target] = freeze;
        FrozenFunds(target, freeze);
    }

    /**
     * 設置買賣價格
     *
     * 如果你想讓ether(或其他代幣)為你的代幣進行背書,以便可以市場價自動化買賣代幣,我們可以這么做。如果要使用浮動的價格碟摆,也可以在這里設置
     *
     * @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 中重新部署合約,貼完代碼后焦履,在右側選擇 My Advanced Token拓劝,Initial Supply 輸入初始金額5000Token name 輸入我們的代幣名稱 陌上花開A嘉裤,Token symbol 代幣符號我們輸入 #,然后點擊部署郑临,輸入部署帳戶的密碼。

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

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

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

使用以太幣購買代幣

接下來公你,我們進入合約頁面踊淳,使用以太幣購買 陌上花開A 代幣,進入合約界面后陕靠,我們能夠看到代幣上的以太幣是 0 ether迂尝,在右側選擇 Buy 方法,Execut from 選擇 Account 4剪芥,在 Send ether 輸入 10 個以太幣垄开,點擊 執(zhí)行。

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

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

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

賣出代幣

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

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

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

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

擴展閱讀

以太坊官方代幣說明

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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末撼唾,一起剝皮案震驚了整個濱河市廉邑,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌倒谷,老刑警劉巖蛛蒙,帶你破解...
    沈念sama閱讀 216,470評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異渤愁,居然都是意外死亡牵祟,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評論 3 392
  • 文/潘曉璐 我一進店門抖格,熙熙樓的掌柜王于貴愁眉苦臉地迎上來诺苹,“玉大人,你說我怎么就攤上這事雹拄∈毡迹” “怎么了?”我有些...
    開封第一講書人閱讀 162,577評論 0 353
  • 文/不壞的土叔 我叫張陵滓玖,是天一觀的道長坪哄。 經(jīng)常有香客問我,道長势篡,這世上最難降的妖魔是什么翩肌? 我笑而不...
    開封第一講書人閱讀 58,176評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮禁悠,結果婚禮上念祭,老公的妹妹穿的比我還像新娘。我一直安慰自己绷蹲,他們只是感情好棒卷,可當我...
    茶點故事閱讀 67,189評論 6 388
  • 文/花漫 我一把揭開白布顾孽。 她就那樣靜靜地躺著,像睡著了一般比规。 火紅的嫁衣襯著肌膚如雪若厚。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,155評論 1 299
  • 那天蜒什,我揣著相機與錄音测秸,去河邊找鬼。 笑死灾常,一個胖子當著我的面吹牛霎冯,可吹牛的內容都是我干的。 我是一名探鬼主播钞瀑,決...
    沈念sama閱讀 40,041評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼沈撞,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了雕什?” 一聲冷哼從身側響起缠俺,我...
    開封第一講書人閱讀 38,903評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎贷岸,沒想到半個月后壹士,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,319評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡偿警,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,539評論 2 332
  • 正文 我和宋清朗相戀三年躏救,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片螟蒸。...
    茶點故事閱讀 39,703評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡盒使,死狀恐怖,靈堂內的尸體忽然破棺而出七嫌,到底是詐尸還是另有隱情忠怖,我是刑警寧澤,帶...
    沈念sama閱讀 35,417評論 5 343
  • 正文 年R本政府宣布抄瑟,位于F島的核電站,受9級特大地震影響枉疼,放射性物質發(fā)生泄漏皮假。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,013評論 3 325
  • 文/蒙蒙 一骂维、第九天 我趴在偏房一處隱蔽的房頂上張望惹资。 院中可真熱鬧,春花似錦航闺、人聲如沸褪测。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽侮措。三九已至懈叹,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間分扎,已是汗流浹背澄成。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留畏吓,地道東北人墨状。 一個月前我還...
    沈念sama閱讀 47,711評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像菲饼,于是被迫代替她去往敵國和親肾砂。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,601評論 2 353

推薦閱讀更多精彩內容