跳到正文

为什么有些代币授权必须先归零?

有些 ERC-20 代币拒绝将一个非零授权额度直接改为另一个非零值。本文说明何时必须先归零、两笔交易为何要依次确认,以及仍然存在的风险。

更新于

仅供教育参考,不构成投资建议;投资可能产生损失。

直接答案

有些 ERC-20 实现会在现有授权额度和 newAmount 都非零时拒绝 approve(spender, newAmount)。对于这类代币,应先提交 approve(spender, 0) 并等待确认,然后才能提交新的非零授权。

并非所有 ERC-20 代币都必须这样做。ERC-20 将 approve 定义为覆盖当前额度,并建议客户端界面先归零,以缓解修改授权时的竞态;但为了兼容性,标准同时说明代币合约本身不应强制这一流程。部分已部署代币仍然实施了该限制。因此,先归零既是兼容性步骤,也是一个有用的检查点,但不能保证旧额度在归零交易确认前不会被使用。

为什么有些代币授权必须先归零?
0 / 5
0 项已核查; 5 项仍未解决

完成核查不代表资产、交易或系统已经安全。

工作原理

  1. 核对链、代币合约、所有者、支出方和预期金额。直接从代币合约读取 allowance(owner, spender),不要只相信钱包显示的标签。
  2. 如果额度已经是 0,只需提交一次目标授权。如果额度非零,直接替换为另一个非零值可能在标准实现上成功,也可能在要求先归零的代币上回滚。
  3. 采用先归零流程时,提交 approve(spender, 0) 并等待成功回执。随后再次读取同一个所有者与支出方的额度,确认它是 0
  4. 重新检查代币余额、支出方和授权用途。只有确认无误后,才提交 approve(spender, newAmount),并等待确认后再把新额度视为生效。
  5. 核验最终额度,并检查期间发生的 TransferApproval 事件。成功的交易回执证明调用已执行,而当前合约状态才显示尚存的权限。

OpenZeppelin 的 SafeERC20.forceApprove 为合约实现了兼容性回退:它先尝试目标值;如果调用失败,则依次尝试 0 和目标值。这个辅助函数修改的是调用合约自身的授权额度,不会自动修复用户钱包的授权,也不能取代对交易顺序和最终状态的核验。

示例

某所有者给支出方的额度为 1000 枚代币,希望降到 100。对于要求先归零的代币,直接调用 approve(spender, 100) 会回滚,因此链上额度仍是 1000;回滚的调用不会只完成部分状态更新。

所有者改为提交 approve(spender, 0)。在这笔交易确认前,支出方使用了 400,额度剩下 600;随后确认的归零交易把剩余额度替换为 0。所有者检查减少后的代币余额,再决定是否授予新的 100。如果授予,支出方已经使用 400,之后还可最多使用 100。先归零让所有者在新权限授出前发现了中途支出,但不会撤销这笔支出。

风险

  • 旧额度在归零交易执行前仍可使用。支出方可能抢在待处理的撤销或降额交易前支出。
  • 如果不等待第一笔确认就同时广播归零与替换交易,预期的检查点就会消失,也可能掩盖中途发生的支出。
  • 链、代币地址或支出方地址错误,会创建或撤销另一项权限。代币符号不是唯一标识。
  • 必须分两步时会产生两笔交易成本,而且任一笔都可能失败、被替换或长期待处理。不能仅凭已提交签名推断链上状态。
  • 无限授权以及可升级或已被攻破的支出方可能危及未来存入的代币。应使用实际可行的最小额度,并在用完后核对剩余额度。
  • 合约集成必须明确处理非标准返回值和授权行为。兼容性封装无法让不可信的支出方变得安全。

常见误区

  • 每种 ERC-20 都要求先归零。 标准建议客户端采用这一顺序,但说明代币合约不应强制执行;只有部分实现拒绝从非零值直接改为非零值。
  • 先归零能彻底解决授权竞态。 支出方仍可在归零交易确认前使用旧额度。
  • 替换授权回滚后,旧额度就被清除了。 回滚会撤销本次尝试的状态变更,因此原额度通常仍然存在。
  • 同时发送两笔交易等同于等待确认。 安全检查点来自确认并检查零额度状态,然后再决定是否提交替换授权。
  • 断开网站连接就会撤销授权。 钱包连接状态与代币合约中的链上授权额度彼此独立。

相关主题

来源

导航

搜索知识库...