函数签名是用来标识函数的唯一标识符,它由函数的名称和参数类型组成。例如,函数 foo(uint256 a, string memory b) 的签名是 foo(uint256,string)。
Solidity 使用特定的哈希函数(例如 Keccak256)来计算函数签名的哈希值,然后取前四个字节作为函数选择器。函数选择器用于在调用合约时确定要执行哪个函数。
下面演示如何使用函数签名和选择器:
contract FunctionSignature {
// 定义一个事件,用于记录调用的结果
event Called(string name);
// 定义一个公开的 foo 函数,接受两个参数
function foo(uint256 a, string memory b) public {
// 触发 Called 事件,传入 "foo" 作为参数
emit Called("foo");
}
// 定义一个公开的 bar 函数,接受一个参数
function bar(address c) public {
// 触发 Called 事件,传入 "bar" 作为参数
emit Called("bar");
}
// 定义一个外部的 fallback 函数,用于处理没有匹配到其他函数的调用
fallback() external {
// 获取 msg.data 的长度(字节数)
uint256 length = msg.data.length;
// 如果长度小于 4,则说明没有提供有效的函数选择器,直接返回
if (length < 4) {
return;
}
// 否则,从 msg.data 中取出前四个字节作为选择器,并存储到变量 selector 中
bytes4 selector;
assembly {
selector := mload(add(msg.data, 0x20))
}
// 判断选择器是否等于 foo 函数的签名哈希值(前四个字节)
if (selector == bytes4(keccak256("foo(uint256,string)"))) {
// 如果是,则调用 foo 函数,并将 msg.data 中剩余的字节作为参数传入
(bool success,) = address(this).delegatecall(
abi.encodePacked(bytes4(keccak256("foo(uint256,string)")), msg.data[4:])
);
require(success);
return;
}
// 判断选择器是否等于 bar 函数的签名哈希值(前四个字节)
if (selector == bytes4(keccak256("bar(address)"))) {
// 如果是,则调用 bar 函数,并将 msg.data 中剩余的字节作为参数传入
(bool success,) = address(this).delegatecall(
abi.encodePacked(bytes4(keccak256("bar(address)")), msg.data[4:])
);
require(success);
return;
}
// 如果都不匹配,则抛出异常
revert();
}
}