上一篇我们发行了一种token,将全部的token发放到了creator的账户里,这样的token交易起来非常不便:我想买N个MTC token,需要给creator转一定量的ether,或者用支付宝转一定的RMB给他,他再往我的账户地址上转N个token——流通效率非常低。这其中还有不可避免地信任问题:我转了RMB给他,他却没有给我token,或者少给了token。
有两种思路可以解决以上问题:
- 交易所,creator把一定量的token approve给交易所的账户,有交易所进行token的售卖。
- 走类似Bancor的思路,把token的发行逻辑放到合约代码里,将代码开源,接受大家监督。
我们接下来就对上一篇的合约进行重构,使之变得更结构化、自动化,首先把常用的modifier、函数提取到一个Util中,以备重用。
contract Utils {
function Utils() public{
}
modifier greaterThanZero(uint256 _amount) {
require(_amount > 0);
_;
}
modifier validAddress(address _address) {
require(_address != 0x0);
_;
}
modifier notThis(address _address) {
require(_address != address(this));
_;
}
function safeAdd(uint256 _x, uint256 _y) internal pure returns (uint256) {
uint256 z = _x + _y;
assert(z >= _x);
return z;
}
function safeSub(uint256 _x, uint256 _y) internal pure returns (uint256) {
assert(_x >= _y);
return _x - _y;
}
function safeMul(uint256 _x, uint256 _y) internal pure returns (uint256) {
uint256 z = _x * _y;
assert(_x == 0 || z / _x == _y);
return z;
}
}
接下来是提取ERC20接口,实现一个通用的ERC20基类
contract ERC20Token, Utils {
string public name = '';
string public symbol = '';
uint8 public decimals = 0;
uint256 public totalSupply = 0;
mapping (address => uint256) public balanceOf;
mapping (address => mapping (address => uint256)) public allowance;
event Transfer(address indexed _from, address indexed _to, uint256 _value);
event Approval(address indexed _owner, address indexed _spender, uint256 _value);
function ERC20Token(string _name, string _symbol, uint8 _decimals) public{
//检验参数的合法性
require(bytes(_name).length > 0 && bytes(_symbol).length > 0);
name = _name;
symbol = _symbol;
decimals = _decimals;
}
function transfer(address _to, uint256 _value)public
validAddress(_to)
returns (bool success)
{
balanceOf[msg.sender] = safeSub(balanceOf[msg.sender], _value);
balanceOf[_to] = safeAdd(balanceOf[_to], _value);
emit Transfer(msg.sender, _to, _value);
return true;
}
function transferFrom(address _from, address _to, uint256 _value)
public
validAddress(_from)
validAddress(_to)
returns (bool success)
{
allowance[_from][msg.sender] = safeSub(allowance[_from][msg.sender], _value);
balanceOf[_from] = safeSub(balanceOf[_from], _value);
balanceOf[_to] = safeAdd(balanceOf[_to], _value);
emit Transfer(_from, _to, _value);
return true;
}
function approve(address _spender, uint256 _value)
public
validAddress(_spender)
returns (bool success)
{
require(_value == 0 || allowance[msg.sender][_spender] == 0);
allowance[msg.sender][_spender] = _value;
emit Approval(msg.sender, _spender, _value);
return true;
}
}
然后引入owner与owned两个概念,也就是控制合约与数据合约的概念。数据合约用来存放用户余额等信息,一经发布便不能再变动,所以数据合约的代码要慎之又慎。控制合约用来处理代币的集体逻辑,例如众筹,根据逻辑操纵数据合约。
//一般来说,数据合约要继承这个基类
contract Owned{
address public owner;
address public newOwner;
event OwnerUpdate(address _prevOwner, address _newOwner);
function Owned() public{
owner = msg.sender;
}
modifier ownerOnly {
assert(msg.sender == owner);
_;
}
function transferOwnership(address _newOwner) public ownerOnly {
require(_newOwner != owner);
newOwner = _newOwner;
}
function acceptOwnership() public {
require(msg.sender == newOwner);
owner = newOwner;
newOwner = 0x0;
emit OwnerUpdate(owner, newOwner);
}
}
//一般来说,控制合约要继承这个基类
contract Owner{
address public creator;
address public ownedDataContract;
Owned public dataContract;
//_dataContract must be specified when creating the owner contract.
function Owner(Owned _dataContract) public{
assert(address(_dataContract) != address(0));
creator = msg.sender;
dataContract = _dataContract;
}
modifier creatorOnly{
assert(msg.sender == creator);
_;
}
function transferTokenOwnership(address _newOwner) public creatorOnly {
dataContract.transferOwnership(_newOwner);
}
function acceptTokenOwnership() public creatorOnly {
dataContract.acceptOwnership();
}
}
接下来引入一个SmartContract基类,用来处理token的发行与销毁。
contract SmartToken is Owned, ERC20Token {
event NewSmartToken(address _token);
event Issuance(uint256 _amount);
event Destruction(uint256 _amount);
function SmartToken(string _name, string _symbol, uint8 _decimals)
ERC20Token(_name, _symbol, _decimals) public
{
emit NewSmartToken(address(this));
}
//只有数据合约的owner才有资格使用issue方法给某个账户发行一定数量的token
function issue(address _to, uint256 _amount)
public
ownerOnly
validAddress(_to)
notThis(_to)
{
totalSupply = safeAdd(totalSupply, _amount);
balanceOf[_to] = safeAdd(balanceOf[_to], _amount);
emit Issuance(_amount);
Transfer(this, _to, _amount);
}
function destroy(address _from, uint256 _amount) public {
require(msg.sender == _from || msg.sender == owner); // validate input
balanceOf[_from] = safeSub(balanceOf[_from], _amount);
totalSupply = safeSub(totalSupply, _amount);
emit Transfer(_from, this, _amount);
emit Destruction(_amount);
}
}
现在基础设施已经构建完毕,我们要实现一个数据合约、一个控制合约
contract MartinToken is SmartToken {
string public version = '0.1';
function MiningSharesToken()
SmartToken("MartinToken", "MTC", 18) public
{
}
function() public payable{
}
}
造一个简单的众筹合约,谁给这个合约转eth,谁就会收到1000倍的token作为回报,不存在赖账问题。
contract CrowdContract is Owner, Utils{
address public tokenAddr;
function CrowdContract(address token) Owner(Owned(token)) public{
tokenAddr = token;
}
function HandleContribute(address to, uint256 amount){
//假设我们众筹阶段,按照1:1000的比例收取eth。假设A给此合约转账3个eth,则发行3000个MTC给A账户
SmartToken mtToken = SmartToken (tokenAddr);
mtToken.issue(msg.sender, amount * 1000);
}
function() public payable{
HandleContribute(msg.sender, msg.value);
}
合约编写完毕,我们要部署,部署的时候,要按照以下顺序:
0.准备一个有ether余额的普通账户,用来部署合约,记为:creator。
1.使用creator部署MartinToken数据合约,得到合约地址,记为tokenAddr。
2.使用creator部署CrowdContract控制合约,得到合约地址,记为controllerAddr。
3.使用creator调用MartinToken合约的transferOwnership方法,参数为controllerAddr。
4.使用creator调用CrowdContract合约的acceptTokenOwnership方法,完成ownership的转换。
5.此时使用账户B给controllerAddr转账,就会收到1000倍的token。
假设饿哦们的CrowdContract逻辑发现了bug,需要对其进行升级,方法很简单:
0.准备好新的合约,记为NewCrowdContract。
1.使用creator账户部署NewCrowdContract合约,得到地址,记为newControllerAddr。
2.使用creator调用CrowdContract合约的transferTokenOwnership方法,参数为newControllerAddr。
3.使用creator调用NewCrowdContract合约的acceptTokenOwnership方法,完成ownership的转换。
4.此时使用账户B给newControllerAddr转账,才会收到1000倍的token,如果用户不知情,仍然给controllerAddr地址转账,是收不到token的。所以升级一定要慎重。一般来讲,可以在控制合约的fallback函数里检查自己是不是token的owner,如果不是,直接throws,拒绝转账。