我们在浏览OpenZeppelin编写的ERC721示例(模板)合约时,会看到这么一段代码:
/*
* bytes4(keccak256('balanceOf(address)')) == 0x70a08231
* bytes4(keccak256('ownerOf(uint256)')) == 0x6352211e
* bytes4(keccak256('approve(address,uint256)')) == 0x095ea7b3
* bytes4(keccak256('getApproved(uint256)')) == 0x081812fc
* bytes4(keccak256('setApprovalForAll(address,bool)')) == 0xa22cb465
* bytes4(keccak256('isApprovedForAll(address,address)')) == 0xe985e9c5
* bytes4(keccak256('transferFrom(address,address,uint256)')) == 0x23b872dd
* bytes4(keccak256('safeTransferFrom(address,address,uint256)')) == 0x42842e0e
* bytes4(keccak256('safeTransferFrom(address,address,uint256,bytes)')) == 0xb88d4fde
*
* => 0x70a08231 ^ 0x6352211e ^ 0x095ea7b3 ^ 0x081812fc ^
* 0xa22cb465 ^ 0xe985e9c ^ 0x23b872dd ^ 0x42842e0e ^ 0xb88d4fde == 0x80ac58cd
*/
bytes4 private constant _INTERFACE_ID_ERC721 = 0x80ac58cd;
constructor () public {
// register the supported interfaces to conform to ERC721 via ERC165
_registerInterface(_INTERFACE_ID_ERC721);
}
有没有读者和我一样好奇它的含义是什么呢?它代表一个标准的ERC721智能合约应该支持(实现)的接口,分别为:'balanceOf(address)'
、'ownerOf(uint256)')
…'safeTransferFrom(address,address,uint256,bytes)')
。
这其中balanceOf(address)
叫着函数的signature。学过函数重载的读者都知道,函数重载是根据函数名称和参数列表区分的,并不包括返回参数。所以这里的signature只有函数名称和参数类型列表,参数之间用逗号区分,并且不包含多余空格。
使用bytes4(keccak256('balanceOf(address)'))
方法计算出来的值叫着函数选择器,它是智能合约调用数据的最开头四个字节。智能合约根据这个选择器来确定调用的是哪一个函数,选择器的计算方法在注释中已经列出。
标准的ERC721智能合约必须支持以上9个接口,但是不能逐个验证(太低效了)。所以将9个函数选择器相异或(注意接口异或的顺序并不影响结果),得到一个bytes4
类型的常量值来代表该系列接口,最后在构造器里注册这个常量值就OK了。
温馨提示: 在上面的注释中,0xa22cb465 ^ 0xe985e9c ^ 0x23b872dd ^ 0x42842e0e ^ 0xb88d4fde ==
这一行中0xe985e9c
少了一个数字5,这应该属于OpenZeppelin的一个笔误,这里为了保持原样就未修改它。希望读者验证注释时能够注意到这一点,不要也少一个5,这样就得不到正确的结果。
通常情况下,我们不需要计算函数选择器或者支持接口值。但是当你想增加一个(系列)接口而又想合约能够表明支持或者不支持这个(系列)接口时,你就需要手动计算你的函数选择器和支持接口常量值。注意:当只有一个接口(函数)时,支持接口常量值就是该函数选择器。我们举一个实际应用的例子。
Alpha Wallet (https://alphawallet.com/) 在显示ERC721代币时,为了一次性获取用户所有的代币ID(标准ERC721不提供这个接口,见第一节的注释),自己增加了一个getBalances
方法:
function getBalances(address owner) public view returns(uint256[] memory) {
return balances[owner];
}
因此,它计算了该函数的选择器作为支持常量值(只增加了一个函数,所以支持常量值就是该函数选择器,多个函数才是选择器相异或)。
/* bytes4(keccak256('getBalances(address)')) == 0xc84aae17 */
bytes4 private constant _INTERFACE_ID_HONOR_BALANCES = 0xc84aae17;
constructor (string memory name, string memory symbol) ERC721Metadata(name,symbol) public {
_registerInterface(_INTERFACE_ID_HONOR_BALANCES);
}
其中_INTERFACE_ID_HONOR_BALANCES
这个常量名称是自定义的,但是值是根据bytes4(keccak256('getBalances(address)'))
计算出来的。下面我们分别使用Solidity和Python进行计算实现,方法很简单。
Solidity不同于其它编程语言,它不是解释后执行或者编译后执行,只能写成智能合约部署在以太坊上供大家调用时执行,所以我们需要编写一个简单的智能合约,代码如下:
pragma solidity ^ 0.5 .0;
contract CalSelector {
/**
* 给定一个函数signature,如 'getSvg(uint256)',计算出它的选择器,也就是调用数据最开始的4个字节
* 该选择器同时也可用于标明合约支持的接口,如alpha钱包对ERC721标准增加的getBalances接口
* bytes4(keccak256('getBalances(address)')) == 0xc84aae17
*/
function getSelector(string memory signature) public pure returns(bytes4) {
return bytes4(keccak256(bytes(signature)));
}
/**
* 用来计算合约支持的一系列接口的常量值,计算方法是将所有支持接口的选择器相异或
* 例如 ERC721元数据扩展接口
* bytes4(keccak256('name()')) == 0x06fdde03
* bytes4(keccak256('symbol()')) == 0x95d89b41
* bytes4(keccak256('tokenURI(uint256)')) == 0xc87b56dd
*
* => 0x06fdde03 ^ 0x95d89b41 ^ 0xc87b56dd == 0x5b5e139f
*/
function getSupportedInterface(bytes4[] memory selectors) public pure returns(bytes4) {
bytes4 result = 0x00000000;
for (uint i = 0; i
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?