从零开始的OpenZeppelin学习 2| ERC20-permit、erc20pausable

1. ERC20-permit

ERC20-permit 是 ERC20 代币标准的一个扩展,正式定义在 EIP-2612 中。它允许代币持有者通过链下签名 的方式授权他人使用自己的代币,而无需发送链上的 approve 交易。这一机制极大地改善了用户体验,特别是在 DeFi 等需要频繁授权的场景中。

传统 ERC20 的问题

在标准 ERC20 中,如果 Alice 想授权 Bob 转移她的代币(例如在 Uniswap 上交易),她必须:

  1. 发送一笔 approve 交易,花费 gas。
  2. 等待交易确认。
  3. 然后 Bob 才能调用 transferFrom 转走代币。

这个过程繁琐且需要用户持有 ETH 支付 gas,对于新用户尤其不友好。

Permit 的工作原理

Permit 引入了 permit 函数,该函数接受以下参数:

  • owner:代币持有者。
  • spender:被授权者。
  • value:授权额度。
  • deadline:签名过期时间。
  • v, r, s:由 owner 使用其私钥对授权信息进行签名的结果。

任何地址(通常是 relayer 或合约)都可以调用 permit,传入签名和授权信息。合约会验证签名是否有效且未过期,然后直接设置 owner 对 spender 的授权额度。这样,授权步骤就和后续的 transferFrom 可以在同一笔交易中完成,甚至可以将授权完全外包给第三方(元交易)。

主要优点

  • Gas 效率 :用户只需签名,无需支付 gas(由调用 permit 的人支付)。
  • 单笔交易 :可以将 permit 和 transferFrom 合并调用,实现"授权并转账"的一步操作。
  • 元交易支持:应用可以代用户提交交易,实现无 gas 体验。
  • 安全 :通过 nonce 和 deadline 防止签名重放。

实现细节

要实现 ERC20-permit,代币合约需要:

  1. 包含 permit 函数,实现签名验证逻辑。
  2. 维护每个地址的 nonces,防止重放攻击。
  3. 计算 DOMAIN_SEPARATOR(根据 EIP-712 标准),确保签名在不同合约或链之间不会混淆。

核心数据结构(EIP-712 类型哈希)

solidity 复制代码
bytes32 public constant PERMIT_TYPEHASH = keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");

简化的 permit 实现(基于 OpenZeppelin)

solidity 复制代码
function permit(
    address owner,
    address spender,
    uint256 value,
    uint256 deadline,
    uint8 v,
    bytes32 r,
    bytes32 s
) external {
    require(block.timestamp <= deadline, "Permit: expired deadline");
    bytes32 structHash = keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, _useNonce(owner), deadline));
    bytes32 hash = _hashTypedDataV4(structHash);
    address signer = ECDSA.recover(hash, v, r, s);
    require(signer == owner, "Permit: invalid signature");
    _approve(owner, spender, value);
}

与 OpenZeppelin 集成

OpenZeppelin 提供了 ERC20Permit 合约,只需继承即可快速获得 permit 功能,同时兼容 EIP-2612。

典型使用场景

  • 去中心化交易所:用户只需签名一次即可完成代币兑换。
  • 流动性挖矿:用户签名授权,合约自动提取质押代币。
  • 批量空投:项目方可以使用 permit 让用户授权后一次转走多份代币。
  • 钱包/浏览器扩展:如 MetaMask 已支持 EIP-2612 签名,可直接生成 permit 签名。

注意事项

  • 签名必须包含 deadline,以防止签名被无限期使用。
  • 必须维护 nonce,每个签名只能使用一次。
  • 验证签名时需根据 EIP-712 构造正确的哈希,包括 DOMAIN_SEPARATOR。
  • 调用 permit 的地址(可能是攻击者)无法改变授权额度,只能按照签名执行。

总结

ERC20-permit 通过引入链下签名机制,将授权与代币转移解耦,让用户能更灵活地管理资产,同时降低链上交互成本。它已成为现代 DeFi 协议的事实标准,并被广泛采用(如 USDC、DAI、UNI 等代币均已支持)。

2. erc20pausable

