一种可升级的ERC20代币合约

        在以太坊中,流通的数字货币除了原生的以太币外,就是各种各样五花八门的ERC20代币了。通常,ERC20代币合约部署后是不需要升级的,但是有时又存在升级代币合约的需求。笔者在学习Aragon的过程中发现很早就有可升级ERC20代币合约的设计方法,这里把它分享出来,以供有需求的开发者一起学习。

一、设计思想

        可升级ERC20代币设计借用了区块链的设计思想。区块链是一个区块接一个区块的按时间顺序链接,每个区块保存了当时那个时刻的世界状态(借用超级账本的术语)并且可以分叉。可升级ERC20代币首先也是将每个用户和代币的发行总量按照区块进行保存,然后把整个合约看作是一条链。部署升级合约或者克隆合约时就是产生一个新的分叉,用来保存新的数据,旧的历史数据仍然保存在旧合约中不用迁移。新合约有一个链接指向旧合约,用来查询历史数据或者进行计算。这其中旧合约仍是可用的,它和新合约是独立的,相当于并行的两条链。笔者认为这和以太坊的分叉很相似,当然不会存在大家选择一个共同的较长的链这么一个说法,你使用哪个合约(新或旧),就在哪个分叉上。

二、主要功能

        可升级ERC20代币主要功能是让你以现在的代币分布重新克隆一个新的代币出来。你既可以升级合约代码后重新部署,也可以代码不变直接克隆一个新币。可以指定克隆(分叉)的时刻(区块高度),甚至可以指定为将来的某个时刻。这里有几点需要注意:

  1. 虽然克隆了当前的代币分布,但是用户的授权信息并没有克隆。
  2. 新克隆的代币可以有新的名称、符号和精度等等,当然最重要的它有自己的地址,是一个完全新的代币。
  3. 从克隆的那一个区块起,新旧合约分叉了,旧合约仍然可以使用,但是不会对分叉完成后的新合约有任何影响。

三、合约实现

        合约源代码使用的Solidity版本是0.4.24以上,0.5.0以下。笔者使用较新的0.6.0版本(最新的是0.6.1)进行了改写,并且做了简单修改。

        警告
        下面贴出来的代码未经过审计,也未充分测试,只是示例,有可能存在错误或者漏洞,切勿直接使用!!!

pragma solidity ^0.6.0;
/**
* 一个可升级和克隆的ERC20代币合约,在Jordi Baylina写的 MiniMeToken Contract上简单修改得来
* 注意,本合约只是做一下示例,未经过详细测试和审计,请不要直接使用
*/
library SafeMath {
    function add(uint a, uint b) internal pure returns (uint c) {
        c = a + b;
        require(c >= a,"invalid operation");
    }
    function sub(uint a, uint b) internal pure returns (uint c) {
        require(b <= a,"invalid operation");
        c = a - b;
    }
    function mul(uint a, uint b) internal pure returns (uint c) {
        c = a * b;
        require(a == 0 || c / a == b,"invalid operation");
    }
    function div(uint a, uint b) internal pure returns (uint c) {
        require(b > 0,"invalid operation");
        c = a / b;
    }
}


contract Owned {
    address public owner;

    event OwnershipTransferred(address indexed _from, address indexed _to);

    constructor() public {
        owner = msg.sender;
    }

    modifier onlyOwner {
        require(msg.sender == owner,"invalid operation");
        _;
    }

    function changeOwner(address _newOwner) external onlyOwner {
        require(_newOwner != address(0),"invalid address");
        emit OwnershipTransferred(owner,_newOwner);
        owner = _newOwner;
    }
}


interface IApproveAndCallFallBack {
    function receiveApproval(address from, uint tokens, address token, bytes calldata data) external;
}


