win10下开发部署Dapp(7):接口化、自动化、可众筹、可升级的token

版权声明:版权所有。转载请注明出处:扶风的博客 https://blog.csdn.net/JohnnyMartin/article/details/79642784

上一篇我们发行了一种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,拒绝转账。

猜你喜欢

转载自blog.csdn.net/JohnnyMartin/article/details/79642784