ERC20与ERC721详解

摘要

数字加密货币大致可以分为原生币(coin)和代币(token)两大类。前者如BTC、ETH等,拥有自己的区块链。后者如Tether、TRON、ONT等,依附于现有的区块链。市场上流通的基于以太坊的代币大都遵从ERC20协议。最近几个月间还出现了一种被称为ERC721的数字加密资产,例如CryptoKitties(参阅[block #24])。所谓ERC20和ERC721究竟是什么呢?

首先,ERC是Ethereum Request for Comments的缩写,代表以太坊开发者提交的协议提案。它相当于是以太坊版的RFC(https://www.ietf.org/standards/rfcs/)。ERC后面的数字是议案的编号。

打算制定以太坊新标准的开发者可以在https://github.com/ethereum/EIPs/issues先创建一个EIP(Ethereum Improvement Proposal,以太坊改进提案),详细描述协议。经过公开审议之后,获得广泛认同的提案会被标准化,列入https://github.com/ethereum/EIPs。有些改动触及区块链共识,比如增加虚拟机操作符,属于核心层变更。而另一些提案则不涉及修改以太坊本身的代码,只是约定俗成的上层协议,它们通常被归类为ERC标准。

ERC20可能是其中最广为人知的标准了。它诞生于2015年,到2017年9月被正式标准化。协议规定了具有可互换性(fungible)代币的一组基本接口,包括代币符号、发行量、转账、授权等。所谓可互换性(fungibility)指代币之间无差异,同等数量的两笔代币价值相等。交易所里流通的绝大部分代币都是可互换的,一单位的币无论在哪儿都价值一单位。

与之相对的则是非互换性(non-fungible)资产。比如CryptoKitties中的宠物猫就是典型的非互换性资产,因为每只猫各有千秋,而且由于不同辈分的稀缺性不同,市场价格也差异巨大。这种非标准化资产很长时间内都没有标准协议,直到2017年9月才出现ERC721提案,定义了一组常用的接口。ERC721至今仍旧处于草案阶段,但已经被不少dApp采用,甚至出现了专门的交易所(Y Combinator孵化的http://opensea.io)。

以下详细拆解ERC20和ERC721协议,技术性偏强,目标读者是以太坊智能合约开发者。

ERC20

ERC20定义了三个可选函数:

// 代币名称。比如"Sleepism Token"。

function name() view returns (string name);

// 代币符号。按惯例一般用全大写字母,比如“SLPT”。

function symbol() view returns (string symbol);

// 小数位数。不少代币采用与ETH一样的设定,也就是18位。

// 这只影响用户界面中货币量的显示方式。代币本身都统一用uint256表示。

function decimal() view returns (uint8 decimals);

由于这三个函数都是返回常量,在Solidity中也可以用以下简略形式定义。solc编译器会自动生成与以上接口等价的字节码。

string public name = "Sleepism Token";

string public symbol = "SLPT";

uint8 public decimal = 18;

ERC20定义了六个必须声明的函数:

// 代币总量。

function totalSupply() view returns (uint256 totalSupply);

// 查询指定地址下的代币余额。

function balanceOf(address _owner) view returns (uint256 balance);

// 给指定地址转入指定量的代币,转账成功则返回true。

// 如果源地址没有足够量的代币,函数应该抛出异常。

// 即使转零个代币,也应该触发Transfer事件(下文中有解释)。

function transfer(address _to, uint256 _value) returns (bool success);

// 与transfer类似的转账函数。区别在于可以指定一个转出地址。

// 如果当前地址得到了转出地址的授权,则可以代理转账操作。

function transferFrom(address _from, address _to, uint256 _value)

returns (bool success);

// 授权指定地址特定的转账额度。

// 被授权的地址可以多次调用transferFrom函数代替源地址转账,总值不超过_value。

// 实际使用时,每次重设额度前应该先调用approve(_spender, 0),

// 等交易被确认后再调用approve(_spender, newAllowance)。

// 如果直接调用一次approve,被授权地址有机会转出高出指定额度的代币。

function approve(address _spender, uint256 _value) returns (bool success);

// 查询指定授权地址剩余的转账额度。

function allowance(address _owner, address _spender) view returns (uint256 remaining);

ERC20还定义了两个事件:

// 转账事件。必须在成功转账(哪怕是零个代币)时触发。

event Transfer(address indexed _from, address indexed _to, uint256 _value);

// 授权事件。必须在成功授权时触发。

event Approval(address indexed _owner, address indexed _spender, uint256 _value);

官方文档参见https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md

ERC721

ERC721与ERC20有些相似,但由于它管理的是非互换性资产(non-fungible token,简称NFT),所以函数语义并不一样。合约下每份ERC721资产都拥有一个uint256类型的独立编号(以下代码中的_tokenId)。

ERC721事件:

// 转账事件。从_from地址转移_tokenId对应资产的所有权到_to地址时触发。

event Transfer(address indexed _from, address indexed _to, uint256 _tokenId);

// 授权转账事件。把_owner地址控制的_tokenId资产授权给_approved地址时触发。

// 发生转账事件时,对应资产的授权地址应被清空。

event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId);

// 授权管理事件。_owner地址授权或取消授权_operator地址的管理权时触发。

event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved);

ERC721函数:

// 查询_owner地址拥有的NFT总个数。

function balanceOf(address _owner) external view returns (uint256);

// 查询_tokenId资产的所属地址。

function ownerOf(uint256 _tokenId) external view returns (address);

// 将_from地址所拥有的_tokenId资产转移给_to地址。

// 调用方必须是资产主人或是已被授权的地址,否则会抛出异常。

function transferFrom(address _from, address _to, uint256 _tokenId) external payable;

// 与transferFrom类似的资产转移函数。

// 它会额外检查_to地址和_tokenId的有效性,另外如果_to是合约地址,

// 还会触发它的onERC721Received回调函数。

function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable;

// 与上述接口类似的资产转移函数。

// 唯一不同点是可以传入额外的自定义参数。

function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data) external payable;

// 把_tokenId资产授权给_approved地址。

function approve(address _approved, uint256 _tokenId) external payable;

// 查询_tokenId资产对应的授权地址。

function getApproved(uint256 _tokenId) external view returns (address);

// 指定或撤销_operator地址的管理权限。

function setApprovalForAll(address _operator, bool _approved) external;

// 查询_operator地址是否已经获得_owner地址的管理权。

function isApprovedForAll(address _owner, address _operator) external view returns (bool);

ERC721元数据接口(可选项):

// 资产名称。比如"Sleepism Collectible"。

function name() external pure returns (string _name);

// 资产符号。比如"SLPC"。

function symbol() external pure returns (string _symbol);

// 描述_tokenId资产的URI。指向一个符合ERC721元数据描述结构的JSON文件。

function tokenURI(uint256 _tokenId) external view returns (string);

元数据描述结构如下所示:

{

"title": "Sleepism Collectible Metadata",

"type": "object",

"properties": {

"name": {

"type": "string",

"description": "Sleep and his Half-brother Death",

},

"description": {

"type": "string",

"description": "A painting by John William Waterhouse",

},

"image": {

"type": "string",

"description": "https://i.imgur.com/sahhbd.png",

}

}

}

ERC721枚举扩宽接口(可选项):

// 合约管理的总NFT数量。

function totalSupply() external view returns (uint256);

// 查询第_index个资产的编码。

function tokenByIndex(uint256 _index) external view returns (uint256);

// 查询_owner地址拥有的第_index个资产的编码。

function tokenOfOwnerByIndex(address _owner, uint256 _index) external view returns (uint256);

符合ERC721协议的合约还需要符合ERC165规范,实现以下函数:

// 查询合约是否实现了interfaceID对应的接口。

function supportsInterface(bytes4 interfaceID) external view returns (bool);

interfaceID由bytes4(keccak256(函数签名))计算得到。有多个函数时,将全部byte4异或(xor)得到最终结果。详见ERC165标准文档(https://github.com/ethereum/EIPs/blob/master/EIPS/eip-165.md)。

例如以下接受转账回调函数接口的interfaceID就是0xf0b9e5ba:

interface ERC721TokenReceiver {

function onERC721Received(address _from, uint256 _tokenId, bytes data) external returns(bytes4);

}

这是支持ERC721资产的钱包或交易平台需要实现的代码。发放ERC721资产的合约本身不需要处理。

ERC721官方文档参见https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md

相关ERC

ERC223是对ERC20的小改进,增加了tokenFallback函数,更好地处理错误情况。详见https://github.com/ethereum/EIPs/issues/223

ERC621在ERC20的基础上添加了increaseSupply和decreaseSupply函数,控制代币总量的增减。详见https://github.com/ethereum/EIPs/pull/621

更近期提出的ERC827则是增强了函数回调功能。详见https://github.com/ethereum/EIPs/issues/827

以上这些ERC尚未标准化,普及率也不高。目前参考一下即可。

如果你有疑问欢迎加微信咨询:


ERC20与ERC721详解


也可以关注我的公众号想我提问:

ERC20与ERC721详解