本文详细介绍了Solidity中的函数选择器(Function Selector),包括其定义、使用方法、计算方式以及相关注意事项。文章还探讨了函数选择器与EVM的关系,并提供了相关的代码示例和实用资源。
函数选择器是一个 4 字节的 ID,Solidity 用于在后台识别函数。
函数选择器是 Solidity 合约了解在交易中你要调用哪个函数的方式。
你可以使用 .selector
方法查看 4 字节 ID:
pragma solidity 0.8.25;
contract SelectorTest{
function foo() public {}
function getSelectorOfFoo() external pure returns (bytes4) {
return this.foo.selector; // 0xc2985578
}
}
如果一个函数不接受任何参数,你可以通过将这 4 个字节(选择器)作为数据发送到合约,并使用低级 call
来调用该函数。
在以下示例中,CallFoo
合约通过调用 FooContract
及适当的四字节函数选择器来调用 FooContract
中定义的 foo 函数。
pragma solidity 0.8.25;
contract CallFoo {
function callFooLowLevel(address _contract) external {
bytes4 fooSelector = 0xc2985578;
(bool ok, ) = _contract.call(abi.encodePacked(fooSelector));
require(ok, "call failed");
}
}
contract FooContract {
uint256 public x;
function foo() public {
x = 1;
}
}
如果 FooContract
和 CallFoo
被部署,并且执行 callFooLowLevel()
函数并传入 FooContract
的地址,那么 FooContract
中的 x
值会被设定为 1
。这表明成功调用了 foo
函数。
msg.sig
是一个全局变量,返回交易数据的前四个字节,这就是 Solidity 合约了解要调用哪个函数的方式。
msg.sig
在整个交易中保持不变,它不会因当前活动的函数而改变。所以即使公共函数在调用期间调用了另一个公共函数,msg.sig
也将是最初调用的函数的选择器。
在以下代码中,foo
调用 bar
来获取 msg.sig
,但当调用 foo
时返回的选择器是 foo
的函数选择器,而不是 bar
:
你可以在 Remix 中测试代码:
//SPDX-License-Identifier: MIT
pragma solidity 0.8.25;
contract SelectorTest {
// 返回函数选择器 `bar()` 0xfebb0f7e
function bar() public pure returns (bytes4) {
return msg.sig;
}
// 返回函数选择器 `foo()` 0xc2985578
function foo() public pure returns (bytes4) {
return bar();
}
function testSelectors() external pure returns (bool) {
assert(this.foo.selector == 0xc2985578);
assert(this.bar.selector == 0xfebb0f7e);
return true;
}
}
Solidity 中的函数签名是一个字符串,包含合约名称后接其接受的参数类型。参数的变量名称从参数中移除。
在以下代码片段中,函数位于左侧,函数签名位于右侧:
function setPoint(uint256 x, uint256 y) --> "setPoint(uint256,uint256)"
function setName(string memory name) --> "setName(string)"
function addValue(uint v) --> "addValue(uint256)"
函数选择器中没有空格。所有 uint 类型必须明确包括其大小(uint256、uint40、uint8 等)。不包括 calldata
和 memory
类型。例如,getBalanceById(uint)
是一个无效的 signature。
函数选择器是函数签名的 keccak256 哈希的前四个字节。
function returnFunctionSelectorFromSignature(
string calldata functionName
) public pure returns(bytes4) {
bytes4 functionSelector = bytes4(keccak256(abi.encodePacked(functionName)));
return(functionSelector);
}
将 “foo()
” 输入上面的函数将返回 0xc2985578
。
以下代码演示了给定函数签名计算函数选择器。
//SPDX-License-Identifier: MIT
pragma solidity 0.8.25;
contract FunctionSignatureTest {
function foo() external {}
function point(uint256 x, uint256 y) external {}
function setName(string memory name) external {}
function testSignatures() external pure returns (bool) {
// 注意:转换为 bytes4 只取前 4 字节
// 并移除其余部分
assert(bytes4(keccak256("foo()")) == this.foo.selector);
assert(bytes4(keccak256("point(uint256,uint256)")) == this.point.selector);
assert(bytes4(keccak256("setName(string)")) == this.setName.selector);
return true;
}
}
接口作为地址编码。例如,f(IERC20 token)
被编码为 f(address token)
。将地址设为可支付不会改变签名。
公共和外部函数有选择器,但内部和私有函数没有。以下代码无法编译:
contract Foo {
function bar() internal {
}
// 无法编译
function barSelector() external pure returns (bytes4) {
return this.bar.selector;
}
}
如果将 bar
更改为 public
或 external
,代码将编译成功。
内部函数不需要函数选择器,因为函数选择器用于外部合约。换句话说,函数选择器是外部用户指定他们要调用的公共或外部函数的方式。
Solidity 函数名可以任意长,如果函数名很长,交易的大小和成本都会增加。如果提供函数选择器,交易通常会更小。
回退函数没有函数选择器。
如果在回退函数内部记录 msg.sig
,它将简单地记录交易数据的前四个字节。如果没有交易数据,则 msg.sig
将返回四个零字节。这并不意味着回退函数的选择器全为零,而是 msg.sig
尝试读取不存在的数据。
contract FunctionSignatureTest {
event LogSelector(bytes4);
fallback() external payable {
emit LogSelector(msg.sig);
}
}
这里是 Remix 上的代码 以测试上述合约。以下是如何在 Remix 上触发回退函数的视频:
https://img.learnblockchain.cn/2025/02/26/file.mp4
函数选择器最多可以持有 2**32 – 1(约 42 亿)个可能值。因此,两个函数具有相同选择器的机会小但实际存在。
contract WontCompile {
function collate_propagate_storage(bytes16 x) external {}
function burn(uint256 amount) external {}
}
上述示例摘自 该论坛帖子。这两个函数的函数选择器都为 0x42966c68
。
函数选择器计算器。请务必添加关键字 function
。计算器期望格式为 function burn(uint256)
。
函数选择器数据库。将 4 个字节放入搜索框会返回匹配的已知函数。
对于熟悉 EVM 的读者来说,有一个常见的混淆源。四字节函数选择器发生在 Solidity 应用层,而不是 EVM 层。以太坊规范中没有任何条款要求函数必须用 4 字节识别。这是一个强烈的约定,但并非必需。
事实上,一个常见的 针对二层应用的优化 是用 1 字节函数选择器来识别函数。也就是说,所有函数调用都会转到回退函数,然后回退函数查看交易中的第一个字节来确定要调用哪个函数。
请查看我们的 Solidity 实训营,了解有关智能合约开发的更多高级主题。
原始发布日期:2024年3月30日
- 原文链接: rareskills.io/post/funct...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!