建立自己的数字货币

飞来飞去   |     |   5326 次阅读

本文介绍如何利用以太坊智能合约建立自己的代币。
这篇文章来源于以太坊官网中的材料,本文是作者通过阅读材料,亲自试验后写的中文版材料,仅供初学者参考。
本文不是对原文进行完整的翻译,主要是作者消化后整理出来的文档,需要看原文的请直接访问原文地址https://ethereum.org/token

一、什么是代币?

代币是利用以太坊的智能合约编写的数字货币。程序员可以通过编写智能合约代码,创建一种新的数字货币。
你可以实现的功能:

基本功能:
-创建数字货币,设置货币的名称、货币总量、货币图标等基本参数。
-创建货币交易功能。实现货币在不同用户之间的转移。

上面的是基本功能,已经可以实现基本的代表交换,下面是高级功能,可以实现更加复杂的应用。
高级功能:
-创建货币的管理者。虽然区块链是去中心化的,但是可以实现合约的管理者,这在许多应用中是有需求的。
-实现数字货币的黑白名单。通过设置黑白名单,可以冻结某些账户。资产仍在账户,但是不允许交易。
-实现货币增发。就如同美联储狂印钞票一样,你作为货币的创建者,也可以实现货币增发的功能,可以在原有货币总量以外,增加额外的钞票。(想想都是很激动了)
-实现挖矿。比特币、以太币的挖矿机制非常出名,矿工、矿机、矿池等概念大家都很熟悉。你可以在自己的货币中实现挖矿机制,奖励实现挖矿目的的用户一些代币。我目前还没有想到这个功能的用处,但是读这部分的内容有助于理解比特币、以太币的挖矿机制。
-实现代币和其他货币的自动兑换。你可以在自己的货币中实现代币与其他数字货币的兑换机制。这个很激动人心哦,你可以像银行一样收交易费啦。例如,买入代币的价格是1ETH,卖出代笔的价格是0.8ETH,这意味着每个代币的流入流出,你可以收取0.2ETH的交易费。是不是很激动,前提是你要忽悠大家用你的代币。
-实现gas的自动补充。以太坊中的交易时需要gas汽油(实际上就是eth)。为了解决某些用户没有ETH,只有代币的情况,可以设计自动补充gas的功能。这个功能将使你的代币更加好用。(用的人越多,收交易费越多,嘿嘿)

以上就是以太坊官方教程中提到的功能。通过学习这些功能的写法,你将学会基本的智能合约编程方法,能够开发出更加多样化的功能。

接下来,我将介绍如何编程智能合约的代码,实现上述功能。

二、实现代币的基本功能

从简单入手,首先实现一个包含基本功能的代币。只需实现代币定义和交易功能。
实现代币的过程是编写智能合约的代码。智能合约你可以理解为是一种程序,类似于C,C++。不要怕,这种语言很好懂,稍微看看就懂啦。
下面是完整的代码,可以直接部署。
-------------------------------------我是分割线,不要拷贝我-----------------------------------------------

/* contract 类似于C++中的类 */
contract MyToken {
/* 设置一个数组存储每个账户的代币信息 */
mapping (address => uint256) public balanceOf;

/* 设置变量 */

/* name 代币名称 */
/* symbol 代币图标 */
/* decimals 代币小数点位数 */
string public name;
string public symbol;
uint8 public decimals;

/* event事件,它的作用是提醒客户端发生了这个事件,你会注意到钱包有时候会在右下角弹出信息 */
event Transfer(address indexed from, address indexed to, uint256 value);

/* 下面这个类似于C++的构造函数,接收用户输入,实现代币的初始化 */
function MyToken(uint256 initialSupply, string tokenName, uint8 decimalUnits, string tokenSymbol) {
balanceOf[msg.sender] = initialSupply; // Give the creator all initial tokens
name = tokenName; // Set the name for display purposes
symbol = tokenSymbol; // Set the symbol for display purposes
decimals = decimalUnits; // Amount of decimals for display purposes
}

/* 代币交易的函数 */
function transfer(address _to, uint256 _value) {
/* 检查发送方有没有足够的代币 */
if (balanceOf[msg.sender] < _value || balanceOf[_to] + _value < balanceOf[_to])
throw;

/* 交易过程,发送方减去代币,接收方增加代币 */
balanceOf[msg.sender] -= _value;
balanceOf[_to] += _value;

/* 提醒客户端发生了交易事件 */
Transfer(msg.sender, _to, _value);
}
}

