首页
学习
活动
专区
圈层
工具
发布
49 篇文章
1
《纸上谈兵·solidity》第 0 课:搭建 Solidity 开发环境(三种方式)
2
《纸上谈兵·solidity》第 1 课:部署你的第一个 Solidity 合约
3
《纸上谈兵·solidity》第 2 课:调用、修改、读取,Solidity 合约不是 REST API
4
《纸上谈兵·solidity》第 3 课:事件(Event)机制与链上日志——不是 print,是广播!
5
《纸上谈兵·solidity》第 4 课:Solidity 合约中的错误处理机制(`require`、`revert`、`assert`)和自定义错误
6
《纸上谈兵·solidity》第 5 课:依赖与外部调用 —— 合约交互的风险与防护
7
《纸上谈兵·solidity》第 6 课:Solidity 数据存储布局 —— memory、storage、calldata 傻傻分不清?
8
《纸上谈兵·solidity》第 7 课:Solidity 函数可见性和修饰器 —— public 和 private 不只是权限标签
9
《纸上谈兵·solidity》第 8 课:Solidity 中的继承与接口 —— 模块化不是“复制粘贴”的借口
10
《纸上谈兵·solidity》第 9 课:Solidity 事件与日志机制 —— 合约世界的“printf”工具
11
《纸上谈兵·solidity》第 10 课:Solidity `fallback` / `receive` 函数 —— 合约如何收 ETH 和响应未知调用?
12
《纸上谈兵·solidity》第 11 课:Solidity 错误处理与异常机制 —— 让合约优雅地失败
13
《纸上谈兵·solidity》第 12 课:Solidity 函数选择器与 ABI 编码原理
14
《纸上谈兵·solidity》第 13 课:Solidity 低级调用 call/delegatecall/staticcall —— 直接和 EVM“对话”
15
《纸上谈兵·solidity》第 14 课:Solidity 中的可升级合约模式 —— 从代理合约到透明代理、UUPS 与安全陷阱
16
《纸上谈兵·solidity》第 15 课:Solidity 库与可重用代码
17
《纸上谈兵·solidity》第 16 课:Pull over Push 支付模式与 Check-Effects-Interactions 原则
18
《纸上谈兵·solidity》第 17 课:合约设计模式实战(二)—— Access Control 与权限管理
19
《纸上谈兵·solidity》第 18 课:合约设计模式实战(三)—— 代理 + 插件化架构(Diamond Standard / EIP-2535)
20
《纸上谈兵·solidity》第 19 课:安全专题(一)—— 常见攻击手法与防御
21
《纸上谈兵·solidity》第 20 课:Solidity 安全专题(二)—— 编译器特性与低级漏洞
22
《纸上谈兵·solidity》第 21 课:Gas 优化与成本分析 —— 写出便宜的智能合约
23
《纸上谈兵·solidity》第 22 课:代币合约(ERC20)从零实现与扩展
24
《纸上谈兵·solidity》第 23 课:NFT 合约(ERC721 / ERC1155)实战
25
《纸上谈兵·solidity》第 24 课:去中心化众筹合约(Crowdfunding)实战
26
《纸上谈兵·solidity》第 25 课:简化版的去中心化交易所(DEX)简化版
27
《纸上谈兵·solidity》第 26 课:借贷合约简化实现
28
《纸上谈兵·solidity》第 27 课:DAO 治理合约(去中心化自治组织)
29
《纸上谈兵·solidity》第 28 课:智能合约安全审计案例复盘 -- The DAO Hack(2016)
30
《纸上谈兵·solidity》第 29 课:智能合约安全审计案例复盘 -- Parity Wallet Hack(2017)
31
《纸上谈兵·solidity》第 30 课:智能合约安全审计案例复盘 -- Nomad Bridge(2022)
32
《纸上谈兵·solidity》第 31 课:多签钱包在跨链桥中的应用 —— Nomad 事件复盘
33
《纸上谈兵·solidity》第 32 课:DeFi 基础合约
34
《纸上谈兵·solidity》第 33 课:多签钱包(Multisig Wallet)-- 合约设计与实现
35
《纸上谈兵·solidity》第 34 课:多签钱包(Multisig Wallet)-- 上线
36
《纸上谈兵·solidity》第 35 课:去中心化交易所(DEX)实战 — 合约设计
37
《纸上谈兵·solidity》第 36 课:去中心化交易所(DEX)实战 — 上线
38
《纸上谈兵·solidity》第 37 课:DeFi 实战 -- 资金池与利率模型
39
《纸上谈兵·solidity》第 38 课:DeFi 实战(2) -- 清算机制与价格预言机
40
《纸上谈兵·solidity》第 39 课:DeFi 实战(3) -- 利息累积与 aToken 设计
41
《纸上谈兵·solidity》第 40 课:DeFi 实战(4) -- 风险控制与防护
42
《纸上谈兵·solidity》第 41 课:DeFi 实战(5) -- 协议费与治理
43
《纸上谈兵·solidity》第 42 课:DeFi 实战(6) -- 跨资产借贷与多市场支持
44
《纸上谈兵·solidity》第 43 课:DeFi 实战(7) -- 清算机制进阶(多资产抵押清算路径、拍卖机制)
45
《纸上谈兵·solidity》第 44 课:DeFi 实战(8) -- 利率曲线与资金池优化(动态利用率模型)
46
《纸上谈兵·solidity》第 45 课:DeFi 实战(9) -- 利息累积与结算机制(可复利)
47
《纸上谈兵·solidity》第 46 课:DeFi 实战(10) -- 跨链借贷与流动性桥接
48
《纸上谈兵·solidity》第 47 课:DeFi 实战(11) -- 治理代币 & 激励机制(Tokenomics & Governance)
49
《纸上谈兵·solidity》第 48 课:DeFi 实战(12) -- 前端 DApp 集成与用户交互(React + ethers.js 实战)

