OpenZeppelin
token
token根据其 同质化性质(fungibility) 分为两种,
- 同质化token,如Ether,我们在意的是我们的balance的大小
- 非同质化token,如NFTs,我们在意的是我们有什么
每一种token的开发,都是一个链上的合约,那么如果要让合约和合约之间能够相互协作,就需要有统一的标准,下面要介绍的的就是一些标准。
ERC20
使用最广泛的 同质化token 标准,但是因为其设计比较简单会带来一些限制。
contract ERC20 is Context, IERC20, IERC20Metadata {}
- Context <<Abstract»
- _msgSender() => return msg.sender;
- _msgData() => return msg.data;
- IERC20Metadata <<Interface»
- name() external view => 查看token的名字
- symbol() external view => 查看token的代号
- decimals() external view => 查看token单位
- IERC20 <<Interface»
- totalSupply() external view => 查看token总量
- balanceOf() external view => 查看某个人的token持有量
- allowance() external view => 查看A用户能代理B用户消费多少token,其实就是一个委托记录
- approve() external =>发起token委托
- transfer() external => 转正给别人
- transferFrom() external => A用B的钱转给别人
- event
- Transfer(from, to, value)
- Approval(owner, spender, value)
上面是ERC20依赖的类,接下来看看ERC20实现了哪些功能
- ERC20
- state variable
- _totalSupply uint256
- _name string
- _symbol string _ _balances mapping(address=>uint256)
- _allowances mapping(address=>mapping(address=>uint256))
- constructor => 设置name和symbol
- IERC20Metadata实现
- name() => return _name
- symbol() => return symbol
- decimals() => return 18 (跟以太坊的Ether一样,1 ether = 10**18 wei)
- IERC20实现
- totalSupply() external view => return _totalSupply()
- balanceOf() external view => return _balances[account]
- allowance() external view => return _allowances[owner][spender]
- approve() public => 需要注意在ERC20里是public的,而在IERC20里是external的。发起token委托,实际功能调用_approve(owner, spender, amount)
- transfer() external => 实际功能调用_transfer(owner, to, amount)
- transferFrom() external => 实际先后调用了两个内部函数
- _spendAllowance(from, spender(msg.sender), amount)
- _transfer(from, to, amount)
- 自己的internal函数
- _transfer() => 实际交易逻辑发生的地方,包含了条件判断和Transfe事件抛出。调用了两个内部函数
- _beforeTokenTransfer()
- _afterTokenTransfer()
- _mint() => 增加_totalSupply,也就是发币的机制,不对外暴露,但是可以用一个external的函数把它包起来对外提供服务,但是需要注意设计控制规则。
- _burn() => 燃烧token函数,某个账户可以决定将自己的token销毁,_totolSupply会相应减少
- _approve() => 代币委托行为实际发生的地方,其实就是添加或者覆盖_allowances里面的数据,会抛出Approval事件
- _spendAllowance => 其实就是检查以下委托金额够不够,然后从_allowances里面相对应的地方去扣掉
- _beforeTokenTransfer => 在交易逻辑执行前触发,由子类自己去实现
- _afterTokenTransfer => 在交易逻辑执行后触发,由子类自己去实现
- _transfer() => 实际交易逻辑发生的地方,包含了条件判断和Transfe事件抛出。调用了两个内部函数
- 自己的函数
- increaseAllowance() public => 其实就是增加_allowance[owner][spender]
- decreaseAllowance() public => 其实就是减少_allowance[owner][spender]
- state variable
我们以可以看到上面代码里面,针对allowances的操作中,owner在委托amount数量的token给spender的时候,并不会对owner的balance是否充足进行确认,而是在实际被委托人spender消费owner的token的时候,如果金额不够,_transfer里面会失败。 这样设计的原因,我想是因为即使owner把token委托给了spender,他自己依旧是可以花token的。如果要在确保allowance里面的委托金额一定要小于owner自己的balance金额的话,很难设计当owner花钱的时候,要怎么对allowance进行相应的改动。代码复杂度会变得很高,得不偿失。
IERC20主要围绕state variables中的balance和allowances两个板块进行的函数规划。而token的基础信息板块是在IERC20metadata接口中规划的。对于msg中的sender和data信息的访问,则封装到了抽象的context中。
总结下来ERC20中对同质化代币的功能实现在于:
- 对发行的token的基础信息_name,_symbol进行设定。如果要对默认的decimals进行设定则是通过覆写decimals()函数去进行的
- 发行币和销毁币,主要对_totalSupply进行操作
- 查账和转账,都是对_balance的操作
- token委托相关的操作,都是对_allowances的操作
ERC721
非同质化token 的解决方案,通常用于藏品和游戏领域。 ERC721涉及的合约有
- Context
- ERC165
- IERC721
- IERC721Metadata
上面的Context合约在ERC20中讲过,就是封装了msg.sender和msg.data。这里主要将后面几个
- ERC165
- supportInterface() public view
- IERC721Metadata <<Interface»
- name() external view => 返回这个token的名字
- symbol() external view => 返回这个token的代号
- tokenURI(tokenId) external view => 返回该token的URI,也就是所谓的资源定位符
- IERC721 <<Interface»
- event
- Transfer(from, to, tokenId)
- Approval(owner, approved, tokenId)
- ApprovalForAll(owner, operator, approved)
- balanceOf(owner) external view => 返回
owner
的所有tokens
- ownerOf(tokenId) external view => 返回
token
的owner
的地址 - safeTransferFrom(from, to, tokenId, data) =>
from
转账给to
,需要满足以下条件from
不能是address0
to
不能是address0
token
是from
所有的,或者from
所代理的to
如果是合约,必须要实现{IERC721Receiver-onERC721Received}
- safeTransferFrom(from, to, tokenId)
- transferFrom(from, to, tokenId) => 和safe的区别就是,当
to
是一个智能合约时,这个智能合约不用实现{IERC721Receiver-onERC721Received}
- approve(to, tokenId) => 委托token
- setApprovalForAll(operator, _approved) => 设置是否将所有的token都委托出去
- getApproved(tokenId) => 返回这个token的代理人地址
- isApprovedForAll(owner, operator) => 返回是否owner被operator全权代理
- event
然后是ERC721的具体实现
- ERC721
- state variables
- _name
- _symbol
- _owner mapping(uint256 => address) =>
token
到owner
的映射,记录了token的所有权 - _balances mapping(address => uint256)
owner
到token count
的映射,记录了owner拥有的token的数量 - _tokenApprovals mapping(uint256 => address)
token
到approved
的映射,记录了token委托 - _operatorApprovals mapping(address => mapping(address => bool)) => 记录
owner
到operator
的是否设置了全权委托
- constructor => 初始化_name和_symbol
- ERC165
- supportsInterface(interfaceId) public view => 判断传入的interfaceId是否符合IERC721,IERC721Metadata以及ERC165的标准
- IERC721Metadata
- name()
- symbol()
- tokenURI() public view =>
return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, tokenId.toString())) : ""
其中的baseURI
由内部函数_baseURI()
提供
- IERC721
- balanceOf(owner) public view => 访问_balance[owner]
- ownerOf(tokenId) public view => 访问_owners[tokenId]
- approve(to, tokenId) public => 进行单个token的委托授权,实际的执行逻辑在_approve()中
- getApproved(tokenId) public view => 访问_tokenApprovals[tokenId]
- setApprovalForAll(operator, approved) public => 设置全权委托
- isApprovedForAll(owner, operator) public view => 查看全权委托设定
- transferFrom(from, to, tokenId) public => token转让,如果接收方是合约,不用在意其是不是符合标准的合约
- safeTransferFrom(from, to, tokenId, data) public => token转让,如果接收方是合约,需要验证在意其是不是符合标准的合约
- 内部函数
- _transfer(from, to, tokenId) =>
- _beforeTokenTransfer(from, to, tokenId)
- _approve(address(0), tokenId) => 把授权取消
- 修改_balance和_owner
- _afterTokenTransfer(from, to, tokenId)
- _approve(to, tokenId) => 将token的权柄授权给某给人,主要是对_tokenApprovals变量进行修改,也会抛出Approval事件
- _setApprovalForAll(owner, operator, approved) =>
_operatorApprovals[owner][operator] = approved
对记录全权委托的变量进行修改 - _beforeTokenTransfer() => 空函数,ERC20的子类可以自己去实现
- _afterTokenTransfer() => 空函数,ERC20的子类可以自己去实现
- _safeTransfer(from, to, tokenId, data) => 实际就是用的_transfer(),只是多了一个安全检查
_checkOnERC721Received(from, to, tokenId, data)
。 - _checkOnERC721Received(from, to, tokenId, data) => 主要是两个检查,先检查是不是合约地址,在检查合约是不是符合要求的
IERC721Receiver
- _mint(to, tokenId) => 给地址
to
发放token,主要也是对 _balance 和 _owners 进行修改 - _safeMint(to, tokenId, data) => 和 safeTransfer 一样,也是加了一个
_checkOnERC721Received(address(0), to, tokenId, data)
检查 - _burn(tokenId) => 将token销毁掉,也就是修改_balance和去_owners里面删掉对应的记录,也会抛出一个发向address(0)的Transfer事件。
- _exists(tokenId) => 很简单的一个函数,就是去判断token在_owner里是否存在
- _requireMinted(tokenId) => 其实就是调用的_exist(tokenId),只是如果返回是false的话,会抛出异常
- _isApprovedOrOwner(spender, tokenId) => 其实就是判断spender是不是tokenId的主人,或者是不是tokenId的主人委托的人
- _baseURI() => 返回空字符串,可以在子类中去重写这个函数以此来修改
baseURI
的值
- _transfer(from, to, tokenId) =>
- state variables
ERC1155
一个 多token 的标准,允许一个合约包含多个 同质化token 和 非同质化token。并且通过批量操作提高 gas 的效率。 除了提供 batch 的操作来提升 gas 的利用效率以外,本质上和ERC721差不太多,因为其是一个 多同质化代币 合约规范,如果我们把每一种同质化代币的数量都设置为1,那么这些各种类型的数量唯一的 同质化代币 也就构成了一个 非同质化代币 了。
contact ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI {}
和上面的一样来分析合约的组成
- Context
- ERC165
- IERC1155MetadataURI
- uri(id) external view => 返回token的uri
- IERC1155
- event
- TransferBatch(operator, from, to, ids, values)
- ApprovalForAll(account, operator, approved)
- URI(value, id)
- function
- balanceOf(account, id) => 因为是多token合约,所以再查看账户的时候需要指定
id
查看的是哪一种token - balanceOfBatch(accounts, ids) => accounts数值和ids数组的长度必须一致,因为每一个
(account, id)
都是一次balanceOf(account, id)
- setApprovalForAll(operator, approved) => 和ERC721里面的
setApprovalForAll
差不多 - isApprovedForAll(account, operator) => 和
setApprovalForAll
配套的,一个 改 一个 查 - safeTransferFrom(from, to, id, amount, data)
- safeBatchTransferFrom(from, to, ids, amounts, data) => 上面的batch版本,ids和amounts的长度要对应
- balanceOf(account, id) => 因为是多token合约,所以再查看账户的时候需要指定
- event
最后是ERC1155
- ERC1155
- state variables
- _balances mapping(uint256 => mapping(address => uint256))
- _operatorApprovals mapping(address => mapping(address => bool))
- _uri string
- function
- constructor(uri_) => 调用
_setURI
来设置_uri
- ERC165
- supportsInterface(interfaceId) public view
- IERC1155MetadataURI -uri(uint256) public view => 返回_uri
- IERC1155
- balanceOf(account, id) public view => 去_balances中查询
- balanceOfBatch(accounts, ids) public view => 针对每一个
(account, id)
去调用balanceOf
- setApprovalForAll(operator, approved) public => 调用的
_setApprovalForAll(_msgSender(), operator, approved)
- isApprovedForAll(account, operator) public view => 去_operatorApprovals里面查就行了
- safeTransferFrom public => 实际的执行者是内部函数
_safeTransferFrom
,只是在调用这个内部函数前先用require进行了资格验证 - safeBatchTransferFrom public => 实际是
_safeBatchTransferFrom
,同样在调用之前要对_msgSender()
做资格验证 - 内部函数
- _safeTransferFrom(from, to, id, amount, data)
-
_safeBatchTransferFrom(from, to, ids, amounts, data)
-
_setURI(newuri)
- _mint(to, id, amount, data)
-
_mintBatch(to, ids, amounts, data)
- _burn(from, id, amount)
-
_burnBatch(from, ids, amounts)
-
_setApprovalForAll(owner, operator, approved)
- _asSingletonArray(element)
- _beforeTokenTransfer(operator, from, to, ids, amounts, data)
- _afterTokenTransfer(operator, from, to, ids, amounts)
- _doSafeTransferAcceptanceCheck(operator, from, to, id, amount, data)
- _doSafeTransferAcceptanceCheck(operator, from, to, ids, amounts)
- constructor(uri_) => 调用
- state variables