1. ERC20-permit
ERC20-permit 是 ERC20 代币标准的一个扩展,正式定义在 EIP-2612 中。它允许代币持有者通过链下签名的方式授权他人使用自己的代币,而无需发送链上的 approve 交易。这一机制极大地改善了用户体验,特别是在 DeFi 等需要频繁授权的场景中。
传统 ERC20 的问题
在标准 ERC20 中,如果 Alice 想授权 Bob 转移她的代币(例如在 Uniswap 上交易),她必须:
这个过程繁琐且需要用户持有 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,代币合约需要:
核心数据结构(EIP-712 类型哈希)
bytes32 public constant PERMIT_TYPEHASH = keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
简化的 permit 实现(基于 OpenZeppelin)
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 事件,便于链下监控。
// 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);
}
}
主要应用场景
实现方式
- 使用 OpenZeppelin 库:最简单的方式是继承 ERC20Pausable 合约(它本身继承自 ERC20 和 Pausable),并添加自己的逻辑。
- 自定义实现:可以在 ERC20 的基础上手动添加暂停逻辑,但需注意安全性和权限控制,建议使用经过审计的标准库。
注意事项
- 中心化风险:暂停权限通常由单一地址或多签控制,若私钥泄露或被恶意使用,可能导致资金被锁或滥用。通常建议使用多签钱包或时间锁来管理权限。
- 功能影响:暂停不仅阻止用户转账,也可能影响依赖代币流动性的其他合约(如 DEX、借贷协议),需提前告知用户。
- 透明性:暂停操作应通过事件公开,并在前端或社群中及时公告。
- 不可逆性:一旦暂停,用户无法主动解冻,必须依赖拥有者调用 unpause,因此需要设计合理的恢复机制。
总结
ERC20Pausable 为代币合约增加了紧急暂停的能力,是一种简单有效的安全措施。虽然它引入了中心化控制点,但在 DeFi 和复杂应用中已成为常见的安全模块,帮助项目方在极端情况下保护用户资产。