//主合约
contract UpgradeERC20 is Owned {
    using SafeMath for uint;
    //用来记录某个block对应的token值
    struct CheckPoint {
        uint128 fromBlock;
        uint value;
    }
    //记录所有地址的余额,它是一组随block变化的快照
    mapping (address => CheckPoint[]) public balances;
    //记录所有地址的授权信息
    mapping (address => mapping(address => uint)) public allowed;
    string public name;             //代币名称
    string public symbol;           //代币符号
    uint8 public decimals;          //代币精度
    string public version = "MY_0.0.1";             //自定义版本号
    //源(原)token合约
    UpgradeERC20 public parentToken;
    //以原合约哪一个block时刻为快照升级
    uint128 public parentSnapShotBlock;
    //当前token是否可交易
    bool public transfersEnabled;
    //token总发行量的历史记录
    CheckPoint[] private totalSupplyHistory;
    //合约克隆工厂
    TokenFactory public tokenFactory;

    event Transfer(address indexed from, address indexed to, uint amount);
    event Approval(address indexed tokenOwner, address indexed spender, uint amount);
    event ClaimedTokens(address indexed _token, address indexed _owner, uint _amount);
    event NewCloneToken(address indexed _cloneToken, uint _snapshotBlock);

    //判定地址不能为0
    modifier notZero(address _addr) {
        require(_addr != address(0), "zero_address");
        _;
    }

    //可以交易
    modifier canTransfer() {
        require(transfersEnabled, "Can not transfer");
        _;
    }

    //构造器,如果合约代码有更新,部署同时也是升级的过程
    //必须先部署克隆工厂合约
    constructor(
        address _factoryAddress,
        address _parentAddress,
        uint128 _parentSnapShotBlock,
        string memory _tokenName,
        string memory _symbol,
        uint8 _decimals,
        bool _transfersEnabled
    ) public notZero(_factoryAddress)
    {
        tokenFactory = TokenFactory(_factoryAddress);
        parentToken = UpgradeERC20(_parentAddress);
        name = _tokenName;
        symbol = _symbol;
        decimals = _decimals;
        parentSnapShotBlock = _parentSnapShotBlock;
        transfersEnabled = _transfersEnabled;
    }

////////////////////////////和历史快照相关方法//////////////////
    //获得某个时刻某个用户的balance
    function balanceOfAt(address _owner, uint _blockNumber) public view returns (uint) {
        if(balances[_owner].length == 0 || balances[_owner][0].fromBlock > _blockNumber) {
            if(address(parentToken) != address(0)) {
                return parentToken.balanceOfAt(_owner, min(_blockNumber, parentSnapShotBlock));
            }else {
                return 0;
            }
        }else {
            return getValueAt(balances[_owner], _blockNumber);
        }
    }

    //获得某个时刻发行的代币总量
    function totalSupplyAt(uint _blockNumber) public view returns(uint) {
        if ((totalSupplyHistory.length == 0) || (totalSupplyHistory[0].fromBlock > _blockNumber)) {
            if (address(parentToken) != address(0)) {
                return parentToken.totalSupplyAt(min(_blockNumber, parentSnapShotBlock));
            } else {
                return 0;
            }
        } else {
            return getValueAt(totalSupplyHistory, _blockNumber);
        }
    }

////////////////////////////ERC20方法////////////////////////////////
    //获取当前发行总量
    function totalSupply() public view returns (uint) {
        return totalSupplyAt(block.number);
    }

    //获取用户最新余额
    function balanceOf(address _owner) public view returns (uint) {
        return balanceOfAt(_owner, block.number);
    }

    //授权
    function approve(address _spender, uint _amount) public canTransfer returns (bool) {
        allowed[msg.sender][_spender] = _amount;
        emit Approval(msg.sender, _spender, _amount);
        return true;
    }

    //获取授权额度
    function allowance(address _owner, address _spender) public view returns (uint) {
        return allowed[_owner][_spender];
    }

    //授权并调用对应合约,_spender为实现了IApproveAndCallFallBack的合约
    function approveAndCall(address _spender, uint _amount, bytes calldata _extraData) external returns (bool) {
        require(approve(_spender, _amount), "approve failed");
        IApproveAndCallFallBack(_spender).receiveApproval(msg.sender, _amount, address(this), _extraData);
        return true;
    }

    //直接交易
    function transfer(address _to, uint _amount) external returns(bool) {
        return _transfer(msg.sender,_to,_amount);
    }

    //授权交易
    function transferFrom(address _from, address _to, uint _amount) external returns(bool) {
        allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_amount);
        return _transfer(_from,_to,_amount);
    }

    //具体交易实现
    function _transfer(address _from, address _to, uint _amount) private canTransfer returns(bool) {
        require(parentSnapShotBlock < block.number, "not started");
        require(_to != address(0) || _to != address(this), "invalid address");
        if(_amount == 0){
            return true;
        }
        uint previousBalanceFromNow = balanceOfAt(_from, block.number).sub(_amount);
        _updateValueAtNow(balances[_from], previousBalanceFromNow);
        uint previousBalanceToNow = balanceOfAt(_from, block.number).add(_amount);
        _updateValueAtNow(balances[_to], previousBalanceToNow);
        emit Transfer(_from,_to,_amount);
        return true;
    }