-------------------------------------我是分割线,不要拷贝我-----------------------------------------------
下面是部署方式,我使用以太坊钱包mist部署合约。
为了节省资金,我使用以太坊测试网络,测试网络是可以用以太坊钱包挖矿的,挖矿难度不大,我已经挖到20个了。做实验够用了。如果你实在挖不到币的话,请在下方留下你的地址,我可以资助你1个以太币做实验(是测试网络的以太币哦)。
其他方式有很多,这里不讲。我觉得初学者用mist最简单。下面是部署的界面。

这个页面是以太坊钱包“CONTRACTS”页面,点击"DEPLAY NEW CONTRACT"按钮就能看见。
图片的最上面是给合约输入以太币,目前不用设置,保持0即可。图中央的左侧是放置代码的地方,将拷贝的代码粘贴到这里即可。右侧的红圈位置可以选择合约,请点击下拉框选择“My Token”。选择以后,会出现图片上的样子,这里可以输入代币的总数(Initial supply),代币的名称(Token name),代币的小数点数目(Decimal units),代币的符号(Token symbol)。你可以按照图上的来填,也可以试着随便填,理解用途。
填写完成后,点击最下面的“DEPLOY”按钮,如果成功,则在钱包主页面可以看到合约正在等待验证,如果失败,会提示原因,请检查输入。

如果你的网络正常,通常1分钟内合约就验证通过,这是可以在以太坊钱包“CONTRACTS”页面看到我们建立的代币TESTCOIN.

上图标红的就是我建立的代币,点击进入代币的管理页面。

这个图就是我建立的代币的管理页面,这个页面可以查看合约持有的代币数量,每个用户持有的代币数量,执行代币交易等等。
图中左侧可以查看代币的基本信息,名称,小数点位数,符号。
图中左侧标红的位置可以查看指定用户代币的持有数量,只需将指定用户的地址填入红圈中的位置,即可自动显示。
图中右侧是代币的交易函数,可以进行代币交易。
“Select function”,这个选择transfer函数
“To-address”,这个是指接收代币用户的地址
"Value",发送代币数量
“Execute from”,这个地方可以选择发送方的地址。
点击“EXECUTE”按钮,就可以发送。

收到货币的人想要查看代币,只需要在“CONTRACTS”页面的"Watch token"输入对应的代币合约地址即可。

截止到这里,一个具备基本功能的代币就实现了。这个代币可以用于积分管理,简单的交易。
等后续实现更多功能后,可以有更多用途。

三、实现代币的高级功能

接下来,我们要在上文的基础上增加下列高级功能:
1、创建货币的管理者。虽然区块链是去中心化的,但是可以实现合约的管理者,这在许多应用中是有需求的。
2、实现货币增发。就如同美联储狂印钞票一样,你作为货币的创建者,也可以实现货币增发的功能,可以在原有货币总量以外,增加额外的钞票。(想想都是很激动了)
3、实现数字货币的黑白名单。通过设置黑白名单,可以冻结某些账户。资产仍在账户,但是不允许交易。
4、实现代币和其他货币的自动兑换。你可以在自己的货币中实现代币与其他数字货币的兑换机制。这个很激动人心哦,你可以像银行一样收交易费啦。例如,买入代币的价格是1ETH,卖出代笔的价格是0.8ETH,这意味着每个代币的流入流出,你可以收取0.2ETH的交易费。是不是很激动,前提是你要忽悠大家用你的代币。
5、实现gas的自动补充。以太坊中的交易时需要gas汽油(实际上就是eth)。为了解决某些用户没有ETH,只有代币的情况,可以设计自动补充gas的功能。这个功能将使你的代币更加好用。(用的人越多,收交易费越多,嘿嘿)

我介绍的思路是这样的:
首先给出全部的代码,这个代码包含了上述所有功能,可以直接部署。
然后,按照功能分别介绍代码和相关知识。

以下是代码,具备所有的高级功能,可以直接部署在以太坊钱包mist。具体的方法可参考上一篇文章。

