智能合约之代币(token)合约解读

token

Posted by 土猪 on August 1, 2020

上次谈了谈众筹合约,与众筹合约密不可分的还有一个合约,就是代币合约(token),这东西以前搞过,但是没有做系统性学习和总结,我觉得能够把‘openzeppelin-solidity’这个模块里的ERC20 token这块给搞清楚,就能够知足了。

image.png

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位小数点。

image.png

真正的对于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);
    }

image.png

代币的销毁并不是像以太币销毁一样往零地址转发币,而是账面上记录减少而已,这个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");
        _;
    }

image.png

你不停地铸币出来,什么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);
    }

image.png

在一个去中心化支付宝系统中,买家希望过一段时间才把钱转移给卖家,那么就可以来参考下这个合约”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

通过各种代币合约的继承,重载等,就可以塑造自己的代币了,我想这是现实中各种物品数字化的一个途径吧。

相关文章阅读:


支付宝打赏

您的打赏是对我最大的鼓励!