在以太坊中,流通的数字货币除了原生的以太币外,就是各种各样五花八门的ERC20代币了。通常,ERC20代币合约部署后是不需要升级的,但是有时又存在升级代币合约的需求。笔者在学习Aragon的过程中发现很早就有可升级ERC20代币合约的设计方法,这里把它分享出来,以供有需求的开发者一起学习。
可升级ERC20代币设计借用了区块链的设计思想。区块链是一个区块接一个区块的按时间顺序链接,每个区块保存了当时那个时刻的世界状态(借用超级账本的术语)并且可以分叉。可升级ERC20代币首先也是将每个用户和代币的发行总量按照区块进行保存,然后把整个合约看作是一条链。部署升级合约或者克隆合约时就是产生一个新的分叉,用来保存新的数据,旧的历史数据仍然保存在旧合约中不用迁移。新合约有一个链接指向旧合约,用来查询历史数据或者进行计算。这其中旧合约仍是可用的,它和新合约是独立的,相当于并行的两条链。笔者认为这和以太坊的分叉很相似,当然不会存在大家选择一个共同的较长的链这么一个说法,你使用哪个合约(新或旧),就在哪个分叉上。
可升级ERC20代币主要功能是让你以现在的代币分布重新克隆一个新的代币出来。你既可以升级合约代码后重新部署,也可以代码不变直接克隆一个新币。可以指定克隆(分叉)的时刻(区块高度),甚至可以指定为将来的某个时刻。这里有几点需要注意:
合约源代码使用的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
代码未仔细审计,欢迎大家留言指出错误或者提出改进意见。