-------------------------------------我是分割线,不要拷贝我-----------------------------------------------

/* 建立一个新合约,类似于C++中的类,实现合约管理者的功能 */
contract owned {
    address public owner;

    function owned() {
        owner = msg.sender;
    }

    modifier onlyOwner {
        if (msg.sender != owner) throw;
        _
    }
        /* 管理者的权限可以转移 */
    function transferOwnership(address newOwner) onlyOwner {
        owner = newOwner;
    }
}
/* 注意“contract MyToken is owned”,这类似于C++中的派生类的概念 */
contract MyToken is owned{
    /* Public variables of the token */
    string public standard = 'Token 0.1';
    string public name;
    string public symbol;
    uint8 public decimals;
    uint256 public totalSupply;
        uint256 public sellPrice;
        uint256 public buyPrice;
        uint minBalanceForAccounts;                                         //threshold amount

    /* This creates an array with all balances */
    mapping (address => uint256) public balanceOf;
        mapping (address => bool) public frozenAccount;

    /* This generates a public event on the blockchain that will notify clients */
    event Transfer(address indexed from, address indexed to, uint256 value);
        event FrozenFunds(address target, bool frozen);

    /* Initializes contract with initial supply tokens to the creator of the contract */
    function MyToken(
    uint256 initialSupply,
    string tokenName,
    uint8 decimalUnits,
    string tokenSymbol,
    address centralMinter
    ) {
    if(centralMinter != 0 ) owner = msg.sender;
        balanceOf[msg.sender] = initialSupply;              // Give the creator all initial tokens
        totalSupply = initialSupply;                        // Update total supply
        name = tokenName;                                   // Set the name for display purposes
        symbol = tokenSymbol;                               // Set the symbol for display purposes
        decimals = decimalUnits;                            // Amount of decimals for display purposes
    }

    /* 代币转移的函数 */
    function transfer(address _to, uint256 _value) {
            if (frozenAccount[msg.sender]) throw;
        if (balanceOf[msg.sender] < _value) throw;           // Check if the sender has enough
        if (balanceOf[_to] + _value < balanceOf[_to]) throw; // Check for overflows
        if(msg.sender.balance<minBalanceForAccounts) sell((minBalanceForAccounts-msg.sender.balance)/sellPrice);
        if(_to.balance<minBalanceForAccounts)      _to.send(sell((minBalanceForAccounts-_to.balance)/sellPrice));
        balanceOf[msg.sender] -= _value;                     // Subtract from the sender
        balanceOf[_to] += _value;                            // Add the same to the recipient
        Transfer(msg.sender, _to, _value);                   // Notify anyone listening that this transfer took place
    }

         /* 货币增发的函数 */
        function mintToken(address target, uint256 mintedAmount) onlyOwner {
            balanceOf[target] += mintedAmount;
            totalSupply += mintedAmount;
            Transfer(0, owner, mintedAmount);
            Transfer(owner, target, mintedAmount);
        }
    /* 冻结账户的函数 */
        function freezeAccount(address target, bool freeze) onlyOwner {
            frozenAccount[target] = freeze;
            FrozenFunds(target, freeze);
        }
        /* 设置代币买卖价格的函数 */
        function setPrices(uint256 newSellPrice, uint256 newBuyPrice) onlyOwner {
            sellPrice = newSellPrice;
            buyPrice = newBuyPrice;
        }
         /* 从合约购买货币的函数 */
        function buy() returns (uint amount){
            amount = msg.value / buyPrice;                     // calculates the amount
            if (balanceOf[this] < amount) throw;               // checks if it has enough to sell
            balanceOf[msg.sender] += amount;                   // adds the amount to buyer's balance
            balanceOf[this] -= amount;                         // subtracts amount from seller's balance
            Transfer(this, msg.sender, amount);                // execute an event reflecting the change
            return amount;                                     // ends function and returns
        }
        /* 向合约出售货币的函数 */
        function sell(uint amount) returns (uint revenue){
            if (balanceOf[msg.sender] < amount ) throw;        // checks if the sender has enough to sell
            balanceOf[this] += amount;                         // adds the amount to owner's balance
            balanceOf[msg.sender] -= amount;                   // subtracts the amount from seller's balance
            revenue = amount * sellPrice;                      // calculate the revenue
            msg.sender.send(revenue);                          // sends ether to the seller
            Transfer(msg.sender, this, amount);                // executes an event reflecting on the change
            return revenue;                                    // ends function and returns
        }

    /* 设置自动补充gas的阈值信息 */
        function setMinBalance(uint minimumBalanceInFinney) onlyOwner {
            minBalanceForAccounts = minimumBalanceInFinney * 1 finney;
        }
}

