ABI (Application Binary Interface)
概念简介
ABI(Application Binary Interface,应用程序二进制接口)是以太坊智能合约的标准化接口规范,定义了如何编码函数调用和数据,使得外部应用程序(如前端、脚本、其他合约)能够与智能合约进行交互。ABI 是连接链上合约和链下应用的桥梁,类似于传统软件开发中的 API 接口定义。
核心作用:
智能合约生命周期中的 ABI:
开发阶段:
Solidity 源代码 → 编译器 → 字节码 + ABI JSON
部署阶段:
字节码 → 部署到区块链 → 合约地址
调用阶段:
前端应用 + ABI JSON + 合约地址
↓
编码函数调用(ABI 编码)
↓
发送交易到区块链
↓
EVM 解码并执行
↓
返回结果(ABI 解码)
↓
前端应用接收数据
ABI vs API:
传统 API(应用程序接口):
- 高级语言定义(如 JSON、XML)
- 人类可读
- 通过 HTTP 等协议传输
区块链 ABI:
- 二进制编码(字节序列)
- 机器可读,需要解码
- 通过交易 calldata 传输
- 严格的编码规则(确定性)
历史发展:
- 2014年:以太坊早期版本定义基础 ABI 规范
- 2015年:正式化 ABI 编码规则(Solidity ABI specification)
- 2017年:引入 ABI 编码 v2(支持复杂类型如嵌套结构体)
- 2020年+:工具生态成熟(ethers.js、viem 等提供完善支持)
ABI 描述格式
JSON 格式规范
ABI 使用 JSON 数组描述合约接口,每个元素描述一个函数、事件、错误或构造函数。
完整示例:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Example {
uint256 public value;
address public owner;
event ValueChanged(uint256 indexed oldValue, uint256 newValue, address indexed changer);
error Unauthorized(address caller);
constructor(uint256 _initialValue) {
value = _initialValue;
owner = msg.sender;
}
function setValue(uint256 _newValue) public {
if (msg.sender != owner) revert Unauthorized(msg.sender);
uint256 oldValue = value;
value = _newValue;
emit ValueChanged(oldValue, _newValue, msg.sender);
}
function getValue() public view returns (uint256) {
return value;
}
function transfer(address _newOwner) external {
require(msg.sender == owner, "Not owner");
owner = _newOwner;
}
}
对应的 ABI JSON:
[
{
"type": "constructor",
"inputs": [
{
"name": "_initialValue",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "nonpayable"
},
{
"type": "function",
"name": "setValue",
"inputs": [
{
"name": "_newValue",
"type": "uint256",
"internalType": "uint256"
}
],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"type": "function",
"name": "getValue",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "transfer",
"inputs": [
{
"name": "_newOwner",
"type": "address",
"internalType": "address"
}
],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"type": "function",
"name": "value",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "owner",
"inputs": [],
"outputs": [
{
"name": "",
"type": "address",
"internalType": "address"
}
],
"stateMutability": "view"
},
{
"type": "event",
"name": "ValueChanged",
"inputs": [
{
"name": "oldValue",
"type": "uint256",
"indexed": true,
"internalType": "uint256"
},
{
"name": "newValue",
"type": "uint256",
"indexed": false,
"internalType": "uint256"
},
{
"name": "changer",
"type": "address",
"indexed": true,
"internalType": "address"
}
],
"anonymous": false
},
{
"type": "error",
"name": "Unauthorized",
"inputs": [
{
"name": "caller",
"type": "address",
"internalType": "address"
}
]
}
]
字段说明
通用字段:
type: 类型
- "function": 函数
- "constructor": 构造函数
- "receive": receive 函数(接收ETH)
- "fallback": fallback 函数
- "event": 事件
- "error": 自定义错误
name: 名称
- 函数/事件/错误的名称
- constructor、receive、fallback 无 name 字段
inputs: 输入参数数组
- name: 参数名
- type: 参数类型(规范类型)
- internalType: 内部类型(Solidity 类型)
- indexed: (仅事件) 是否索引
- components: (复杂类型) 子字段
outputs: 输出参数数组
- 仅函数有此字段
- 结构同 inputs
stateMutability: 状态可变性
- "pure": 不读取不修改状态
- "view": 读取状态但不修改
- "nonpayable": 修改状态,不接受 ETH
- "payable": 修改状态,接受 ETH
anonymous: (仅事件) 是否匿名
- false: 普通事件(包含事件签名)
- true: 匿名事件(不包含事件签名,节省 Gas)
ABI 编码规则
函数选择器 (Function Selector)
计算方法:
步骤:
1. 获取函数签名(不含参数名)
2. 计算 keccak256 哈希
3. 取前 4 字节
示例:
function transfer(address _to, uint256 _amount)
1. 函数签名:
"transfer(address,uint256)"
注意:
- 无空格
- 无参数名
- 使用规范类型名
2. keccak256 哈希:
keccak256("transfer(address,uint256)")
= 0xa9059cbb2ab09eb219583f4a59a5d0623ade346d962bcd4e46b11da047c9049b
3. 前 4 字节:
0xa9059cbb
代码实现:
// Solidity
bytes4 selector = bytes4(keccak256("transfer(address,uint256)"));
// selector = 0xa9059cbb
// JavaScript (ethers.js)
const selector = ethers.utils.id("transfer(address,uint256)").slice(0, 10);
// selector = "0xa9059cbb"
// JavaScript (viem)
import { keccak256, toBytes } from 'viem';
const selector = keccak256(toBytes("transfer(address,uint256)")).slice(0, 10);
常见函数选择器:
ERC20:
- transfer(address,uint256): 0xa9059cbb
- approve(address,uint256): 0x095ea7b3
- transferFrom(address,address,uint256): 0x23b872dd
- balanceOf(address): 0x70a08231
ERC721:
- safeTransferFrom(address,address,uint256): 0x42842e0e
- ownerOf(uint256): 0x6352211e
常用函数:
- initialize(): 0x8129fc1c
- upgradeTo(address): 0x3659cfe6
基本类型编码
编码规则:
所有类型都编码为 32 字节(256 位):
uint<N> (N = 8 to 256, steps of 8):
- 右对齐,左侧补零
- 示例: uint256(42)
→ 0x000000000000000000000000000000000000000000000000000000000000002a
int<N>:
- 有符号整数,二补码表示
- 示例: int256(-1)
→ 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
address:
- 20 字节地址,右对齐
- 示例: 0x1234567890123456789012345678901234567890
→ 0x0000000000000000000000001234567890123456789012345678901234567890
bool:
- true = 1, false = 0
- 示例: true
→ 0x0000000000000000000000000000000000000000000000000000000000000001
bytes<N> (N = 1 to 32):
- 定长字节,左对齐,右侧补零
- 示例: bytes4(0x12345678)
→ 0x1234567800000000000000000000000000000000000000000000000000000000
代码示例:
// Solidity 编码
function encodeBasicTypes() public pure returns (bytes memory) {
return abi.encode(
uint256(42), // 0x000...02a
int256(-1), // 0xfff...fff
address(0x1234...7890), // 0x000...1234567890
true, // 0x000...001
bytes4(0x12345678) // 0x123...000
);
}
// JavaScript (ethers.js)
const encoded = ethers.utils.defaultAbiCoder.encode(
['uint256', 'int256', 'address', 'bool', 'bytes4'],
[42, -1, '0x1234567890123456789012345678901234567890', true, '0x12345678']
);
动态类型编码
动态类型包括:
bytes(不定长字节)string(字符串)- 动态数组(
T[])
编码规则:
动态类型的编码分为两部分:
1. 头部(Head):数据偏移量(32 字节)
2. 尾部(Tail):实际数据
结构:
[头部] [尾部]
↓ ↓
offset length + data
示例:bytes
function encodeBytes() public pure returns (bytes memory) {
bytes memory data = hex"1234";
return abi.encode(data);
}
编码结果:
0x0000000000000000000000000000000000000000000000000000000000000020 // offset = 32
0000000000000000000000000000000000000000000000000000000000000002 // length = 2
1234000000000000000000000000000000000000000000000000000000000000 // data (左对齐)
解释:
- 第1个32字节:偏移量 = 0x20 (32),表示数据从第32字节开始
- 第2个32字节:长度 = 0x02,表示2个字节
- 第3个32字节:数据 = 0x1234(左对齐,右侧补零)
示例:string
function encodeString() public pure returns (bytes memory) {
return abi.encode("Hello");
}
编码结果:
0x0000000000000000000000000000000000000000000000000000000000000020 // offset
0000000000000000000000000000000000000000000000000000000000000005 // length = 5
48656c6c6f000000000000000000000000000000000000000000000000000000 // "Hello" (UTF-8)
"Hello" 的 UTF-8 编码:
H = 0x48
e = 0x65
l = 0x6c
l = 0x6c
o = 0x6f
示例:动态数组
function encodeDynamicArray() public pure returns (bytes memory) {
uint256[] memory arr = new uint256[](3);
arr[0] = 1;
arr[1] = 2;
arr[2] = 3;
return abi.encode(arr);
}
编码结果:
0x0000000000000000000000000000000000000000000000000000000000000020 // offset
0000000000000000000000000000000000000000000000000000000000000003 // length = 3
0000000000000000000000000000000000000000000000000000000000000001 // arr[0] = 1
0000000000000000000000000000000000000000000000000000000000000002 // arr[1] = 2
0000000000000000000000000000000000000000000000000000000000000003 // arr[2] = 3
复杂类型编码
多个参数混合:
function complexEncode(
uint256 a,
bytes memory b,
uint256 c
) public pure returns (bytes memory) {
return abi.encode(a, b, c);
}
调用:complexEncode(42, hex"1234", 100)
编码结果:
// 头部(Head)
0x000000000000000000000000000000000000000000000000000000000000002a // a = 42 (静态)
0000000000000000000000000000000000000000000000000000000000000060 // b 的偏移 = 96
0000000000000000000000000000000000000000000000000000000000000064 // c = 100 (静态)
// 尾部(Tail)- b 的数据
0000000000000000000000000000000000000000000000000000000000000002 // b.length = 2
1234000000000000000000000000000000000000000000000000000000000000 // b.data
规则:
- 静态类型(uint256)直接编码在头部
- 动态类型(bytes)在头部存储偏移量,数据在尾部
- 偏移量从头部开始计算
结构体编码:
struct Person {
string name;
uint256 age;
address wallet;
}
function encodeStruct() public pure returns (bytes memory) {
Person memory p = Person({
name: "Alice",
age: 30,
wallet: 0x1234567890123456789012345678901234567890
});
return abi.encode(p);
}
编码结果:
// 头部
0x0000000000000000000000000000000000000000000000000000000000000020 // struct 偏移
0000000000000000000000000000000000000000000000000000000000000060 // name 偏移(从struct开始)
000000000000000000000000000000000000000000000000000000000000001e // age = 30
0000000000000000000000001234567890123456789012345678901234567890 // wallet
// 尾部 - name 数据
0000000000000000000000000000000000000000000000000000000000000005 // name.length = 5
416c696365000000000000000000000000000000000000000000000000000000 // "Alice"
嵌套数组:
function encodeNestedArray() public pure returns (bytes memory) {
uint256[][] memory nested = new uint256[][](2);
nested[0] = new uint256[](2);
nested[0][0] = 1;
nested[0][1] = 2;
nested[1] = new uint256[](1);
nested[1][0] = 3;
return abi.encode(nested);
}
编码结果:
// 主数组头部
0x0000000000000000000000000000000000000000000000000000000000000020 // 偏移
0000000000000000000000000000000000000000000000000000000000000002 // 外层数组长度 = 2
0000000000000000000000000000000000000000000000000000000000000040 // nested[0] 偏移
00000000000000000000000000000000000000000000000000000000000000a0 // nested[1] 偏移
// nested[0] 数据
0000000000000000000000000000000000000000000000000000000000000002 // 长度 = 2
0000000000000000000000000000000000000000000000000000000000000001 // nested[0][0] = 1
0000000000000000000000000000000000000000000000000000000000000002 // nested[0][1] = 2
// nested[1] 数据
0000000000000000000000000000000000000000000000000000000000000001 // 长度 = 1
0000000000000000000000000000000000000000000000000000000000000003 // nested[1][0] = 3
函数调用编码
Calldata 结构
完整格式:
Calldata = Function Selector (4 bytes) + Encoded Arguments
示例:
transfer(address _to, uint256 _amount)
调用:
transfer(0x1234567890123456789012345678901234567890, 100)
Calldata:
0xa9059cbb // 选择器 (4 bytes)
0000000000000000000000001234567890123456789012345678901234567890 // _to (32 bytes)
0000000000000000000000000000000000000000000000000000000000000064 // _amount = 100 (32 bytes)
总长度:4 + 32 + 32 = 68 字节
生成 Calldata:
// ethers.js
const iface = new ethers.utils.Interface([
"function transfer(address _to, uint256 _amount)"
]);
const calldata = iface.encodeFunctionData("transfer", [
"0x1234567890123456789012345678901234567890",
100
]);
// calldata = "0xa9059cbb0000000000000000000000001234567890123456789012345678901234567890000000000000000000000000000000000000000000000000000000000000064"
// viem
import { encodeFunctionData } from 'viem';
const calldata = encodeFunctionData({
abi: [{
name: 'transfer',
type: 'function',
inputs: [
{ name: '_to', type: 'address' },
{ name: '_amount', type: 'uint256' }
]
}],
functionName: 'transfer',
args: ['0x1234567890123456789012345678901234567890', 100n]
});
// Solidity
bytes memory calldata = abi.encodeWithSelector(
bytes4(keccak256("transfer(address,uint256)")),
0x1234567890123456789012345678901234567890,
100
);
// 或者
calldata = abi.encodeCall(
IERC20.transfer,
(0x1234567890123456789012345678901234567890, 100)
);
解码 Calldata
手动解码:
contract CalldataDecoder {
function decodeTransfer(bytes calldata data)
public
pure
returns (address to, uint256 amount)
{
require(data.length >= 68, "Invalid calldata length");
// 检查函数选择器
bytes4 selector = bytes4(data[0:4]);
require(
selector == bytes4(keccak256("transfer(address,uint256)")),
"Wrong function"
);
// 解码参数
assembly {
// 跳过选择器(4字节),读取第一个参数
to := calldataload(add(data.offset, 4))
// 读取第二个参数
amount := calldataload(add(data.offset, 36))
}
}
}
// JavaScript
function decodeCalldata(calldata) {
// 提取选择器
const selector = calldata.slice(0, 10); // "0x" + 8 hex chars
// 提取参数
const to = "0x" + calldata.slice(34, 74); // 跳过 0x + 选择器(8) + 填充(24)
const amount = parseInt(calldata.slice(74, 138), 16);
return { selector, to, amount };
}
使用库解码:
// ethers.js
const iface = new ethers.utils.Interface([
"function transfer(address _to, uint256 _amount)"
]);
const decoded = iface.decodeFunctionData("transfer", calldata);
// decoded = {
// _to: "0x1234567890123456789012345678901234567890",
// _amount: BigNumber(100)
// }
// viem
import { decodeFunctionData } from 'viem';
const decoded = decodeFunctionData({
abi: [...],
data: calldata
});
事件编码
Log 结构
事件日志格式:
event Transfer(address indexed from, address indexed to, uint256 value);
emit Transfer(0xaaa..., 0xbbb..., 100);
Log 结构:
{
address: "0x合约地址",
topics: [
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", // 事件签名
"0x000000000000000000000000aaa...", // indexed: from
"0x000000000000000000000000bbb..." // indexed: to
],
data: "0x0000000000000000000000000000000000000000000000000000000000000064" // value = 100
}
事件签名计算:
步骤类似函数选择器,但使用完整 32 字节:
1. 事件签名:
"Transfer(address,address,uint256)"
2. keccak256 哈希:
keccak256("Transfer(address,address,uint256)")
= 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef
3. 完整 32 字节作为 topics[0]
注意:
- 匿名事件(anonymous)没有 topics[0]
- 最多 3 个 indexed 参数(topics[1-3])
- 非 indexed 参数编码在 data 中
常见事件签名:
ERC20:
Transfer(address,address,uint256):
0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef
Approval(address,address,uint256):
0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925
ERC721:
Transfer(address,address,uint256):
0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef
Approval(address,address,uint256):
0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925
ApprovalForAll(address,address,bool):
0x17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31
解析事件日志
代码示例:
// ethers.js
const iface = new ethers.utils.Interface([
"event Transfer(address indexed from, address indexed to, uint256 value)"
]);
// 解析日志
const log = {
topics: [
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
"0x000000000000000000000000aaa...",
"0x000000000000000000000000bbb..."
],
data: "0x0000000000000000000000000000000000000000000000000000000000000064"
};
const parsed = iface.parseLog(log);
// parsed = {
// name: "Transfer",
// args: {
// from: "0xaaa...",
// to: "0xbbb...",
// value: BigNumber(100)
// }
// }
// viem
import { decodeEventLog } from 'viem';
const decoded = decodeEventLog({
abi: [{
name: 'Transfer',
type: 'event',
inputs: [
{ name: 'from', type: 'address', indexed: true },
{ name: 'to', type: 'address', indexed: true },
{ name: 'value', type: 'uint256', indexed: false }
]
}],
data: log.data,
topics: log.topics
});
过滤事件日志:
// 获取特定地址的 Transfer 事件
const filter = {
address: contractAddress,
topics: [
ethers.utils.id("Transfer(address,address,uint256)"),
null, // from (任意)
ethers.utils.hexZeroPad(myAddress, 32) // to (我的地址)
],
fromBlock: 'latest',
toBlock: 'latest'
};
const logs = await provider.getLogs(filter);
ABI 编码变体
abi.encode vs abi.encodePacked
标准编码(abi.encode):
// 每个参数占 32 字节
bytes memory encoded = abi.encode(uint8(1), uint16(2));
结果:
0x0000000000000000000000000000000000000000000000000000000000000001 // uint8(1) - 32字节
0000000000000000000000000000000000000000000000000000000000000002 // uint16(2) - 32字节
长度:64 字节
紧密编码(abi.encodePacked):
// 最小字节表示,无填充
bytes memory packed = abi.encodePacked(uint8(1), uint16(2));
结果:
0x010002 // uint8(1) - 1字节,uint16(2) - 2字节
长度:3 字节
警告:
- 不可逆解码(丢失类型信息)
- 可能导致哈希碰撞
安全问题示例:
// ❌ 危险:哈希碰撞
bytes32 hash1 = keccak256(abi.encodePacked("a", "bc"));
bytes32 hash2 = keccak256(abi.encodePacked("ab", "c"));
// hash1 == hash2 ✅ 碰撞!
// ✅ 安全:使用标准编码
bytes32 hash1 = keccak256(abi.encode("a", "bc"));
bytes32 hash2 = keccak256(abi.encode("ab", "c"));
// hash1 != hash2 ✅ 不同
abi.encodeWithSelector vs abi.encodeWithSignature
使用选择器:
bytes4 selector = bytes4(keccak256("transfer(address,uint256)"));
bytes memory data = abi.encodeWithSelector(
selector,
0x1234567890123456789012345678901234567890,
100
);
使用签名字符串:
bytes memory data = abi.encodeWithSignature(
"transfer(address,uint256)",
0x1234567890123456789012345678901234567890,
100
);
// 等价于 encodeWithSelector,但在运行时计算选择器
// Gas 成本更高
类型安全版本(Solidity 0.8.13+):
interface IERC20 {
function transfer(address to, uint256 amount) external returns (bool);
}
// ✅ 类型安全,编译时检查
bytes memory data = abi.encodeCall(
IERC20.transfer,
(0x1234567890123456789012345678901234567890, 100)
);
// 优点:
// - 类型检查
// - IDE 自动补全
// - 防止拼写错误
工具与库
Web3 库支持
const { ethers } = require("ethers");
// 1. 创建接口
const iface = new ethers.utils.Interface([
"function transfer(address to, uint256 amount)",
"event Transfer(address indexed from, address indexed to, uint256 value)"
]);
// 2. 编码函数调用
const calldata = iface.encodeFunctionData("transfer", [
"0x1234567890123456789012345678901234567890",
ethers.utils.parseEther("1.0")
]);
// 3. 解码函数调用
const decoded = iface.decodeFunctionData("transfer", calldata);
// 4. 编码函数结果
const result = iface.encodeFunctionResult("transfer", [true]);
// 5. 解码函数结果
const [success] = iface.decodeFunctionResult("transfer", result);
// 6. 解析事件
const log = {
topics: [...],
data: "0x..."
};
const event = iface.parseLog(log);
// 7. 编码事件过滤器
const filter = iface.encodeFilterTopics("Transfer", [
"0xaaa...", // from
null // to (任意)
]);
viem:
import {
encodeFunctionData,
decodeFunctionData,
encodeFunctionResult,
decodeFunctionResult,
encodeEventTopics,
decodeEventLog
} from 'viem';
// 编码函数
const data = encodeFunctionData({
abi: [...],
functionName: 'transfer',
args: ['0x...', 100n]
});
// 解码函数
const result = decodeFunctionData({
abi: [...],
data: '0x...'
});
// 编码事件主题
const topics = encodeEventTopics({
abi: [...],
eventName: 'Transfer',
args: {
from: '0x...',
to: null // 通配符
}
});
// 解码事件
const event = decodeEventLog({
abi: [...],
data: log.data,
topics: log.topics
});
web3.py:
from web3 import Web3
# 编码函数调用
encoded = contract.encode_abi(
fn_name='transfer',
args=['0x1234567890123456789012345678901234567890', 100]
)
# 解码函数调用
decoded = contract.decode_function_input(encoded)
# 解析事件
event = contract.events.Transfer().processLog(log)
在线工具
ABI 编解码工具:
1. ChainTool
- URL: https://chaintool.tech/calldata
- 功能:Calldata 编解码、可视化
2. HashEx ABI Decoder
- URL: https://abi.hashex.org/
- 功能:解码交易输入数据
3. OpenChain
- URL: https://openchain.xyz/tools/abi
- 功能:ABI 编解码、调用堆栈分析
4. Ethereum Signature Database
- URL: https://www.4byte.directory/
- 功能:函数签名查询
5. Samczsun's Calldata Decoder
- URL: https://calldata.swiss-knife.xyz/
- 功能:高级 calldata 解码
函数选择器查询:
ChainTool querySelector:
https://chaintool.tech/querySelector
输入:函数签名
输出:4字节选择器
示例:
输入:transfer(address,uint256)
输出:0xa9059cbb
最佳实践
类型安全
// ❌ 不安全:字符串拼接,易出错
bytes memory data = abi.encodeWithSignature(
"tranfer(address,uint256)", // 拼写错误!
to,
amount
);
// ✅ 安全:使用接口,编译时检查
bytes memory data = abi.encodeCall(
IERC20.transfer,
(to, amount)
);
Gas 优化
// ❌ 高 Gas:运行时计算选择器
function badCall(address token, address to, uint256 amount) external {
bytes memory data = abi.encodeWithSignature(
"transfer(address,uint256)",
to,
amount
);
token.call(data);
}
// ✅ 低 Gas:预计算选择器
bytes4 constant TRANSFER_SELECTOR = bytes4(keccak256("transfer(address,uint256)"));
function goodCall(address token, address to, uint256 amount) external {
bytes memory data = abi.encodeWithSelector(
TRANSFER_SELECTOR,
to,
amount
);
token.call(data);
}
// ✅✅ 最优:直接调用
function bestCall(address token, address to, uint256 amount) external {
IERC20(token).transfer(to, amount);
}
安全注意事项
1. 验证解码数据:
function processCalldata(bytes calldata data) external {
// ✅ 验证长度
require(data.length >= 4, "Invalid calldata");
// ✅ 验证选择器
bytes4 selector = bytes4(data[0:4]);
require(
selector == this.expectedFunction.selector,
"Wrong function"
);
// ✅ 解码并验证参数
(address to, uint256 amount) = abi.decode(data[4:], (address, uint256));
require(to != address(0), "Invalid address");
require(amount > 0, "Invalid amount");
}
2. 避免 encodePacked 的哈希碰撞:
// ❌ 危险
function dangerousHash(string memory a, string memory b)
public
pure
returns (bytes32)
{
return keccak256(abi.encodePacked(a, b));
}
// ✅ 安全
function safeHash(string memory a, string memory b)
public
pure
returns (bytes32)
{
return keccak256(abi.encode(a, b));
}
// ✅ 或使用分隔符
function safeHashWithSeparator(string memory a, string memory b)
public
pure
returns (bytes32)
{
return keccak256(abi.encodePacked(a, "|", b));
}
推荐阅读
- Solidity ABI Specification - 官方 ABI 规范
- Ethereum Contract ABI - ABI 规范中文版
- ethers.js Documentation - ethers.js ABI 编码文档
- viem Documentation - viem ABI 工具文档
- EIP-712: Typed structured data hashing - 结构化数据签名
- 4byte Directory - 函数签名数据库
- OpenChain ABI Tools - ABI 工具集
相关概念
- Calldata:交易输入数据
- Function Selector:函数选择器(前4字节)
- Event Signature:事件签名(32字节)
- Topics:事件日志的索引字段
- Bytecode:合约字节码
- Interface:合约接口定义
- EIP-712:结构化数据签名标准
- Type Hashing:类型哈希