《纸上谈兵·solidity》第 12 课:Solidity 函数选择器与 ABI 编码原理

在以太坊的世界里,合约函数调用不是“直接调用函数”,而是发送一段经过 ABI 编码的二进制数据。这些数据不仅包含了调用哪个函数的信息,还包括函数参数的序列化内容。理解 ABI 编码与函数选择器,可以帮助我们:

  • 调试交易数据(从原始 data 解读调用意图)
  • 手写低级调用(call / delegatecall
  • 分析合约安全问题(例如函数签名冲突)

1. 什么是函数选择器(Function Selector)

函数选择器是函数调用数据的前 4 个字节,用来标识调用的是哪一个函数。

它的生成方式是:

代码语言:txt
复制
selector = keccak256("函数名(参数类型列表)") 前 4 个字节

例如:

代码语言:txt
复制
function transfer(address to, uint256 amount) public;

计算:

代码语言:txt
复制
keccak256("transfer(address,uint256)") 
= 0xa9059cbb...
selector = 0xa9059cbb

作用:当 EVM 收到一笔交易时,会查看 msg.data 的前 4 个字节,通过函数选择器找到匹配的函数,然后解码后续数据作为参数。


2. ABI 编码流程

ABI(Application Binary Interface)规定了参数如何序列化。以 transfer(address,uint256) 调用为例:

  1. 计算函数选择器:0xa9059cbbundefined
  2. 按 ABI 规则编码参数:
    • address 类型是 20 字节,要补齐到 32 字节(左填充 0)
    • uint256 类型是 32 字节(大端序,左填充 0)
  3. 拼接:data = 0xa9059cbb 000000000000000000000000<to地址20字节> 000000000000000000000000000000000000000000000000<amount>

3. Solidity 中的函数选择器使用

我们可以通过 Solidity 内置属性获取函数选择器:

代码语言:txt
复制
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract SelectorExample {
    function getSelector() public pure returns (bytes4) {
        return this.transfer.selector;
    }

    function transfer(address to, uint256 amount) public pure returns (bool) {
        return true;
    }
}

4. 使用 Foundry 验证 ABI 编码

我们用 Foundry 写一个测试,验证 ABI 编码是否正确。

src/SelectorExample.sol

代码语言:txt
复制
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract SelectorExample {
    function transfer(address to, uint256 amount) public pure returns (bool) {
        return true;
    }
}

test/SelectorExample.t.sol

代码语言:txt
复制
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "forge-std/Test.sol";
import "../src/SelectorExample.sol";

contract SelectorExampleTest is Test {
    SelectorExample example;

    function setUp() public {
        example = new SelectorExample();
    }

    function testSelector() public {
        bytes4 selector = example.transfer.selector;
        assertEq(selector, bytes4(keccak256("transfer(address,uint256)")));
    }

    function testABIEncoding() public {
        address to = address(0x123);
        uint256 amount = 100;

        bytes memory encoded = abi.encodeWithSelector(
            example.transfer.selector,
            to,
            amount
        );

        emit log_bytes(encoded);
    }
}

运行:

代码语言:bash
复制
➜  counter git:(main) ✗ forge test --match-path test/SelectorExample.t.sol -vvv
[⠊] Compiling...
[⠆] Compiling 1 files with Solc 0.8.29
[⠰] Solc 0.8.29 finished in 1.26s
Compiler run successful with warnings:
Warning (5667): Unused function parameter. Remove or comment out the variable name to silence this warning.
 --> src/SelectorExample.sol:5:23:
  |
5 |     function transfer(address to, uint256 amount) public pure returns (bool) {
  |                       ^^^^^^^^^^

Warning (5667): Unused function parameter. Remove or comment out the variable name to silence this warning.
 --> src/SelectorExample.sol:5:35:
  |
5 |     function transfer(address to, uint256 amount) public pure returns (bool) {
  |                                   ^^^^^^^^^^^^^^


Ran 2 tests for test/SelectorExample.t.sol:SelectorExampleTest
[PASS] testABIEncoding() (gas: 5278)
Logs:
  0xa9059cbb00000000000000000000000000000000000000000000000000000000000001230000000000000000000000000000000000000000000000000000000000000064

[PASS] testSelector() (gas: 5512)
Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 8.14ms (2.12ms CPU time)

Ran 1 test suite in 438.24ms (8.14ms CPU time): 2 tests passed, 0 failed, 0 skipped (2 total tests)

输出的 log_bytes 会显示完整的 ABI 编码结果。


5. 低级调用与函数选择器

有时我们需要使用 call 来调用其他合约的函数:

代码语言:txt
复制
(bool success, bytes memory data) = target.call(
    abi.encodeWithSelector(
        bytes4(keccak256("transfer(address,uint256)")),
        to,
        amount
    )
);
  • abi.encodeWithSelector:手动指定函数选择器
  • abi.encodeWithSignature:直接用签名字符串计算选择器

6. 安全注意事项

  1. 函数签名冲突: 不同函数如果签名哈希的前 4 个字节相同(极低概率),可能导致调用混淆。
  2. 低级调用缺乏类型检查: 使用 call 时不会检查参数类型是否匹配,需谨慎验证输入。
  3. 解码风险:abi.decode 时必须确保数据格式正确,否则会 revert。

总结

  • 函数选择器是调用函数的“身份证”
  • ABI 编码定义了参数如何序列化
  • 熟悉函数选择器和 ABI 编码,可以进行底层调试、合约互操作以及安全分析

练习建议:

  1. 计算几个常见 ERC20 函数的选择器
  2. 用 Foundry 编写测试,验证 abi.encodeWithSelector 与手工拼接结果一致
  3. 尝试用低级调用向一个已部署的 ERC20 发送交易
下一篇
举报
领券