Use python and solidity to calculate the Ethereum smart contract function selector and support interface constant value respectively

1. What is the function selector and support interface constant value

       When we browse the ERC721 sample (template) contract written by OpenZeppelin, we will see this piece of code:

/*
 *     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);
}

       Are there any readers as curious as I am about what it means? It represents a standard contract should support intelligent ERC721 (achieve) interface, namely: 'balanceOf(address)', 'ownerOf(uint256)')... 'safeTransferFrom(address,address,uint256,bytes)').

       This is balanceOf(address)called the signature of the function. Readers who have studied function overloading know that function overloading is distinguished by function names and parameter lists, and does not include return parameters. Therefore, the signature here is only a list of function names and parameter types. The parameters are separated by commas and do not contain extra spaces.

bytes4(keccak256('balanceOf(address)'))The value calculated        using the method is called the function selector, which is the first four bytes of the smart contract call data. The smart contract uses this selector to determine which function is called. The calculation method of the selector is listed in the comments.

       The standard ERC721 smart contract must support the above 9 interfaces, but it cannot be verified one by one (too inefficient). Therefore, the 9 function selectors are XORed (note that the order of interface XORs does not affect the result), and a bytes4constant value of a type is obtained to represent the series of interfaces. Finally, register this constant value in the constructor and it is OK.

Warm reminder:
       In the above comment, there is a missing number 5in0xa22cb465 ^ 0xe985e9c ^ 0x23b872dd ^ 0x42842e0e ^ 0xb88d4fde ==this line0xe985e9c. This should belong to a typo by OpenZeppelin. It is not modified here in order to keep it as it is. I hope that readers will notice this when verifying the comments, and do not have a 5, so that the correct result will not be obtained.

2. Under what circumstances do we need to manually calculate the function selector and support interface constant values

       Under normal circumstances, we do not need to calculate function selectors or support interface values. But when you want to add a (series) interface and want the contract to indicate that it supports or does not support this (series) interface, you need to manually calculate your function selector and support interface constant values. Note: When there is only one interface (function), the support interface constant value is the function selector. Let us give an example of practical application.

       Alpha Wallet ( https://alphawallet.com/ ) When displaying ERC721 tokens, in order to obtain all the token IDs of users at one time (standard ERC721 does not provide this interface, see the note in section 1), we have added a getBalancesmethod by ourselves :

function getBalances(address owner) public view returns(uint256[] memory) {
    
    
    return balances[owner];
}

       Therefore, it calculates the selector of the function as the support constant value (only one function is added, so the support constant value is the function selector, and multiple functions are the selector XOR).

/* 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);
}

       Wherein _INTERFACE_ID_HONOR_BALANCESthe constant is a custom name, but the value is based on bytes4(keccak256('getBalances(address)'))calculated. Below we use Solidity and Python for calculation and implementation, the method is very simple.

Three, use Solidity calculation

       Solidity is different from other programming languages. It is not executed after interpretation or executed after compilation. It can only be written as a smart contract and deployed on Ethereum for everyone to call. So we need to write a simple smart contract, the code is as follows:

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 < selectors.length; i++) {
    
    
            result = result ^ selectors[i];
        }
        return result;
    }
}

       The contract is very simple. There are only two functions. The first function uses the method in the comments to calculate the function selector. Note that for convenience, the type of the input parameter is a string, which must be converted to a bytes4type before calculation . The second function input parameter is all function selectors. The bytes4 result = 0x00000000;code here is because 0 XOR is the value itself, and 4 bytes are just 8 hexadecimals. Note that the values ​​returned by these two functions are all bytes4types.

       The contract deployment process will not be introduced in this article, skip it first, let's briefly introduce the contract call.

Fourth, use Python to connect smart contracts on Ethereum

       To use Python to connect to Ethereum smart contracts, you need to use web3.pythis library. Install it first:

$ pip install web3

       We infuraconnect to Ethereum through nodes, so you need one more INFURA_PROJECT_ID. Then set it into the environment variable:

$ export WEB3_INFURA_PROJECT_ID=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

       But INFURA_PROJECT_IDit doesn't matter if you don't have this . After testing, it is found that you can connect to the smart contract without setting the environment variable temporarily. If you are insured or find that the following code is not running correctly, you still need to go to the infura website to create a new project and get your own project_id.

       We first use python to build an object to connect to the Ethereum smart contract, and create a new one in the working directory contract.py. The code is as follows:

# 连接一个Kovan测试网上的用来计算支持接口常量值的智能合约
from web3.auto.infura.kovan import w3


# 合约ABI。注意,这里所有的true和false要替换成python的True和False
contract_abi = [
  {
    
    
    "constant": True,
    "inputs": [
      {
    
    
        "internalType": "string",
        "name": "signature",
        "type": "string"
      }
    ],
    "name": "getSelector",
    "outputs": [
      {
    
    
        "internalType": "bytes4",
        "name": "",
        "type": "bytes4"
      }
    ],
    "payable": False,
    "stateMutability": "pure",
    "type": "function"
  },
  {
    
    
    "constant": True,
    "inputs": [
      {
    
    
        "internalType": "bytes4[]",
        "name": "selectors",
        "type": "bytes4[]"
      }
    ],
    "name": "getSupportedInterface",
    "outputs": [
      {
    
    
        "internalType": "bytes4",
        "name": "",
        "type": "bytes4"
      }
    ],
    "payable": False,
    "stateMutability": "pure",
    "type": "function"
  }
]
# Kovan测试网上合约地址
contract_address = '0x07d74Cf0Ce4A1b10Ece066725DB1731515d62b76'
# 构造合约对象
CalSelector = w3.eth.contract(address=contract_address,abi=contract_abi)

       Constructing a contract object requires the ABI and address of the contract. Based on the principle of free, we CalSelectordeploy the smart contract in Section 3 of this article on the testnet kovan.

       It should be noted that because the contract is simple and the ABI is small, it is written directly in the code, and usually the contract ABI is located in a separate file. Since the ABI after contract compiled (compiled using truffle) in trueand falseis the first letter lowercase, and python has carried out a distinctive first letter capitalized, it is necessary to replace the manual Trueand False. If you save the contract ABI in a separate file, after reading the file, you first need to use the jsonmodule loadsmethod to convert it into a dictionary, and then get the ['abi']attributes, you do not need to manually replace the truesum false.

Five, use python to calculate and compare the two

       Create a new in the same working directory test.py, the code is as follows:

from web3.auto.infura.kovan import w3
from contract import CalSelector


# bytes4(keccak256('isApprovedForAll(address,address)')) == 0xe985e9c5
func = 'isApprovedForAll(address,address)'

# 0x70a08231 ^ 0x6352211e ^ 0x095ea7b3 ^ 0x081812fc ^
#     0xa22cb465 ^ 0xe985e9c ^ 0x23b872dd ^ 0x42842e0e ^ 0xb88d4fde == 0x80ac58cd
# 注意,接口出现的顺序并不影响计算结果,这个也是显然亦见的
selectors = [
    0x70a08231,
    0x6352211e,
    0x095ea7b3,
    0x081812fc,
    0xa22cb465,
    0xe985e9c5,
    0x23b872dd,
    0x42842e0e,
    0xb88d4fde
]


def calSelectorByPython(_func):
    result = w3.keccak(text=_func)
    selector = (w3.toHex(result))[:10]
    return selector


def calSelectorBySolidity(_func):
    selector = CalSelector.functions.getSelector(_func).call()
    return w3.toHex(selector)


def calSupportedInterfaceByPython(_selectors):
    result = int('0x00000000',16)
    for selector in _selectors:
        result = result ^ selector
    return w3.toHex(result)


def calSupportedInterfaceBySolidity(_selectors):
    _param = [ w3.toBytes(selector) for selector in _selectors]
    supported_interface = CalSelector.functions.getSupportedInterface(_param).call()
    return w3.toHex(supported_interface)


if __name__ == "__main__":
    print(calSelectorByPython(func))
    print(calSelectorBySolidity(func))
    print('-------------------------')
    print(calSupportedInterfaceByPython(selectors))
    print(calSupportedInterfaceBySolidity(selectors))

       The code is also very simple. Four functions are defined, namely, using python and using the contract to calculate the function selector, using python and using the contract to calculate the constant value of the supported interface.

       The code directly uses the comments in the first section of this article for verification, pay attention to the clerical error I mentioned. Run directly test.py:

➜  python python3 test.py
0xe985e9c5
0xe985e9c5
-------------------------
0x80ac58cd
0x80ac58cd

       It can be seen from the output that the result calculated using python is consistent with the result calculated using the contract, and is the same as the number in the comment. One point that needs to be explained is that the function selector and interface constant value are all bytes4types, which are types in python bytes. For the convenience of display, we convert them into hexadecimal string form.

6. Calculate directly on etherscan

       As you can see from the previous content, connecting to the Ethereum smart contract requires a series of operations, which is still a little troublesome. What if we don't want to use python or other programming languages ​​(such as JavaScript) to connect to Ethereum smart contracts for calculations? Don't worry, we also have a way to call the contract directly on the web page.

       We know that if a smart contract is open source, then the method of the contract can be called directly on etherscan. Using this we can directly calculate the function selector and support interface constant value on etherscan.

       Visit the following website, the last address in the URL is the address of the contract in the third section of this article on the kovan testnet:

https://kovan.etherscan.io/address/0x07d74cf0ce4a1b10ece066725db1731515d62b76#readContract

Reminder:
Since etherscan cannot be accessed directly, you need to surf the Internet scientifically.

       After opening the webpage, click Contract below (a green tick next to it means it has been open sourced). If you click Code, the contract source code and ABI will be displayed. We click Read Contract, and the two functions defined in this contract will appear in the list below.

       Note: Contracts that have not passed the open source certification cannot directly call the corresponding functions.
Insert picture description here
       Enter in the calculation function selector parameter getBalances(address)(this is the custom interface added by Alpha Wallet to ERC721), click Query, and the corresponding result will be obtained after the query is over. Similarly, we getSupportedInterfaceinput in the method [0x06fdde03,0x95d89b41,0xc87b56dd](this is all the function selectors of the ERC721 metadata extension interface), and the corresponding result will be obtained.

       You can also bookmark this website so that you can directly access it for related calculations when necessary.

       Okay, the above is the whole content of this article. Please leave a message and correct any deficiencies or errors.

Guess you like

Origin blog.csdn.net/weixin_39430411/article/details/104566176
Recommended