关键词:ERC20、approve、allowance、transferFrom、双重授权攻击、SafeERC20、permit、授权流程图
📚 作者:Henry 🧱 系列:《ERC 系列标准全景图解》 · 第 3 篇 👨💻 受众:Web3 前端工程师 / 区块链开发者 / Web3入门者 👉 系列持续更新中,建议收藏专栏或关注作者
ERC-20 是最常见的智能合约接口,但它的授权流程却常出错、易被滥用,甚至是攻击目标。本篇将系统讲透 ERC-20 的授权机制与背后的漏洞根源,并提供工程实战建议与测试策略。
📘 标准接口回顾:
function approve(address spender, uint256 amount) external returns (bool);
function allowance(address owner, address spender) external view returns (uint256);
function transferFrom(address from, address to, uint256 amount) external returns (bool);
这个组合形成了owner → spender 的授权花费机制,用于 DeFi 交互、合约自动扣款等。
approve(spender, amount)
授权某个地址可代为操作;allowance(owner, spender)
映射中;spender
可随时调用 transferFrom
消耗额度。⚠️ 安全隐患:Approve 重复授权攻击(Race Condition)
在未重置 allowance
为 0 的情况下,直接修改授权值存在竞争风险:
// ⚠️ 危险做法:
approve(spender, 100) → approve(spender, 200)
如果 spender
在两次 approve
之间抢先使用旧额度,就可能:
transferFrom(..., 100)
;200
,最终获得 300 token 的使用权!以 OpenZeppelin 的实现为例:
function transferFrom(address from, address to, uint256 value) public override returns (bool) {
uint256 currentAllowance = allowance(from, _msgSender());
require(currentAllowance >= value, "ERC20: insufficient allowance");
_approve(from, _msgSender(), currentAllowance - value);
_transfer(from, to, value);
return true;
}
这是 ERC-20 最臭名昭著的问题之一
approve(spender, 50)
approve
成功后,用户以为授权变成 50,实则余额已空ERC-20 中 approve(spender, amount)
是覆盖而不是累加
OpenZeppelin 提供了推荐的做法:
token.approve(spender, 0);
token.approve(spender, 1000);
⚠️ 缺点:需要两次交易,gas 成本翻倍
increaseAllowance
/ decreaseAllowance
OpenZeppelin 在 ERC20 中实现了以下辅助方法:
function increaseAllowance(address spender, uint256 addedValue) public returns (bool)
function decreaseAllowance(address spender, uint256 subtractedValue) public returns (bool)
优势:
问题描述 | 原因 |
---|---|
approve(...) 返回值不是 bool? |
某些代币如 USDT 非标准实现 |
transferFrom(...) revert |
allowance 不足或未设置 |
重复授权失败 | 钱包要求先 approve 0 |
授权额度足够但交易失败 | spender 地址错误 or 合约未处理 transferFrom |
OpenZeppelin 提供 SafeERC20
库来封装授权调用:
using SafeERC20 for IERC20;
IERC20(token).safeApprove(spender, amount);
IERC20(token).safeTransferFrom(from, to, amount);
优点:
场景 | 推荐做法 |
---|---|
用户授权界面(前端) | 提供 “Approve Max” / “Revoke” 按钮 |
合约集成第三方 token | 永远使用 SafeERC20 封装 |
重复调用 approve | 先 approve(0),再 approve(N) |
项目中统一授权逻辑 | 写一个 ERC20ApprovalManager 组件或合约模块 |
测试授权边界 | 使用 hardhat/foundry 测试 fuzz approve 行为 |
approve + transferFrom + allowance
increaseAllowance
/ SafeERC20
来提升安全性与兼容性如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!