上次谈了谈众筹合约,与众筹合约密不可分的还有一个合约,就是代币合约(token),这东西以前搞过,但是没有做系统性学习和总结,我觉得能够把‘openzeppelin-solidity’这个模块里的ERC20 token这块给搞清楚,就能够知足了。
Token文件夹下有好几个代币标准,ERC20,ERC721 和 ERC777,ERC20是最初的,也是目前最常用的代币合约标准,所以我就看看它就得了,别的标准也是衍生出来的。
让我们先从一个接口合约IERC20看起来吧,这个合约不做任何具体事情,就定义了ERC20标准要处理的几个函数:
查询总共有多少代币
function totalSupply() external view returns (uint256);
查询某个账户里代币数量
function balanceOf(address account) external view returns (uint256);
把amount数量的代币转移给recipient地址
function transfer(address recipient, uint256 amount) external returns (bool);
查询允许spender花owner多少钱
function allowance(address owner, address spender) external view returns (uint
256);
允许spender花费amount的代币
function approve(address spender, uint256 amount) external returns (bool);
把amount数量的代币从sender转移到recipient账户里
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
在发生transfer行为时候发一个事件给链上节点
event Transfer(address indexed from, address indexed to, uint256 value);
发生审批行为时候发事件给链上节点
event Approval(address indexed owner, address indexed spender, uint256 value);
对于ERC20标准,除了在接口合约IERC20.sol中定义之外,还在ERC20Detailed 合约里定义了如下变量:
constructor (string memory name, string memory symbol, uint8 decimals) public {
_name = name;
_symbol = symbol;
_decimals = decimals;
}
举个例子吧,以太坊名字是“ethereum”,symbol就是“ETH”,decimals就是18,decimal我一开始不怎么理解。因为以太坊上EVM不能处理小数运算,在以太坊智能合约上代币单位是不可分割的,有了decimal,在以太坊上就能正确地显示小数点后面数字,假设黄金用kg来显示,它的小数点是3位,那么对于1.228kg,我们告诉以太坊系统1228和3位小数点。
真正的对于ERC20标准的实现,是在ERC20.sol合约里完成的,其实这些实现都挺简单的,比如这个,都是账面上写来写去的,只不过这些账面记录在所有区块链节点上都有复制,保证了不可篡改性。
function _transfer(address sender, address recipient, uint256 amount) internal {
require(sender != address(0), "ERC20: transfer from the zero address");
require(recipient != address(0), "ERC20: transfer to the zero address");
_balances[sender] = _balances[sender].sub(amount);
_balances[recipient] = _balances[recipient].add(amount);
emit Transfer(sender, recipient, amount);
}
我以前还没有注意到在ERC20.sol里面就实现了铸币和销毁币的功能。这个铸币功能可以针对某个以太账户,造出代币来给它,其实也就是账面功夫,我们不能从零地址销毁币和往上面造币,要知道,在以太坊里面,零地址专门用来销毁以太币的,如同黑洞,只进不出:
function _mint(address account, uint256 amount) internal {
require(account != address(0), "ERC20: mint to the zero address");
_totalSupply = _totalSupply.add(amount);
_balances[account] = _balances[account].add(amount);
emit Transfer(address(0), account, amount);
}
代币的销毁并不是像以太币销毁一样往零地址转发币,而是账面上记录减少而已,这个burn和burnFrom,就增加了个发送者主动要求减少在account账户上资金使用数量而已,仔细一想,还真是需要这样处理:
function _burn(address account, uint256 value) internal {
require(account != address(0), "ERC20: burn from the zero address");
_totalSupply = _totalSupply.sub(value);
_balances[account] = _balances[account].sub(value);
emit Transfer(account, address(0), value);
}
function _burnFrom(address account, uint256 amount) internal {
_burn(account, amount);
_approve(account, msg.sender, _allowances[account][msg.sender].sub(amount));
}
接下来这两个函数我觉得有些多余了,自己算好需要多少allowance,直接调用一个approve函数不就得了,何必多此一举?
function increaseAllowance(address spender, uint256 addedValue) public returns (bool) {
_approve(msg.sender, spender, _allowances[msg.sender][spender].add(addedValue));
return true;
}
function decreaseAllowance(address spender, uint256 subtractedValue) public returns (bool) {
_approve(msg.sender, spender, _allowances[msg.sender][spender].sub(subtractedValue));
return true;
}
接下来一个个合约看过来,ERC20Burnable.sol,特简单,就调用了下burn函数销毁币而已:
function burn(uint256 amount) public {
_burn(msg.sender, amount);
}
function burnFrom(address account, uint256 amount) public {
_burnFrom(account, amount);
}
既然能销毁,就能无中生有,看看这个ERC20Mintable.sol,就是造币,注意啊,造币需要特权才能做,它继承了‘MinterRole’这个合约,我就纳闷了,为什么销毁币就不需要特权呢?
contract ERC20Mintable is ERC20, MinterRole {
/**
* @dev See `ERC20._mint`.
*
* Requirements:
*
* - the caller must have the `MinterRole`.
*/
function mint(address account, uint256 amount) public onlyMinter returns (bool) {
_mint(account, amount);
return true;
}
}
在’MinterRole.sol’合约里有这么个东西,所以骨子里,还是合约创造者才有这个特权让谁有特权来铸币,所以智能合约的这个毛病不改掉,我觉得很难真正去中心化啊。
constructor () internal {
_addMinter(msg.sender);
}
modifier onlyMinter() {
require(isMinter(msg.sender), "MinterRole: caller does not have the Minter role");
_;
}
你不停地铸币出来,什么4万亿,8万亿的,印钱总该有个底线吧?我们来看看这个合约,可谓底线,‘ERC20Capped.sol’,就是限制铸币的总量:
function _mint(address account, uint256 value) internal {
require(totalSupply().add(value) <= _cap, "ERC20Capped: cap exceeded");
super._mint(account, value);
}
再看看这个合约,ERC20Pausable.sol,跟以前说过的可中止的众筹合约一样,这个代币合约允许合约所有人中止代币在账户间转移,它也是继承了lifecycle/Pausable.sol合约,引入了whenNotPaused这个modifier,然后仅仅在函数中调用了父合约的相关函数而已:
function transfer(address to, uint256 value) public whenNotPaused returns (bool) {
return super.transfer(to, value);
}
在一个去中心化支付宝系统中,买家希望过一段时间才把钱转移给卖家,那么就可以来参考下这个合约”TokenTimelock.sol”,它不是一个代币合约,但是却被分类到了ERC20这个文件夹下,我不是很理解。
function release() public {
// solhint-disable-next-line not-rely-on-time
require(block.timestamp >= _releaseTime, "TokenTimelock: current time is before release time");
uint256 amount = _token.balanceOf(address(this));
require(amount > 0, "TokenTimelock: no tokens to release");
_token.safeTransfer(_beneficiary, amount);
}
最后来谈谈以太坊区块链上的时间戳(timestamp),它用的是unix时间戳,最小单位是秒。
Unix时间戳(Unix timestamp),或称Unix时间(Unix time)、POSIX时间(POSIX time),是一种时间表示方式,定义为从格林威治时间1970年01月01日00时00分00秒起至现在的总秒数。
以太坊中有如下几种时间单位:
1 == 1 seconds
1 minutes == 60 seconds
1 hours == 60 minutes
1 days == 24 hours
1 weeks == 7 days
1 years == 365 days
我们需要把unix时间戳转换成这种可读的时间,如果是python语言,用下面的脚本就可以:
import datetime
print(
datetime.datetime.fromtimestamp(
int("1284105682")
).strftime('%Y-%m-%d %H:%M:%S')
)
output:
2010-09-10 13:31:22
我们也需要在适当时候,把可读时间转化成unix时间戳,还是用python:
from datetime import timezone
dt = datetime(2015, 10, 19)
timestamp = dt.replace(tzinfo=timezone.utc).timestamp()
print(timestamp)
output:
1445212800.0
通过各种代币合约的继承,重载等,就可以塑造自己的代币了,我想这是现实中各种物品数字化的一个途径吧。
相关文章阅读: