什么是函数选择器Solidity的函数选择器(FunctionSelector)是EVM中用于标识智能合约中特定函数的唯一4字节(8位十六进制)标识符。它本质上是函数签名的Keccak-256哈希值的前4个字节。主要用于在低级调用(如call、delegatecall、sta
Solidity 的函数选择器(Function Selector)是 EVM 中用于标识智能合约中特定函数的唯一 4 字节(8 位十六进制)标识符。它本质上是函数签名的 Keccak-256 哈希值的前 4 个字节。主要用于在低级调用(如call、delegatecall、staticcall)中指定要调用的目标函数,它是 EVM(以太坊虚拟机)在解析交易或消息调用时,用来确定应该执行合约中哪个函数的关键机制。
函数选择器是函数签名的 Keccak-256 (SHA-3) 哈希值的前 4 个字节(最高位的 4 个字节)。主要可以分为三步:
1. 函数签名(Function Signature):
函数签名由函数名和参数类型组成,不包括返回值。
```js
functionName(typeN1, typeN2...)
```
注意:
不包括参数名。
不包括空格。
不包括返回类型。
使用参数的规范类型名称(全称且区分大小写)。 例如:
uint
必须写为 uint256
int
必须写为 int256
byte
必须写为 bytes1
示例:
transfer(address,uint256)
approve(address,uint256)
balanceOf(address)
2. 计算函数 Keccak-256 哈希:
3. 提取前 4 字节:
a9059cbb
transfer(address,uint256)
的选择器: 0xa9059cbb
使用案例:
function callTransfer(address _token, address _to, uint256 _amount) public {
// 生成包含选择器和参数的 calldata
bytes memory data = abi.encodeWithSignature("transfer(address,uint256)", _to, _amount);
// 发起调用
(bool success, ) = _token.call(data);
require(success, "Transfer call failed");
}
在 Solidity 中,abi.encode 和相关的方法主要用于将数据编码成字节数组,便于存储、传输或计算哈希值。这些方法是以太坊 ABI 编码标准的一部分。
将参数编码为标准 ABI 格式的字节数组,不带任何长度信息,适合用于哈希计算。
pragma solidity ^0.8.0;
contract ABIEncodeExample {
function encodeExample(uint256 a, string memory b) public pure returns (bytes memory) {
return abi.encode(a, b);
}
}
0x
0000000000000000000000000000000000000000000000000000000000000001 // uint256 a
0000000000000000000000000000000000000000000000000000000000000040 // 偏移量(字符串 b 的起始位置)
0000000000000000000000000000000000000000000000000000000000000005 // 字符串 b 的长度
68656c6c6f000000000000000000000000000000000000000000000000000000 // 字符串 b 的内容(hello)
以紧凑格式编码参数。
pragma solidity ^0.8.0;
contract ABIEncodePackedExample {
function encodePackedExample(uint256 a, string memory b) public pure returns (bytes memory) {
return abi.encodePacked(a, b);
}
}
编码结果:对 encodePackedExample(1, "hello") 的结果。
0x
010000000000000000000000000000000000000000000000000000000000000068656c6c6f
将数据编码,并在开头添加函数选择器。
用于构造函数调用数据
选择器是目标函数签名的前4字节
pragma solidity ^0.8.0;
contract ABIEncodeWithSelectorExample {
function encodeWithSelectorExample(address recipient, uint256 amount) public pure returns (bytes memory) {
return abi.encodeWithSelector(bytes4(keccak256("transfer(address,uint256)")), recipient, amount);
}
}
编码结果:对 encodeWithSelectorExample(0xAbCdEf0000000000000000000000000000000000, 100) 的结果:
0xa9059cbb // 函数选择器 transfer(address,uint256)
000000000000000000000000abcdef0000000000000000000000000000000000 // recipient
0000000000000000000000000000000000000000000000000000000000000064 // amount
根据字符串形式的函数签名生成编码数据。
pragma solidity ^0.8.0;
contract ABIEncodeWithSignatureExample {
function encodeWithSignatureExample(address recipient, uint256 amount) public pure returns (bytes memory) {
return abi.encodeWithSignature("transfer(address,uint256)", recipient, amount);
}
}
编码结果:对 encodeWithSelectorExample(0xAbCdEf0000000000000000000000000000000000, 100) 的结果
0xa9059cbb // 函数选择器 transfer(address,uint256)
000000000000000000000000abcdef0000000000000000000000000000000000 // recipient
0000000000000000000000000000000000000000000000000000000000000064 // amount
在低级调用(如 call)中,函数选择器用于标识目标函数。
contract Example {
function callTransfer(address target, address recipient, uint256 amount) public {
bytes memory data = abi.encodeWithSelector(
bytes4(keccak256("transfer(address,uint256)")),
recipient,
amount
);
(bool success, ) = target.call(data);
require(success, "Call failed");
}
}
当合约接收到调用但没有匹配函数时,会触发 fallback 函数,开发者可以通过 msg.data 获取函数选择器。
contract FallbackExample {
receive() external payable {}
bytes4 selector = bytes4(msg.data[:4]); // depositETH 函数选择器
// 根据 selector 执行相应逻辑
}
receive() external payable {
depositETH()
}
// 上面两个 receive 方法的效果有
function depositETH() public {
}
}
在模块化合约(如钻石标准)中,函数选择器用于路由到不同的实现模块。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!