【solidity进阶】函数选择器详解

什么是函数选择器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 哈希:

  • 使用 keccake-256 对 functionName(typeN1,typeN2,typeN3....typeN)进行 Hash

3. 提取前 4 字节:

  • 取上面哈希值的前 4 个字节(8 个十六进制字符): 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");
}

ABI 系列函数

在 Solidity 中,abi.encode 和相关的方法主要用于将数据编码成字节数组,便于存储、传输或计算哈希值。这些方法是以太坊 ABI 编码标准的一部分。

1. abi.encode

将参数编码为标准 ABI 格式的字节数组,不带任何长度信息,适合用于哈希计算。

  • 所有数据被编码为 32 字节的倍数
  • 所有数据(如字符串、数组)在编码中会包含指向其内容的偏移量
pragma solidity ^0.8.0;

contract ABIEncodeExample {
    function encodeExample(uint256 a, string memory b) public pure returns (bytes memory) {
        return abi.encode(a, b);
    }
}
  • 编码结果:对 encodeExample(1, "hello") 的结果:
    0x
    0000000000000000000000000000000000000000000000000000000000000001 // uint256 a
    0000000000000000000000000000000000000000000000000000000000000040 // 偏移量(字符串 b 的起始位置)
    0000000000000000000000000000000000000000000000000000000000000005 // 字符串 b 的长度
    68656c6c6f000000000000000000000000000000000000000000000000000000 // 字符串 b 的内容(hello)

2. abi.encodePacked

以紧凑格式编码参数。

  • 移除了动态类型的填充字节,编码结果更短。
  • 适用于哈希计算或签名场景。
  • 需要注意不同类型可能导致编码结果冲突。
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

    3. abi.encodeWithSelector

    将数据编码,并在开头添加函数选择器。

  • 用于构造函数调用数据

  • 选择器是目标函数签名的前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

4. abi.encodeWIthSignature

根据字符串形式的函数签名生成编码数据。

  • 等价于 abi.encodeWithSelector,但更直观。
  • 可直接传入函数签名字符串,省去手动计算选择器的步骤。
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

注意事项

  • abi.encodePacked 的哈希冲突:
    • 动态类型(如 string 和 bytes)的编码结果可能导致冲突
    • 例如:abi.encodePacked("abc", "def") 和 abi.encodePacked("ab", "cdef") 的结果相同。
    • 解决方法:加入额外信息,如长度。
  • 函数选择器冲突
    • 确保选择器唯一,避免多个函数签名产生相同选择器。
  • 与哈希结合
    • keccak256(abi.encode(...)) 和 keccak256(abi.encodePacked(...)) 用途不同。
    • 标准 ABI 用于数据验证,紧凑编码多用于签名和验证。

函数选择器的用途

1. 智能合约调用

在低级调用(如 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");
    }
}

2. Fallback 函数

当合约接收到调用但没有匹配函数时,会触发 fallback 函数,开发者可以通过 msg.data 获取函数选择器。

 contract FallbackExample {
   receive() external payable {}
        bytes4 selector = bytes4(msg.data[:4]); // depositETH 函数选择器
        // 根据 selector 执行相应逻辑
    }

     receive() external payable {
        depositETH()
    }

    // 上面两个 receive 方法的效果有
    function depositETH() public {

    }
}

3. EIP-2535

在模块化合约(如钻石标准)中,函数选择器用于路由到不同的实现模块。

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
mengbuluo222
mengbuluo222
0x9Ff1...FaA5
前端开发求职中... 8年+开发经验,拥有丰富的开发经验,擅长VUE、React开发。