一、什么是代币?
代币就类似于游乐场的游戏币,可以作为换取物品或服务的凭证。通常,代币需要使用货币来换取。与货币不同,代币的使用范围较小,一般只能够在特定范围内使用(比如游乐场、商店等等)。
在区块链世界中,代币其实就是一串数字,这串数字我们称之为“token”。token代表了区块链中的数字资产,具有一定价值。
与游戏币、Q币不一样,代币token具有以下三大特征:
1)权益证明:代表一种权利,一种固有的内在价值和使用价值;
2)加密:为了防止篡改,保护隐私;
3)可流通性:可以进行交易,兑换;
目前以太坊上有185,214个代币的智能合约,具体可以查阅:https://etherscan.io/tokens
二、ERC20标准
许多人应该都听过代码即法律(Code Is Law)。因为程序写完了,无论执行多少次都应该得到同样的结果,除非有外界因素的干扰。在多人协作的过程中一定是要按照一个标准来进行分工,这样才能最快地完成任务。
如果这么多代币的标准不统一,对于其他人来查看代码是相当痛苦的,众筹的人也就没有办法来检查代币分发的是否合理,也没有办法做到多种钱包的兼容。所以才推出了一种以太坊代币的标准:ERC20标准。【来自于:https://www.jianshu.com/p/a5158fbfaeb9】
ERC20标准提供了实现以太坊智能合约的一套接口函数。这些接口函数有:
contract ERC20 {
// 发行代币的总量
function totalSupply() constant returns (uint totalSupply);
// 获取该地址所拥有的token个数
function balanceOf(address _owner) constant returns (uint balance);
// 转账交易:将自己的token转账给_to地址,_value为转账个数
function transfer(address _to, uint _value) returns (bool success);
// 委托转账,与approve搭配使用
function transferFrom(address _from, address _to, uint _value) returns (bool success);
// 授权:批准_spender账户从自己的账户转移_value个token
function approve(address _spender, uint _value) returns (bool success);
// 返回_spender还能提取token的个数
function allowance(address _owner, address _spender) constant returns (uint remaining);
// 当成功转移token时,一定要触发Transfer事件
event Transfer(address indexed _from, address indexed _to, uint _value);
// 当调用approval函数成功时,一定要触发Approval事件
event Approveal(address indexed _owner, address indexed _spender, uint _value);
// ERC20代币的名字
string public constant name = "Token Name";
// ERC20代币的符号
string public constant symbol = "SYM";
// 支持几位小数点后几位。如果设置为3,表示支持0.001
uint8 public constant decimals = 18;
}
这些接口函数包含了token的转账、授权等功能。如果项目方要在以太坊上发行代币来进行融资,一定会按照这个标准来实现相应的函数。以下以BNB为例,看一下智能合约的实现原理。
三、BNB代币源码分析
contract SafeMath {
// 乘法(internal修饰的函数只能够在当前合约或子合约中使用)
function safeMul(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a * b;
assert(a == 0 || c / a == b);
return c;
}
// 除法
function safeDiv(uint256 a, uint256 b) internal pure returns (uint256) {
assert(b > 0);
uint256 c = a / b;
assert(a == b * c + a % b);
return c;
}
// 减法
function safeSub(uint256 a, uint256 b) internal pure returns (uint256) {
assert(b <= a);
assert(b >=0);
return a - b;
}
// 加法
function safeAdd(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
assert(c>=a && c>=b);
return c;
}
}
contract ZhongB is SafeMath{
// 代币的名字
string public name;
// 代币的符号
string public symbol;
// 代币支持的小数位
uint8 public decimals;
// 代表发行的总量
uint256 public totalSupply;
// 管理者
address public owner;
// 该mapping保存账户余额,Key表示账户地址,Value表示token个数
mapping (address => uint256) public balanceOf;
// 该mappin保存指定帐号被授权的token个数
// key1表示授权人,key2表示被授权人,value2表示被授权token的个数
mapping (address => mapping (address => uint256)) public allowance;
// 冻结指定帐号token的个数
mapping (address => uint256) public freezeOf;
// 定义事件
event Transfer(address indexed from, address indexed to, uint256 value);
event Burn(address indexed from, uint256 value);
event Freeze(address indexed from, uint256 value);
event Unfreeze(address indexed from, uint256 value);
// 构造函数(1000000, "ZhongB", 18, "ZB")
constructor(
uint256 initialSupply, // 发行数量
string tokenName, // token的名字 BinanceToken
uint8 decimalUnits, // 最小分割,小数点后面的尾数 1ether = 10** 18wei
string tokenSymbol // ZB
) public {
decimals = decimalUnits;
balanceOf[msg.sender] = initialSupply * 10 ** 18;
totalSupply = initialSupply * 10 ** 18;
name = tokenName;
symbol = tokenSymbol;
owner = msg.sender;
}
// 转账:某个人花费自己的币
function transfer(address _to, uint256 _value) public {
// 防止_to无效
assert(_to != 0x0);
// 防止_value无效
assert(_value > 0);
// 防止转账人的余额不足
assert(balanceOf[msg.sender] >= _value);
// 防止数据溢出
assert(balanceOf[_to] + _value >= balanceOf[_to]);
// 从转账人的账户中减去一定的token的个数
balanceOf[msg.sender] = SafeMath.safeSub(balanceOf[msg.sender], _value);
// 往接收帐号增加一定的token个数
balanceOf[_to] = SafeMath.safeAdd(balanceOf[_to], _value);
// 转账成功后触发Transfer事件,通知其他人有转账交易发生
emit Transfer(msg.sender, _to, _value);// Notify anyone listening that this transfer took place
}
// 授权:授权某人花费自己账户中一定数量的token
function approve(address _spender, uint256 _value) public returns (bool success) {
assert(_value > 0);
allowance[msg.sender][_spender] = _value;
return true;
}
// 授权转账:被授权人从_from帐号中给_to帐号转了_value个token
function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) {
// 防止地址无效
assert(_to != 0x0);
// 防止转账金额无效
assert(_value > 0);
// 检查授权人账户的余额是否足够
assert(balanceOf[_from] >= _value);
// 检查数据是否溢出
assert(balanceOf[_to] + _value >= balanceOf[_to]);
// 检查被授权人在allowance中可以使用的token数量是否足够
assert(_value <= allowance[_from][msg.sender]);
// 从授权人帐号中减去一定数量的token
balanceOf[_from] = SafeMath.safeSub(balanceOf[_from], _value);
// 往接收人帐号中增加一定数量的token
balanceOf[_to] = SafeMath.safeAdd(balanceOf[_to], _value);
// 从allowance中减去被授权人可使用token的数量
allowance[_from][msg.sender] = SafeMath.safeSub(allowance[_from][msg.sender], _value);
// 交易成功后触发Transfer事件,并返回true
emit Transfer(_from, _to, _value);
return true;
}
// 消币
function burn(uint256 _value) public returns (bool success) {
// 检查当前帐号余额是否足够
assert(balanceOf[msg.sender] >= _value);
// 检查_value是否有效
assert(_value > 0);
// 从sender账户中中减去一定数量的token
balanceOf[msg.sender] = SafeMath.safeSub(balanceOf[msg.sender], _value);
// 更新发行币的总量
totalSupply = SafeMath.safeSub(totalSupply,_value);
// 消币成功后触发Burn事件,并返回true
emit Burn(msg.sender, _value);
return true;
}
// 冻结
function freeze(uint256 _value) public returns (bool success) {
// 检查sender账户余额是否足够
assert(balanceOf[msg.sender] >= _value);
// 检查_value是否有效
assert(_value > 0);
// 从sender账户中减去一定数量的token
balanceOf[msg.sender] = SafeMath.safeSub(balanceOf[msg.sender], _value);
// 往freezeOf中给sender账户增加指定数量的token
freezeOf[msg.sender] = SafeMath.safeAdd(freezeOf[msg.sender], _value);
// freeze成功后触发Freeze事件,并返回true
emit Freeze(msg.sender, _value);
return true;
}
// 解冻
function unfreeze(uint256 _value) public returns (bool success) {
// 检查解冻金额是否有效
assert(freezeOf[msg.sender] >= _value);
// 检查_value是否有效
assert(_value > 0);
// 从freezeOf中减去指定sender账户一定数量的token
freezeOf[msg.sender] = SafeMath.safeSub(freezeOf[msg.sender], _value);
// 向sender账户中增加一定数量的token
balanceOf[msg.sender] = SafeMath.safeAdd(balanceOf[msg.sender], _value);
// 解冻成功后触发事件
emit Unfreeze(msg.sender, _value);
return true;
}
// 管理者自己取钱
function withdrawEther(uint256 amount) public {
// 检查sender是否是当前合约的管理者
assert(msg.sender == owner);
// sender给owner发送token
owner.transfer(amount);
}
}
四、代币部署和发行
(1)打开Metamask,切换到Ropsten Test Network。
(2)打开remix,选择Run -> Environment -> Injected Web3。
(3)选择要部署的合约,然后在Deploy输入框中填写构造函数参数后,点击Deploy按钮。
(4)部署成功后,重新打开Metamask,添加代币。
第一步:复制合约地址。
第二步:打开Metamask添加代币。
第三步:点击自定义代币,然后输入合约地址后点击下一步。
(4)确认添加信息无误后,点击“添加代币”按钮。添加成功后界面如下所示:
添加成功后,可以在etherscan上查看代币的信息。
五、验证合约
部署了一个合约之后,如果想让大家参与进来,那么必须接受大家的审计,以确保你的合约的功能确实如你所说,全世界的人都看到了合约的所有功能,那么就可以放心使用了。所以,上联的代币在被大家使用前都要进行验证。
执行验证的步骤:
第一步:切换到合约页面。
第二步:选择Code菜单,然后点击Verify and Publish,进入验证页面。
第三步:填写合约的相关信息,然后点击下一步。
第四步:把合约代码原封不动地复制到Contract Code输入框中。
第五步:让后点击“进行人机身份验证”,认证成功后点击Verify and Publish按钮。
验证成功后,在合约详情页面可以看到合约代码,如下图所示:
六、转账测试
第一步:连接Metamask。
第二步:打开Metamask插件,然后切换到Account2后添加代币。添加完成后可以看到,Account2的ZB代币数量为0。
第三步:复制Account2的地址,然后切换会Account1。接着在etherscan的合约详情页面进入WriteContract,输入转账信息后点击Write按钮。
第四步:打开Metamask插件,查看Account2的ZB代币余额。