-------------------------------------我是分割线,不要拷贝我-----------------------------------------------

接着,我们按照功能分别介绍
3.1创建货币的管理者。
虽然区块链是去中心化的,但是可以实现合约的管理者,这在许多应用中是有需求的。可以通过设置,给智能合约添加管理人员。
添加的过程可以利用继承的概念。
3.1.1代码解析
首先定义一个父类

contract owned {
address public owner;
function owned() {
owner = msg.sender;
}

上述代码定义一个变量“owner”,这个变量的类型是address,这是用于存储代币的管理者。
owned()类似于C++中的构造函数,功能是给owner赋值。

接下来定义一个modifier(修改标志),可以理解为函数的附属条件。这个条件的内容是假设发送者不是owner(管理者),就跳出。起到一个身份鉴别的作用。

modifier onlyOwner {
if (msg.sender != owner) throw;
_
}

接着定义一个transferOwnership函数,这个函数是用于转移管理者的身份。

function transferOwnership(address newOwner) onlyOwner {
owner = newOwner;
}

注意,transferOwnership后面跟着 “onlyOwner”。所以这个函数的前提是,发送者必须是owner。

接着修改MyToken.
contract MyToken is owned{
//在mytoken中添加了地址变量centralMinter,这个变量是有输入位置的。

function MyToken(
uint256 initialSupply,
string tokenName,
uint8 decimalUnits,
string tokenSymbol,
address centralMinter
) {
if(centraMinter != 0) owner=msg.sender;

上述的if从句,只要输入地址不为0,拥有者就是发送者,所以这里输入什么都没关系。这个if从句,目前没看到有什么用处。
3.1.2代码功能测试
实验内容:
1、建立合约,设置合约的管理者为账号1。
实验成功,实验过程很简单,就不说了。
2、将管理者从账号1转移给账号2.
实验成功。转移成功后可以在代币页面查看,“OWNER”是否已经更改。

3.2实现货币增发
就如同美联储狂印钞票一样,你作为货币的创建者,也可以实现货币增发的功能,可以在原有货币总量以外,增加额外的钞票。(想想都是很激动了)。
可以实现货币增发。通过代码可以实现管理者给特定人员增发代币。这个代币是凭空产生的,这将导致代币总量发生变化。
这个函数可以实现挖矿的功能。当矿工达到一定目标后,管理者可以通过调用函数给矿工转移一定的资金。
3.2.1代码解析

1 function mintToken(address target, uint256 mintedAmount) onlyOwner {
2 balanceOf[target] += mintedAmount;
3 totalSupply += mintedAmount;
4 Transfer(0, owner, mintedAmount);
5 Transfer(owner, target, mintedAmount);
6 }

第2句代码给指定目标增加代币数量;
第3句代码给代币总量增加相应的数目;
第4句和第5句代码的意义只是提醒客户端发生了这样的交易。
体会:
凡是public参数都可以在钱包看;
凡是函数都可以在钱包中调用
3.2.2代码功能测试
设计实验:
1、给指定地址增发代币。
功能实现成功,管理者可以增发货币给指定地址。
2、使用费管理者调用增发函数
失败。非管理者无法增发。

3.3实现数字货币的黑白名单
通过设置黑白名单,可以冻结某些账户。资产仍在账户,但是不允许交易。
本文演示的是设置黑名单,即黑名单中的用户不能转账。
3.3.1代码解析

mapping (address => bool) public frozenAccount;
event FrozenFunds(address target, bool frozen);
function freezeAccount(address target, bool freeze) onlyOwner {
frozenAccount[target] = freeze;
FrozenFunds(target, freeze);
}

申请一个数组“freezeAccount”,存储冻结账户的地址和冻结信息
申请一个事件“FrozenFunds”,提醒客户端发生了冻结
建立一个函数“freezeAccount”,设置冻结数组对应位置为freeze,
在transfer中增加冻结代码
function transfer(address _to, uint256 _value)
{ if (frozenAccount[msg.sender]) throw;
假设账户冻结,则transfer函数跳出。
3.3.2代码功能测试
设置实验:
1、正常情况可交易
实验成功
2、冻结后,不能交易
实验成功
3、只有管理员可以冻结。
实验成功
4、能不能冻结自己
实验成功。可以冻结管理者自己。
5、能不能设置0,设置0是不是就是解锁
实验成功。设置0即解锁。
这个图是代币管理页面,在address中输入地址,即可查看是否冻结,NO代表没有冻结,YES代表冻结。

3.4实现代币和其他货币的自动兑换。
你可以在自己的货币中实现代币与其他数字货币的兑换机制。这个很激动人心哦,你可以像银行一样收交易费啦。
实现代币和数字货币兑换的代码
3.4.1代码解析

function buy() returns (uint amount){
amount = msg.value / buyPrice; // 这个value是用户输入的购买代币支付的以太币数目。amount是根据汇率算出来的代币数目
if (balanceOf[this] < amount) throw; // checks if it has enough to sell
balanceOf[msg.sender] += amount; // 购买者增加代币
balanceOf[this] -= amount; // 合约减少代币
Transfer(this, msg.sender, amount); // execute an event reflecting the change
return amount; // ends function and returns
}

function sell(uint amount) returns (uint revenue){
if (balanceOf[msg.sender] < amount ) throw; // checks if the sender has enough to sell
balanceOf[this] += amount; // 合约增加代币
balanceOf[msg.sender] -= amount; // 出售者减少代币
revenue = amount * sellPrice; // amount是用户数输入的出售代币的数量
msg.sender.send(revenue); // 用户获得因为输出代币得到的以太币
Transfer(msg.sender, this, amount); // executes an event reflecting on the change
return revenue; // ends function and returns
}

这里的代码实现的是简单的买卖。即合约本身作为中央银行,用户和合约做买卖。用户从合约购买代币,用户向合约出售代币。
注意:这里的代码没有实现检测功能,即可能出现合约没有代币和合约没有以太币,导致交易异常。这个代码没有处理。实际使用时,请自行添加检测代码。

/* 设置代币买卖价格的函数 */

function setPrices(uint256 newSellPrice, uint256 newBuyPrice) onlyOwner {
sellPrice = newSellPrice;
buyPrice = newBuyPrice;
}

这个函数是设置代币的汇率。包括购买汇率buyPrice,出售汇率sellPrice。我们在实验时,为了简单,设置buyPrice=sellPrice=0.01ETH。当然这个比例是自由设定的。在实际中,你可以设计买入代币buyPrice的价格是1ETH,卖出代币sellPrice的价格是0.8ETH,这意味着每个代币的流入流出,你可以收取0.2ETH的交易费。是不是很激动,前提是你要忽悠大家用你的代币。
3.4.2代码功能测试
设计实验:
1、调用setPrices函数,设计汇率。
实验成功。注意,智能合约汇率的单位是wei,1个以太币ETH=10的18次方个wei。
我们设计buyPrice=sellPrice=0.01ETH=10000000000000000(10的16次方)
设置价格也是1次交易。每一次价格变动都会写入区块链
2、账号1买入200代币,猜测买入不成功
实验不成功。显示不能买,因为合约没代币,只有eth
3、账号1售出100代币,猜测成功,账号1获得1ETH
实验成功.账号1得到1个代币,出售成功。当前显示gf1合约有100个代币,9个eth。
4、账号2售出800代币,猜测成功,获得8ETH,合约ETH为1
实验成功,账号2得到8个代币,出售成功。当前显示gf1合约有900个代币,1个eth。
5、账号2买入500代币,猜测成功,收取5ETH.
实验成功,账号2得到500个代币,购买成功。当前显示gf1合约有400个代币,6个eth。
6、修改价格,sell 修改为10的17次方。这意味着代币价格升值了。只需50个代币,就能换取5ETH。
实验成功。修改了sell价格。
7、账号1售出60代币,收取6ETH,合约还有0ETH,460个代币。
实验成功。成功交易。当前显示gf1合约有460个代币,0个eth。
sell price 设置为100000000000000000,这意味着账号2可以用60个代币获得6ETH。合约破产了,无力支付剩余的代币。
注意,此时合约以及破产,合约没有以太币ETH,但是用户仍然有代币。合约无法承兑了。
8、账号2售出100代币,猜测不成功。
实验成功。然交易成功,账号2减少100代币,gf1合约增加100代币,但是账号2没得到对应的eth。
猜测,可能的原因是, msg.sender.send(revenue); 这个函数,执行失败。
9、重新设置买卖价格,高价购入代币,看以前的欠款会不会补交。 猜测不会补交。这是一个漏洞,可能通过修改售价。窃取资金。普通用户是没有权限的。
实验成功,确实没有补交,账户1花费5ETH购买50个代币。
11、账号2售出50个代币,获得合约剩余的5个ETH。
实验成功。
以后,如果账户1和账户2再售出代币,将不能得到ETH。
** 注意:这和目前白帽黑客在DAO上做的一样,合约本身已经没有钱了。代币就失去了价值。目前,还没有机制,可以检测是否还有足够的钱支持代币兑换。**
实际的交易系统需要考虑这个问题。至少要有提示。目前的DAO出现了这个问题,DAO中的以太币已经被白猫黑客转移到其他地方。现在如果通过分裂的方式兑换DAO币,会导致得不到以太币。造成资金损失。具体内容请参考http://ethfans.org/topics/404

3.5实现gas的自动补充。以太坊中的交易时需要gas汽油(实际上就是eth)
为了解决某些用户没有ETH,只有代币的情况,可以设计自动补充gas的功能。这个功能将使你的代币更加好用。(用的人越多,收交易费越多,嘿嘿)
以太坊中每一次交易都需要支付一定的交易费用(gas,eth)。在某些案例中,不希望客户去处理eth的事情。
所以可以通过代码实现代币和eth的自动兑换,当用户ETH比较少的时候,自动更换一部分代币,得到足够交易的eth。
对于用户来说,只需处理代币,而不用了解背后的ETH。
3.5.1代码解析

uint minBalanceForAccounts;//注意,这个参数是一个私有变量,意味着钱包里看不见。
function setMinBalance(uint minimumBalanceInFinney) onlyOwner {
minBalanceForAccounts = minimumBalanceInFinney * 1 finney;
}

申请一个变量minBalanceForAccounts,存储自动兑换gas的阈值
创建一个设置阈值的函数

/* Send coins */
function transfer(address _to, uint256 _value) {
...
if(msg.sender.balance<minBalanceForAccounts)
sell((minBalanceForAccounts-msg.sender.balance)/sellPrice);
}

在交易函数中,提前做一个检测,如果账户的eth不够阈值,则交易。

还有一种做法是,发送者检测收款方有没有足够的ETH,如果没有,发送者则兑换一部分自己的代币,将得到的ETH发送给收款方(这种做法就是为收款方服务,收款方不用处理ETH、GAS的事情)。

/* Send coins */
function transfer(address _to, uint256 _value) {
...
if(_to.balance<minBalanceForAccounts)
_to.send(sell((minBalanceForAccounts-_to.balance)/sellPrice));
}

3.5.2代码功能测试
设计实验:
1、账户3给账户1转款,账户3没有eth。检查是否执行了自动兑换。
实验失败。发现错误原因,还没有设置价格。
首先设置价格
1代币=1000000000000000(16个0)
即使设置了价格,还是不能执行。原因是账户3目前没有ETH,因此不足以支付交易的费用gas。所以,账户3必须拥有足够的ETH来执行交易。
重新设计实验。账户1给账户3转账0.005ETH. 成功
实验成功,账户自动兑换了4个代币,获得0.004个ETH。目前ETH总数是0.006ETH。
2、账户1给账户3转款,检查账户3的变动。前提条件,先将账户3的ETH清0.
实验成功。发现账户兑换了5个代币,发送给账户3。
有意思的是,收款方收到的不是0.005,而是0.0044,应该是扣除了手续费。

截止到这里,所有的高级功能都已经实现了。
以太坊官网的TOKEN教程中还实现了挖矿的功能。有兴趣的可以直接看原文。
代币的教程就写到这里,以上的实验我都在自己的电脑上进行了实验。欢迎交流。

 
5 人喜欢