本文适合以太坊工程师、SDK 开发者、Web3 技术学习者阅读。
目标是从标准 → 生态 → 实践 → SDK 设计的维度,全面理解 ERC-20。
1. 为什么需要 ERC-20?
ERC-20 是 EVM 生态中最重要的智能合约标准之一。它提供了一个统一的接口来表示可替代资产(Fungible Token),让所有钱包、交易所、DApp 都能以相同方式与代币交互。
ERC-20 的典型用途包括:
① 支付
- DApp 内付费
- 协议手续费
- 游戏通证
② 资产表示
- 稳定币(USDC、USDT)
- 项目代币(治理/激励)
- 跨链映射资产
- 协议 LP Token / 借贷凭证
③ 权限控制(Access Control)
- 代币持仓解锁权限
- Staking 质押
- DAO 投票权
ERC-20 是 Web3 价值流转的最小单位。
2. ERC-20 标准接口(EIP-20)
ERC-20 的规范来自 EIP-20,定义了每个代币必须实现的最小接口。
2.1 读取方法
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function allowance(address owner, address spender) external view returns (uint256);
2.2 写入方法
function transfer(address to, uint256 amount) external returns (bool);
function approve(address spender, uint256 amount) external returns (bool);
function transferFrom(address from, address to, uint256 amount) external returns (bool);
2.3 事件(非常关键)
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
事件在实际应用中具有极高重要性:
- 浏览器需要事件解析转账记录
- SDK 和 indexer 靠日志更新余额
- 多数前端依赖事件来判定交易结果
没有事件的 ERC-20 难以在生态中正常工作。
3. 现实世界中“不完全标准”的 ERC-20
虽然有 EIP-20,但现实情况是:
许多代币(包括主流代币)并不完全符合标准。
例如:
- USDT:transfer / approve 不返回 bool
- 手续费代币:转账会自动扣手续费
- rebasing token:余额会动态变化
- 权限代币:可被冻结 / 黑名单
- shares 模型:balanceOf 返回的是 share,而不是实际用户余额
3.1 常见的不标准行为分类
| 🎭 类别 | 描述 | 示例 |
|---|---|---|
| no-return token | transfer 不返回 bool | USDT |
| revert-without-reason | 错误时不返回 error message | 多数老代币 |
| fee-on-transfer | 转账会扣手续费 | SafeMoon 类 |
| rebasing token | 余额动态改变 | AMPL, aToken |
| shares token | balanceOf 返回 share | cToken |
| blacklist/pause | 管理者可冻结 | USDC(早期版本) |
| gas 优化行为 | 无 revert message、行为非标 | 若干旧代币 |
这是为什么很多 SDK 或协议并不能 100% 兼容所有 ERC-20。
4. SafeERC20:生态兼容问题的解决方案
OpenZeppelin 的 SafeERC20 是目前合约中唯一能安全处理所有“不标准 ERC-20”的方式。
其核心逻辑如下:
function _callOptionalReturn(IERC20 token, bytes memory data) private {
(bool success, bytes memory returndata) = address(token).call(data);
require(success, "ERC20: low-level call failed");
// 如果代币有返回值,则必须是 true
if (returndata.length > 0) {
require(abi.decode(returndata, (bool)), "ERC20: operation did not succeed");
}
}
SafeERC20 能解决:
✔ 代币不返回 bool
→ 按成功处理
✔ 代币返回 false
→ revert
✔ 可捕获各种不标准行为
→ 统一接口、降低错误率
SafeERC20 是智能合约层的事实标准。
5. ERC-20 的常见扩展标准
ERC-20 本身非常基础,但生态上已经出现许多扩展:
① EIP-2612 permit(最关键)
允许用户用签名授权,无需链上 approve。
用途:
- Gasless approve
- 改善用户体验
- DEX swap 前不用额外发一次 approve
② EIP-3009 transferWithAuthorization
用于 meta-transaction 风格的“凭证转账”。
USDC、USDT 普遍支持。
③ EIP-20 Metadata(名称/符号/精度)
function name() external view returns (string);
function symbol() external view returns (string);
function decimals() external view returns (uint8);
6. ERC-20 的设计缺陷
ERC-20 在设计上存在一些长期累积的问题:
1. approve 的竞态漏洞(race condition)
必须先将 allowance 设置为 0,再设置新值。
2. transferFrom 与 fee-on-transfer 代币不兼容
用户允许 100,但只收到 95。
3. 无批量转账 / 无批量 allowance
后来才出现 permit2 与 multicall。
4. 无授权过期机制
permit 才解决了这个问题。
因此 ERC-20 在 DeFi 时代暴露出明显不足。
7. SDK / 客户端应如何正确处理 ERC-20?
✔ 1. 不依赖 transfer / approve 的返回值
因为许多代币不返回 bool。
✔ 2. 使用事件(logs)判断行为
✔ 3. 允许 fee-on-transfer / shares 模型的行为差异
✔ 4. 支持 permit(提升用户体验)
✔ 5. 支持自定义 decimals,而不是假设是 18
✔ 6. multicall 时要考虑 no-return token
✔ 7. 提供 fallback 的 ABI decode 逻辑
8. 总结
ERC-20 虽然看似简单,但完整理解这个标准需要掌握:
-
标准接口(EIP-20)
-
现实世界的偏差(七大不标准类型)
-
SafeERC20 的解决方案
-
扩展标准(permit、EIP-3009 等)
-
SDK 和协议如何容错与兼容