/////////////////////////////////克隆代币//////////////////////////////////////
    //此方法用来创建克隆的代币,当_snapshotBlock为0时从最新的快照开始克隆
    function createCloneToken(
        string calldata _cloneTokenName,
        uint8 _cloneDecimalUnits,
        string calldata _cloneTokenSymbol,
        uint128 _snapshotBlock,
        bool _transfersEnabled
    ) external returns(address)
    {
        uint128 snapshot = _snapshotBlock == 0 ? uint128(block.number - 1) : _snapshotBlock;
        address cloneTokenAddress =tokenFactory.createCloneToken(
            address(this),
            snapshot,
            _cloneTokenName,
            _cloneDecimalUnits,
            _cloneTokenSymbol,
            _transfersEnabled,
            msg.sender
        );
        emit NewCloneToken(cloneTokenAddress, snapshot);
        return cloneTokenAddress;
    }

///////////////////////////////增发、燃烧及交易控制///////////////////////////////
    //增发代币
    function mintTokens(address _owner, uint _amount) external onlyOwner notZero(_owner) returns(bool) {
        uint _totalSupply = totalSupply().add(_amount);
        uint _ownerBalanceNow = balanceOf(_owner).add(_amount);
        _updateValueAtNow(totalSupplyHistory, _totalSupply);
        _updateValueAtNow(balances[_owner], _ownerBalanceNow);
        emit Transfer(address(0),_owner,_amount);
        return true;
    }

    //燃烧代币
    function burnTokens(address _owner, uint _amount) external returns(bool) {
        require(msg.sender == _owner || msg.sender == owner, "permission denied");
        uint _totalSupply = totalSupply().sub(_amount);
        uint _ownerBalanceNow = balanceOf(_owner).sub(_amount);
        _updateValueAtNow(totalSupplyHistory, _totalSupply);
        _updateValueAtNow(balances[_owner], _ownerBalanceNow);
        emit Transfer(_owner,address(0),_amount);
        return true;
    }

    //改变可交易状态
    function enableTransfers(bool _transfersEnabled) external onlyOwner {
        transfersEnabled = _transfersEnabled;
    }

//////////////////////////////其它辅助(内部)方法///////////////////////////////////
   //更新某个CheckPoint[]数组中当前block的值
   function _updateValueAtNow(CheckPoint[] storage checkPoints, uint _value) internal {
       //如果当前block没有记录
       if (checkPoints.length == 0 || checkPoints[checkPoints.length - 1].fromBlock < block.number) {
           CheckPoint memory newCheckPoint = CheckPoint(uint128(block.number),_value);
           checkPoints.push(newCheckPoint);
       } else {
           checkPoints[checkPoints.length - 1].value = _value;
       }
   }

   //该方法用来提取无意中发送到合约的eth或者代币,当代币地址为0时提取ETH
   function claimTokens(address _token) external onlyOwner  {
        if (_token == address(0)) {
            msg.sender.transfer(address(this).balance);
            return;
        }
        UpgradeERC20 token = UpgradeERC20(_token);
        uint balance = token.balanceOf(address(this));
        token.transfer(owner, balance);
        emit ClaimedTokens(_token, owner, balance);
    }

    //获取两个uint的最小值
    function min(uint a, uint b) internal pure returns (uint) {
        return a < b ? a : b;
    }

    //获得指定CheckPoint数组中对应block的值
    function getValueAt(CheckPoint[] storage checkPoints, uint _block) internal view returns (uint) {
       if (checkPoints.length == 0)
           return 0;
       if (_block >= checkPoints[checkPoints.length-1].fromBlock)
           return checkPoints[checkPoints.length-1].value;
       if (_block < checkPoints[0].fromBlock)
           return 0;

       uint _min = 0;
       uint max = checkPoints.length-1;
       while (max > _min) {
           uint mid = (max + _min + 1) / 2;
           if (checkPoints[mid].fromBlock<=_block) {
               _min = mid;
           } else {
               max = mid-1;
           }
       }
       return checkPoints[_min].value;
   }
}


/**
*代币克隆工厂,该合约必须先部署
*/
contract TokenFactory {
    function createCloneToken(
        address _parentToken,
        uint128 _snapshotBlock,
        string calldata _tokenName,
        uint8 _decimalUnits,
        string calldata _tokenSymbol,
        bool _transfersEnabled,
        address _newOwner
    ) external returns (address)
    {
        UpgradeERC20 newToken = new UpgradeERC20(
            address(this),
            _parentToken,
            _snapshotBlock,
            _tokenName,
            _tokenSymbol,
            _decimalUnits,
            _transfersEnabled
        );
        newToken.changeOwner(_newOwner);
        return address(newToken);
    }
}

四、原文链接

原文链接: => https://github.com/aragon/aragon-apps/blob/master/shared/minime/contracts/MiniMeToken.sol

代码未仔细审计,欢迎大家留言指出错误或者提出改进意见。

发布了15 篇原创文章 · 获赞 0 · 访问量 548

猜你喜欢

转载自blog.csdn.net/weixin_39430411/article/details/103972435