win10下開發部署Dapp(7):介面化、自動化、可眾籌、可升級的token
上一篇我們發行了一種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,拒絕轉賬。