0%

solidity | 选择器

selector 当我们调用智能合约时,本质上是向目标合约发送了一段 calldata,在 remix 中发送一次交易后,可以在详细信息中看见 input 即为此次交易的calldata

发送的 calldata 中前 4 个字节是 selector(函数选择器)。

msg.data

msg.datasolidity 中的一个全局变量,值为完整的 calldata(调用函数时传入的数据)。

在下面的代码中,我们可以通过 Log 事件来输出调用 mint 函数的 calldata

1
2
3
4
5
6
// event 返回msg.data
event Log(bytes data);

function mint(address to) external{
emit Log(msg.data);
}

当参数为 0x2c44b726ADF1963cA47Af88B284C06f30380fC78 时,输出的 calldata

0x6a6278420000000000000000000000002c44b726adf1963ca47af88b284c06f30380fc78

这段很乱的字节码可以分成两部分:

4 个字节为函数选择器 selector

0x6a627842

后面32个字节为输入的参数:

0x0000000000000000000000002c44b726adf1963ca47af88b284c06f30380fc78

其实 calldata 就是告诉智能合约,我要调用哪个函数,以及参数是什么。

method id、selector和函数签名

method id定义为函数签名的 Keccak 哈希后的前 4 个字节,当selectormethod id相匹配时,即表示调用该函数,那么函数签名是什么?

函数签名,为”函数名(逗号分隔的参数类型)”。

举个例子,上面代码中 mint 的函数签名为 mint(address) 。在同一个智能合约中,不同的函数有不同的函数签名,因此我们可以通过函数签名来确定要调用哪个函数。

注意,在函数签名中,uintint 要写为 uint256int256

我们写一个函数,来验证 mint 函数的 method id是否为 0x6a627842。大家可以运行下面的函数,看看结果。

1
2
3
function mintSelector() external pure returns(bytes4 mSelector){
return bytes4(keccak256("mint(address)"));
}

结果正是 0x6a627842

使用selector

我们可以利用 selector 来调用目标函数。例如我想调用 mint 函数,我只需要利用 abi.encodeWithSelectormint 函数的 method id 作为 selector 和参数打包编码,传给call函数:

1
2
3
4
function callWithSignature() external returns(bool, bytes memory){
(bool success, bytes memory data) = address(this).call(abi.encodeWithSelector(0x6a627842, 0x2c44b726ADF1963cA47Af88B284C06f30380fC78));
return(success, data);
}

直接获取

1
2
3
4
5
6
7
8
9
10
11
12
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract erc20 is ERC20 {
constructor(string memory name_, string memory symbol_) ERC20(name_, symbol_){
}

function getSelector()public pure returns(bytes4){
return ERC20.transfer.selector;
}
}

其它方法请参考

请我喝杯咖啡吧~