1. 程式人生 > >win10下開發部署Dapp(7):介面化、自動化、可眾籌、可升級的token

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,拒絕轉賬。