代币(token)在以太坊中代表数字资产,并不是所有的代币都符合特定的规范。基于ERC20的代币更容易互换,也可以与各种钱包dapp兼容。标准化非常有利,也就意味着这些资产可以用于不同的平台和项目,否则只能用在特定的场合。
ERC20协议需要实现的接口标准如下:
contract ERC20 {
/* 代币总量 */
function totalSupply() constant returns (uint totalSupply);
/* 账户代币查询 */
function balanceOf(address _owner) constant returns (uint balance);
/* 代币转账(从自己账户) */
function transfer(address _to, uint _value) returns (bool success);
/* 代币转账(从委托账户) */
function transferFrom(address _from, address _to, uint _value) returns (bool success);
/* 在自己账户设置_spender账户的委托金 */
function approve(address _spender, uint _value) returns (bool success);
/* 返回_spender仍然被允许从_owner提取的金额。 */
function allowance(address _owner, address _spender) constant returns (uint remaining);
/* 事件:转账事件通知 */
event Transfer(address indexed _from, address indexed _to, uint _value);
/* 事件:委托设置事件通知 */
event Approval(address indexed _owner, address indexed _spender, uint _value);
}
官方一个功能基本完整的代币合约:
/* 一个相对比较完善的代币合约 */
pragma solidity ^0.4.16;
/* 创建一个父类, 账户管理员 */
contract owned {
address public owner;
function owned() public {
owner = msg.sender;
}
/* modifier是修改标志 */
modifier onlyOwner {
require(msg.sender == owner);
_;
}
/* 修改管理员账户, onlyOwner代表只能是用户管理员来修改 */
function transferOwnership(address newOwner) onlyOwner public {
owner = newOwner;
}
}
/* receiveApproval服务合约指示代币合约将代币从发送者的账户转移到服务合约的账户(通过调用服务合约的 */
interface tokenRecipient { function receiveApproval(address _from, uint256 _value, address _token, bytes _extraData) public; }
contract TokenERC20 {
// 代币(token)的公共变量
string public name; //代币名字
string public symbol; //代币符号
uint8 public decimals = 18; //代币小数点位数, 18是默认, 尽量不要更改
uint256 public totalSupply; //代币总量
// 记录各个账户的代币数目
mapping (address => uint256) public balanceOf;
// A账户存在B账户资金
mapping (address => mapping (address => uint256)) public allowance;
// 转账通知事件
event Transfer(address indexed from, address indexed to, uint256 value);
// 销毁金额通知事件
event Burn(address indexed from, uint256 value);
/* 构造函数 */
function TokenERC20(
uint256 initialSupply,
string tokenName,
string tokenSymbol
) public {
totalSupply = initialSupply * 10 ** uint256(decimals); // 根据decimals计算代币的数量
balanceOf[msg.sender] = totalSupply; // 给生成者所有的代币数量
name = tokenName; // 设置代币的名字
symbol = tokenSymbol; // 设置代币的符号
}
/* 私有的交易函数 */
function _transfer(address _from, address _to, uint _value) internal {
// 防止转移到0x0, 用burn代替这个功能
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);
}
/* 传递tokens */
function transfer(address _to, uint256 _value) public {
_transfer(msg.sender, _to, _value);
}
/* 从其他账户转移资产 */
function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) {
require(_value <= allowance[_from][msg.sender]); // Check allowance
allowance[_from][msg.sender] -= _value;
_transfer(_from, _to, _value);
return true;
}
/* 授权第三方从发送者账户转移代币,然后通过transferFrom()函数来执行第三方的转移操作 */
function approve(address _spender, uint256 _value) public
returns (bool success) {
allowance[msg.sender][_spender] = _value;
return true;
}
/*
为其他地址设置津贴, 并通知
发送者通知代币合约, 代币合约通知服务合约receiveApproval, 服务合约指示代币合约将代币从发送者的账户转移到服务合约的账户(通过调用服务合约的transferFrom)
*/
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;
}
}
/**
* 销毁代币
*/
function burn(uint256 _value) public returns (bool success) {
require(balanceOf[msg.sender] >= _value); // Check if the sender has enough
balanceOf[msg.sender] -= _value; // Subtract from the sender
totalSupply -= _value; // Updates totalSupply
Burn(msg.sender, _value);
return true;
}
/**
* 从其他账户销毁代币
*/
function burnFrom(address _from, uint256 _value) public returns (bool success) {
require(balanceOf[_from] >= _value); // Check if the targeted balance is enough
require(_value <= allowance[_from][msg.sender]); // Check allowance
balanceOf[_from] -= _value; // Subtract from the targeted balance
allowance[_from][msg.sender] -= _value; // Subtract from the sender's allowance
totalSupply -= _value; // Update totalSupply
Burn(_from, _value);
return true;
}
}
/******************************************/
/* ADVANCED TOKEN STARTS HERE */
/******************************************/
contract MyAdvancedToken is owned, TokenERC20 {
uint256 public sellPrice;
uint256 public buyPrice;
/* 冻结账户 */
mapping (address => bool) public frozenAccount;
/* This generates a public event on the blockchain that will notify clients */
event FrozenFunds(address target, bool frozen);
/* 构造函数 */
function MyAdvancedToken(
uint256 initialSupply,
string tokenName,
string tokenSymbol
) TokenERC20(initialSupply, tokenName, tokenSymbol) public {}
/* 转账, 比父类加入了账户冻结 */
function _transfer(address _from, address _to, uint _value) internal {
require (_to != 0x0); // Prevent transfer to 0x0 address. Use burn() instead
require (balanceOf[_from] >= _value); // Check if the sender has enough
require (balanceOf[_to] + _value > balanceOf[_to]); // Check for overflows
require(!frozenAccount[_from]); // Check if sender is frozen
require(!frozenAccount[_to]); // Check if recipient is frozen
balanceOf[_from] -= _value; // Subtract from the sender
balanceOf[_to] += _value; // Add the same to the recipient
Transfer(_from, _to, _value);
}
/// 向指定账户增发资金
function mintToken(address target, uint256 mintedAmount) onlyOwner public {
balanceOf[target] += mintedAmount;
totalSupply += mintedAmount;
Transfer(0, this, mintedAmount);
Transfer(this, target, mintedAmount);
}
/// 冻结 or 解冻账户
function freezeAccount(address target, bool freeze) onlyOwner public {
frozenAccount[target] = freeze;
FrozenFunds(target, freeze);
}
function setPrices(uint256 newSellPrice, uint256 newBuyPrice) onlyOwner public {
sellPrice = newSellPrice;
buyPrice = newBuyPrice;
}
/// @notice Buy tokens from contract by sending ether
function buy() payable public {
uint amount = msg.value / buyPrice; // calculates the amount
_transfer(this, msg.sender, amount); // makes the transfers
}
function sell(uint256 amount) public {
require(this.balance >= amount * sellPrice); // checks if the contract has enough ether to buy
_transfer(msg.sender, this, amount); // makes the transfers
msg.sender.transfer(amount * sellPrice); // sends ether to the seller. It's important to do this last to avoid recursion attacks
}
}
代币合约部署方式很多,本地工具有truffle,官方有网页编译器remix,另外以太坊官方钱包也可以完成合约部署。本文主要介绍从官方remix编译器编译、部署到测试私链的方法。并将代币添加到,在以太坊官方钱包完成转账。
官方remix编译器地址:http://remix.ethereum.org/
合约编译,步骤图示如下所示:
合约部署,步骤:
remix编译器合约调用示例
前面说到,按照ERC20协议部署的代币合约有很好的兼容性,可以将代币添加到官方钱包完成转账。
命令行启动官方钱包,将钱包连接到部署合约的私链网络,命令如下:
./Ethereum\ Wallet --rpc http://10.11.178.42:8545
在钱包上面工具栏选择CONTACTS,选择“WATCH TOKEN”, 将代币合约部署地址填到地址栏,点击ok完成添加,如下图。
在工具栏选择SEND,填入相关的代币信息,确保无误后点击send完成转账。