ERC20Pausable 是 ERC20 代币标准的一个扩展,通常由 OpenZeppelin 的合约库实现。它允许合约拥有者在紧急情况下暂停 所有代币的转移操作(包括 transfer、transferFrom、mint、burn 等),从而为合约提供了一层安全熔断机制。

核心工作原理

  • 状态变量 :合约维护一个布尔值 paused,表示当前是否暂停。
  • 修饰符 :使用 whenNotPaused 修饰符检查 paused 是否为 false,若为 true 则交易回滚。
  • 控制函数 :只有具有特定角色(通常是合约拥有者)的地址可以调用 pause() 和 unpause() 来切换暂停状态。
  • 事件 :暂停或取消暂停时会触发 Paused 和 Unpaused 事件,便于链下监控。
solidity 复制代码
// OpenZeppelin 的简化示例
contract ERC20Pausable is ERC20, Pausable {
    function pause() public onlyOwner {
        _pause();
    }

    function unpause() public onlyOwner {
        _unpause();
    }

    function _beforeTokenTransfer(address from, address to, uint256 amount)
        internal
        whenNotPaused
        override
    {
        super._beforeTokenTransfer(from, to, amount);
    }
}

主要应用场景

  1. 安全应急
    当发现合约漏洞或遭遇黑客攻击时,拥有者可立即暂停所有转账,防止资金进一步流失,为修复争取时间。
  2. 监管合规
    在需要遵守法规的场景下(如证券型代币),可在特定时期暂停交易,配合法律要求。
  3. 升级过渡
    在合约升级期间,暂停代币转移可以避免状态不一致或数据损坏。
  4. 市场极端情况
    例如稳定币脱钩时,项目方可能临时暂停交易以稳定市场(需谨慎使用)。

实现方式

  • 使用 OpenZeppelin 库 :最简单的方式是继承 ERC20Pausable 合约(它本身继承自 ERC20 和 Pausable),并添加自己的逻辑。
  • 自定义实现:可以在 ERC20 的基础上手动添加暂停逻辑,但需注意安全性和权限控制,建议使用经过审计的标准库。

注意事项

  • 中心化风险:暂停权限通常由单一地址或多签控制,若私钥泄露或被恶意使用,可能导致资金被锁或滥用。通常建议使用多签钱包或时间锁来管理权限。
  • 功能影响:暂停不仅阻止用户转账,也可能影响依赖代币流动性的其他合约(如 DEX、借贷协议),需提前告知用户。
  • 透明性:暂停操作应通过事件公开,并在前端或社群中及时公告。
  • 不可逆性 :一旦暂停,用户无法主动解冻,必须依赖拥有者调用 unpause,因此需要设计合理的恢复机制。

总结

ERC20Pausable 为代币合约增加了紧急暂停的能力,是一种简单有效的安全措施。虽然它引入了中心化控制点,但在 DeFi 和复杂应用中已成为常见的安全模块,帮助项目方在极端情况下保护用户资产。

相关推荐
该学习了咕咕6 小时前
顶刊论文学习笔记
学习
Linux-lucky7 小时前
45-Linux学习之旅之防火墙与WEB加固
linux·前端·网络·学习·ubuntu
li星野7 小时前
【学习记录】k-Wave 时间步长、CFL 稳定性与参数实验全解析
学习
心祭丫丫10 小时前
【每天一个 Python 小案例17】Python 实现通讯录
开发语言·人工智能·笔记·python·学习
Nerd(BUG版)13 小时前
Flutter学习day5--事件手势与命中测试
学习·flutter
Nerd(BUG版)15 小时前
KMP学习day4--Kotlin空安全与泛型
学习·安全·kotlin
東隅已逝,桑榆非晚15 小时前
算法6. 双指针法-查找总价格为目标的两个商品
c++·学习·算法
wdyan29716 小时前
带着小白学习-微服务-5 实战:配置中心-数据库连接
数据库·学习·微服务
叼烟扛炮16 小时前
Linux 学习笔记(五十):网络层——IP 协议、子网划分与 NAT 魔法
linux·笔记·学习
叼烟扛炮16 小时前
Linux 学习笔记(六十):单线程的逆袭——epoll 高并发服务器实战
linux·服务器·网络·笔记·学习·算法