1. ERC20-permit
ERC20-permit 是 ERC20 代币标准的一个扩展,正式定义在 EIP-2612 中。它允许代币持有者通过链下签名 的方式授权他人使用自己的代币,而无需发送链上的 approve 交易。这一机制极大地改善了用户体验,特别是在 DeFi 等需要频繁授权的场景中。
传统 ERC20 的问题
在标准 ERC20 中,如果 Alice 想授权 Bob 转移她的代币(例如在 Uniswap 上交易),她必须:
- 发送一笔
approve交易,花费 gas。 - 等待交易确认。
- 然后 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,代币合约需要:
- 包含
permit函数,实现签名验证逻辑。 - 维护每个地址的
nonces,防止重放攻击。 - 计算
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);
}
}
主要应用场景
- 安全应急
当发现合约漏洞或遭遇黑客攻击时,拥有者可立即暂停所有转账,防止资金进一步流失,为修复争取时间。 - 监管合规
在需要遵守法规的场景下(如证券型代币),可在特定时期暂停交易,配合法律要求。 - 升级过渡
在合约升级期间,暂停代币转移可以避免状态不一致或数据损坏。 - 市场极端情况
例如稳定币脱钩时,项目方可能临时暂停交易以稳定市场(需谨慎使用)。
实现方式
- 使用 OpenZeppelin 库 :最简单的方式是继承
ERC20Pausable合约(它本身继承自ERC20和Pausable),并添加自己的逻辑。 - 自定义实现:可以在 ERC20 的基础上手动添加暂停逻辑,但需注意安全性和权限控制,建议使用经过审计的标准库。
注意事项
- 中心化风险:暂停权限通常由单一地址或多签控制,若私钥泄露或被恶意使用,可能导致资金被锁或滥用。通常建议使用多签钱包或时间锁来管理权限。
- 功能影响:暂停不仅阻止用户转账,也可能影响依赖代币流动性的其他合约(如 DEX、借贷协议),需提前告知用户。
- 透明性:暂停操作应通过事件公开,并在前端或社群中及时公告。
- 不可逆性 :一旦暂停,用户无法主动解冻,必须依赖拥有者调用
unpause,因此需要设计合理的恢复机制。
总结
ERC20Pausable 为代币合约增加了紧急暂停的能力,是一种简单有效的安全措施。虽然它引入了中心化控制点,但在 DeFi 和复杂应用中已成为常见的安全模块,帮助项目方在极端情况下保护用户资产。