19.1 selector
当我们调用智能合约时,本质上是向目标合约发送了一段calldata,在remix中发送一次交易后,可以在详细信息中看见input即为此次交易的calldata。
发送的calldata中前4个字节是selector(函数选择器)。
示例代码,将msg.data作为事件发出:
contract Selector {
address public addr;
event Log(bytes data);
function selector(address _addr) external {
addr = _addr;
emit Log(msg.data);
}
}
remix日志中的input:
Log信息(msg.data):
[
{
"from": "0xc5c97AAd92a962396229cbC8392e62585B04DfB3",
"topic": "0xafabcf2dd47e06a477a89e49c03f8ebe8e0a7e94f775b25bbb24227c9d0110b2",
"event": "Log",
"args": {
"0": "0x91fa5ace0000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc4",
"data": "0x91fa5ace0000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc4"
}
}
]
可见input信息和全局变量msg.data相同,都是此次调用函数的calldata:
0x91fa5ace0000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc4
前四个字节为函数选择器:
0x91fa5ace
后面32字节为传入的参数(20字节的地址补零):
0000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc4
calldata作用是:告诉智能合约,要调用哪个函数,以及参数是什么。
19.2 函数签名、selector和method id
-
函数签名:"函数名(逗号分隔的参数类型)",例如例子中
selector()
的函数签名是:"selector(address)"
- selector:函数选择器为calldata的前四个字节。
-
method id:函数签名哈希值的前四个字节,例如:
bytes4(keccak256("selector(address)"))
实现一个函数获取上述例子中selector()
函数的method id:
function getSelector() external pure returns (bytes4){
return bytes4(keccak256("selector(address)"));
}
调用函数的返回值和函数选择器相同,为0x91fa5ace。Solidity中通过函数选择器(selector)匹配method id的方式确定要调用的函数。
19.3 selector的使用
我们可以利用selector来调用目标函数。例如我想调用selector()
函数,我只需要利用abi.encodeWithSelector将selector()
函数的method id作为selector和参数打包编码,传给call函数:
function useSelector(address _addr) external returns (bool) {
(bool success, ) = address(this).call(abi.encodeWithSelector(0x91fa5ace, _addr));
return success;
}
调用selector()
函数成功,输出的Log:
[
{
"from": "0xf02A102153DDf132032B7De5D19F43aA049052Dd",
"topic": "0xafabcf2dd47e06a477a89e49c03f8ebe8e0a7e94f775b25bbb24227c9d0110b2",
"event": "Log",
"args": {
"0": "0x91fa5ace0000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc4",
"data": "0x91fa5ace0000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc4"
}
}
]