智能合约之代币(token)合约解读
hive-105017·@chenlocus·
0.000 HBD智能合约之代币(token)合约解读
上次谈了谈[众筹合约](https://hive.blog/hive-105017/@chenlocus/crowdsale),与众筹合约密不可分的还有一个合约,就是代币合约(token),这东西以前搞过,但是没有做系统性学习和总结,我觉得能够把‘[openzeppelin-solidity](https://github.com/OpenZeppelin/openzeppelin-contracts)’这个模块里的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"); _; } ``` <img src="https://images.hive.blog/DQmT18oDk27h8HkpoyQ3D8dFTubVKmRvQG7fcpEuk8251tw/image.png" alt="image.png" style="zoom:200%;" /> 你不停地铸币出来,什么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); } ``` <img src="https://images.hive.blog/DQmXyMp1MZuoRHczA6X7uhaYWPXakztrcaf8TceP45GNiac/image.png" alt="image.png" style="zoom:200%;" /> 在一个去中心化支付宝系统中,买家希望过一段时间才把钱转移给卖家,那么就可以来参考下这个合约”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 ``` 通过各种代币合约的继承,重载等,就可以塑造自己的代币了,我想这是现实中各种物品数字化的一个途径吧。
👍 laissez-faire, executive-board, awesome-gadgets, abandi, best-strategy, natur-pur, steem-fund, online-24-7, coin-doubler, angelinafx, malricinferno, zintarmortalis, rakk3187, unit101, littlegurl747, scarletreaper, izhmash, sevensixtwo, bastogne, thirdarmy, voodooranger, suonghuynh, mys, apix, petrvl, whd, fbslo, steemitcuration, steem.idols, belahejna, ew-and-patterns, qam2112, rufruf, maajaanaa, kryptoformator, mmmmkkkk311, mastergerund, mariuszkarowski, ntowl, xiguang, crypto.income, ecoinstant, ecoinstats, ecoinstar, hive.curation, upfundme, teamaustralia, lebin, fun2learn, btscn, bert0, franyeligonzalez, jgb, memeteca, flamingirl, mangou007, glitterfart, winniex, tongchat27, lovequeen, centerlink, ausbitbank, krystle, mikepedro, gohba.handcrafts, scooter77, bearone, onethousandwords, mrsquiggle, webcoop, masterwu, liverpool-fan, thevillan, cryptwo, jordan.white306, bengy, bec-on-the-block, terrylovejoy, positiveninja, evlachsblog, supersoju, bobaphet, melbourneswest, cryptoslicex, lifeofryan, niouton, deividluchi, vegemitekid, jay.ell, alphaccino.art, crowdwitness, quochuy, lordnigel, vcclothing, terrybogan, mumma-monza, sapphic, urs, gamersclassified, chrisdavidphoto, vincy, sweetsssj, cecilian, bilpcoinrecords, kggymlife, tagalong, silvertop, abit, portraits, santigs, softworld, elizabethbit, futurecurrency, nadhora, yoogyart, minigame, xiaoshancun, victory622, dunkman, shawmeow, waytolifecare, haejin, dcommerce, jjal, mutabor78, hellohive, sujaytechnicals, elevator09, rivalhw, cn-reader,