WTF-Solidity 函数选择器(Function Selector)深度解析:从 calldata 到 selector 调用
WTF-Solidity 函数选择器Function Selector深度解析从 calldata 到 selector 调用【免费下载链接】WTF-SolidityWTF Solidity 极简入门教程供小白们使用。Now supports English! 官网: https://wtf.academy项目地址: https://gitcode.com/GitHub_Trending/wt/WTF-Solidity函数选择器Function Selector是 Solidity 智能合约调用中最底层的机制每次调用合约实际上就是向目标合约发送一段calldata而这段数据的前 4 个字节就是选择器它决定了 EVM 会执行合约中的哪一个函数。本篇基于 WTF-Solidity 第 29 讲西班牙语版 Languages/es/29_Selector_es/readme.md结合仓库中的 Selector.sol 源码系统讲解selector与msg.data、函数签名的关系并演示如何用abi.encodeWithSelector配合call精准调用目标函数。读完本文你将能手动解析任意交易的 calldata、自行计算任意函数的 method id并实现不看 ABI 也能按选择器调用函数的底层调用能力。一、一切从calldata说起一次合约调用发生了什么调用一个智能合约时本质上就是向合约地址发送一段calldata调用数据。在 Remix 中发送交易后打开交易详情可以看到input字段它记录的正是这次交易的calldatamsg.data是 Solidity 的全局变量其值就是完整的calldata即调用函数时传入的全部数据。在下面的代码中通过事件Log可以把调用mint函数时的完整calldata原样输出源码见 Languages/es/29_Selector_es/Selector.sol// event 返回 msg.data event Log(bytes data); // 输入参数 to: 0x2c44b726ADF1963cA47Af88B284C06f30380fC78 function mint(address /*to*/) external { emit Log(msg.data); }当参数传入0x2c44b726ADF1963cA47Af88B284C06f30380fC78时事件输出的calldata为0x6a6278420000000000000000000000002c44b726adf1963ca47af88b284c06f30380fc78这段看似杂乱的字节码可以清晰地拆成两部分前 4 字节是选择器selector 0x6a627842 紧随其后的 32 字节是输入参数 0x0000000000000000000000002c44b726adf1963ca47af88b284c06f30380fc78从这个例子可以直观看出calldata的作用它告诉智能合约我要调用哪个函数由 selector 指定以及调用时的参数是什么由后续编码数据指定。其中calldata开头的 4 个字节就是本讲的核心概念——函数选择器Function Selector。二、method id、selector 与函数签名三个概念的底层关系2.1 定义method id的定义是对函数签名做Keccak哈希后取结果的前 4 个字节。当传入的selector与某个函数的method id匹配时EVM 就会执行该函数。那么函数签名Function Signature又是什么它的格式为函数名(用逗号分隔的参数类型)例如上面mint函数的签名就是mint(address)。在同一份合约中不同函数具有不同的函数签名因此合约可以据此判定应该执行哪一个函数。这与 WTF-Solidity 第 21 讲21_CallContract/readme.md中介绍的函数调用机制一脉相承。2.2 必须注意的签名规范在写函数签名时有一个极易踩坑的规则uint和int必须写成uint256和int256。也就是说即使函数声明时参数类型写的是uint在计算函数签名时也要展开成uint256否则哈希结果进而 selector就会不同。2.3 用代码验证 method id在 Selector.sol 中通过keccak256直接计算mint(address)的哈希并截取前 4 字节即可验证mint函数的 method id 正是0x6a627842// 输出 selector // mint(address) 0x6a627842 function mintSelector() external pure returns (bytes4 mSelector) { return bytes4(keccak256(mint(address))); }调用mintSelector的返回结果与上节 calldata 中的前 4 字节完全一致从源码结构看keccak256哈希第 28 讲 28_Hash/readme.md配合bytes4强制类型转换正是 Solidity 官方约定计算 method id 的标准做法。三、用 selector 调用目标函数abi.encodeWithSelector实战拿到 selector 后可以用它来直接调用目标函数。核心工具是abi.encodeWithSelector它把函数的 method id 作为 selector与参数一起打包编码再交给call发送从而在不依赖编译器自动派发的情况下完成一次函数调用// 使用 selector 来调用函数 function callWithSignature() external returns (bool, bytes memory) { // 利用 abi.encodeWithSelector 将 mint 函数的 selector 和参数打包编码 (bool success, bytes memory data) address(this).call( abi.encodeWithSelector( 0x6a627842, 0x2c44b726ADF1963cA47Af88B284C06f30380fC78 ) ); return (success, data); }调用callWithSignature后可以在日志中看到mint函数被成功触发Log事件被输出这里补充一个关键细节上面示例中传入0x2c44b726ADF1963cA47Af88B284C06f30380fC78是一个带引号的字符串字面量。在标准用法中应直接传入address类型参数abi.encodeWithSelector会自动将其按 ABI 规范编码为 32 字节。编码规则可进一步参考第 27 讲 27_ABIEncode/readme.md。四、源码纵深不同类型参数的 selector 是如何计算的仅掌握基础类型还不够实际合约中的函数参数可能包含定长数组、动态数组、结构体、枚举等复杂类型。仓库根目录下的中文原版 29_Selector/Selector.sol 提供了覆盖这些场景的完整示例可直接用于理解 selector 的签名编码规则。4.1 各类参数的函数签名与 method id 对照函数函数签名method idnonParamSelector()nonParamSelector()0x03817936elementaryParamSelector(uint256, bool)elementaryParamSelector(uint256,bool)0x3ec37834fixedSizeParamSelector(uint256[3])fixedSizeParamSelector(uint256[3])0xead6b8bdnonFixedSizeParamSelector(uint256[], string)nonFixedSizeParamSelector(uint256[],string)0xf0ca01demappingParamSelector(DemoContract, User, uint256[], School)mappingParamSelector(address,(uint256,bytes),uint256[],uint8)0xe355b0ce从上表可以提炼出函数签名的几个编码规律均可在 29_Selector/Selector.sol 源码注释中找到对应依据无参数函数签名为函数名()括号内为空。基础类型如uint256、bool直接写类型名并用逗号分隔。定长数组如uint256[3]在类型后带数组长度。动态数组与字符串如uint256[]、string与定长数组写法不同无长度数字。合约类型与结构体合约类型DemoContract在签名中写为其地址类型address结构体User展开为元组(uint256,bytes)。枚举类型枚举School在签名中写为其底层类型uint8。4.2 逐一验证复杂参数的 selector 计算与调用29_Selector/Selector.sol 中每个函数都同时用两种方式互相印证既通过keccak256计算函数签名的哈希也通过内置的this.functionName.selector属性直接取得真实 selector二者一致才返回。例如// 无参数 selector function nonParamSelector() external returns(bytes4 selectorWithNonParam){ emit SelectorEvent(this.nonParamSelector.selector); return bytes4(keccak256(nonParamSelector())); }4.3 用真实参数逐一触发扩展版的 callWithSignature 展示了如何为上述每一种参数类型构造真实参数并用 selector 调用// 初始化 uint256 数组 uint256[] memory param1 new uint256[](3); param1[0] 1; param1[1] 2; param1[2] 3; // 初始化 struct User memory user; user.uid 1; user.name 0xa0b1; // 利用 abi.encodeWithSelector 将函数的 selector 和参数打包编码 (bool success0, bytes memory data0) address(this).call(abi.encodeWithSelector(0x03817936)); // nonParamSelector() (bool success1, bytes memory data1) address(this).call(abi.encodeWithSelector(0x3ec37834, 1, 0)); // elementaryParamSelector(uint256,bool) (bool success2, bytes memory data2) address(this).call(abi.encodeWithSelector(0xead6b8bd, [1,2,3])); // fixedSizeParamSelector(uint256[3]) (bool success3, bytes memory data3) address(this).call(abi.encodeWithSelector(0xf0ca01de, param1, abc)); // nonFixedSizeParamSelector(uint256[],string) (bool success4, bytes memory data4) address(this).call(abi.encodeWithSelector(0xe355b0ce, 0x9D7f74d0C41E726EC95884E0e97Fa6129e3b5E99, user, param1, 1)); // mappingParamSelector(...) require(success0 success1 success2 success3 success4);注意其中两点call返回的(bool success, bytes memory data)中success表示目标调用是否成功末尾用require统一校验所有调用都成功abi.encodeWithSelector的第一个参数是bytes4类型的 selector后续参数按顺序编码进 calldata其布局与第 27 讲 27_ABIEncode/readme.md 中介绍的 ABI 编码规则完全一致——这也解释了为什么只要函数签名确定、参数类型确定离线端也能构造出合法可执行的交易数据。五、总结本讲围绕函数选择器建立了完整的认知链条msg.data即完整 calldata调用合约时发送的数据可通过全局变量msg.data原样获取selector 是 calldata 的前 4 字节它由函数签名经keccak256哈希后截取而来即method id函数签名有严格规范格式为函数名(参数类型列表)且uint/int必须写作uint256/int256复杂类型合约、结构体、枚举、数组需按 ABI 规则展开selector 可直接驱动调用通过abi.encodeWithSelector把 selector 与参数打包后传给call即可在不依赖编译器派发的情况下精确调用目标函数。掌握 selector 机制是理解低层调用call、delegatecall、代理合约、多签钱包乃至各类安全漏洞如 selector 碰撞的必备基础。想动手验证可基于本仓库的 Languages/es/29_Selector_es/Selector.sol 或完整示例 29_Selector/Selector.sol 在 Remix 中部署后逐个调用mintSelector、nonParamSelector与callWithSignature观察返回的 selector 与日志输出。【免费下载链接】WTF-SolidityWTF Solidity 极简入门教程供小白们使用。Now supports English! 官网: https://wtf.academy项目地址: https://gitcode.com/GitHub_Trending/wt/WTF-Solidity创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考