函数选择器 selector
本章讲解在 Solidity
中,什么是函数选择器、函数签名,以及函数选择器的计算方法。
函数选择器(Function Selector)简称 selector
,是一个 4 字节的哈希值,用于标识智能合约中的一个特定函数。
比如,0x7afbe4f1 就是下面 Add
函数的选择器:
function Add(uint256 a, uint256 b) public pure returns(uint256);
1. selector的计算方法
函数选择器是如何计算的呢?在回答这个问题之前,我们首先要弄懂一个概念:函数签名。
函数签名(Function Signature)是使用文本字符来标识函数的一种方式。函数签名中包括函数名称和参数类型。
比如,上面 Add
函数的函数签名就是字符串 “Add(uint256,uint256)”。
函数签名需要遵循的规则:
- 不包含返回值部分。
- 只包含参数类型,不包含参数名称。
- 参数间用逗号分隔,不能有空格。
- 不包含可见性和可变性等属性。
另外,参数类型必须使用规范写法,不能使用别名。比如,uint
是 unit256
的别名,虽然两者实际是同一种类型,但在函数签名中,不能使用 uint
,只能使用 unit256
。
我们弄懂了函数签名,也就容易理解函数选择器的计算方法了。
函数选择器是使用 keccak256
计算出函数签名的哈希值,然后取其前 4 个字节。
比如,计算 Add
的函数选择器的 Solidity
代码:
bytes4(keccak256(bytes("Add(uint256,uint256)")))
其中,最内层的 bytes("Add(uint256,uint256)") 将函数签名转为字节数组 bytes
,再用 keccak256
对它求哈希值 ,最后取哈希值的前 4 个字节 bytes4
。
这是计算函数选择器的合约:
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract CalcSelector { function selector() external pure returns (bytes4) { return bytes4(keccak256(bytes("Add(uint256,uint256)"))); } }
将合约部署到 Remix
上,调用 selector
函数,就可以得到 Add
函数的选择器为 0x7afbe4f1。
2. selector的作用和原理
在以太坊上部署的二进制代码的合约中,使用函数选择器来定位和执行特定的函数,并传递相应的参数。
比如,一个合约中包含加法 Add
和减法 Sub
两个函数:
/ SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract SelectorDemo { function Add(uint256 a, uint256 b) public pure returns(uint256){ return a+b; } function Sub(uint256 a, uint256 b) public pure returns(uint256){ return a+b; } }
这个智能编译后,它的二进制代码的结构如下:
............. ............. 0x7afbe4f1: // Add函数入口 ............. ............. 0x1ec9c2c0: // Sub函数入口 ............. .............
当外部调用合约的 Add
函数时,执行代码会提取请求数据中的函数选择器,然后开始匹配函数入口,当找到 0x7afbe4f1 时,发现两者相等,于是执行里面的代码。
3. msg.data
当外部合约或者客户端调用一个智能合约的函数,就需要向区块链提交一段二进制代码,在这段代码中就包含了被调用函数的选择器。
当被调用的合约函数收到请求数据,就会将请求数据,原封不动地封装在全局变量 msg.data
中。
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract MsgData { event Log(bytes data); function Add(uint256 a, uint256 b) public returns(uint256) { // 触发 Log 事件,输出 msg.data emit Log(msg.data); return a+b; } }
将合约部署到 Remix
上,输入参数 1 和 2,然后调用 Add
函数:
调用 Add
函数后,会输出事件 Log
,它里面的 data
可以分为 3 个部分:
0x7afbe4f1 // Add 函数选择器 0000000000...000000001 // 参数 a 的值 0000000000...000000002 // 参数 b 的值
我们可以看到,请求数据的前 4 个字节,正是 Add
函数的选择器。