您当前的位置: 首页 >  Python

MateZero

暂无认证

  • 1浏览

    0关注

    92博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

使用python和solidity分别计算以太坊智能合约函数选择器和支持接口常量值

MateZero 发布时间:2020-02-29 11:13:10 ,浏览量:1

一、什么是函数选择器与支持接口常量值

       我们在浏览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计算

       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             
关注
打赏
1648304347
查看更多评论
0.0384s