ERC2612 Link to heading
ERC20-授权问题 Link to heading
- 授权、转账需要两笔交易
- 如果要优化体验,无线授权给 Bank , 可能带来安全问题
能否在离线签名进行授权、转账验证签名
approve + transferFrom => signatures + transferFrom
ERC20-Permit(EIP2612) Link to heading
- 签名授权
- (授权)可以在线下签名进行,签名信息可以在执行接收转账交易时提交到链上(permit),让授权和转账在一笔交易 里完成。
- 同时转账交易也可以由接收方(或其他第三方)来提交,也避免了用户(ERC20的拥有者)需要有 ETH的依赖。
应用中使用签名要注意 Link to heading
• 签名被重用
• 如何防止重用,签名中加入
• Nonce
• ChainID
• Deadline (可选,但推荐)
• 业务逻辑 + 授权(amount:, spender)
Example Link to heading
TokenBank Link to heading
// SPDX-License-Identifier: SEE LICENSE IN LICENSE
pragma solidity ^0.8.0;
import "./myToken.sol";
contract TokenBank {
// 记录每个地址在银行中的余额
mapping(address => uint256) public balances;
// ERC20代币合约地址
MyToken public token;
// 委托授权结构体
struct DelegateAuthorization {
address owner; // 代币所有者
address delegate; // 被委托人
uint256 amount; // 授权金额
uint256 deadline; // 授权截止时间
bool used; // 是否已使用
}
// 委托授权映射:授权哈希 => 授权信息
mapping(bytes32 => DelegateAuthorization) public delegateAuthorizations;
// 事件
event Deposit(address indexed user, uint256 amount);
event Withdraw(address indexed user, uint256 amount);
event DelegateAuthorizationCreated(address indexed owner, address indexed delegate, uint256 amount, uint256 deadline, bytes32 authHash);
event DelegateDepositExecuted(address indexed owner, address indexed delegate, uint256 amount, bytes32 authHash);
// 构造函数,设置要管理的ERC20代币
constructor(address _tokenAddress, address _permit2Address) {
token = MyToken(_tokenAddress);
permit2 = IPermit2(_permit2Address);
}
// 存款函数
function deposit(uint256 _amount) external {
require(_amount > 0, "Amount must be greater than 0");
require(token.balanceOf(msg.sender) >= _amount, "Insufficient token balance");
// 从用户账户转移代币到合约
require(token.transferFrom(msg.sender, address(this), _amount), "Transfer failed");
// 更新用户在银行的余额
balances[msg.sender] += _amount;
emit Deposit(msg.sender, _amount);
}
// 取款函数
function withdraw(uint256 _amount) external {
require(_amount > 0, "Amount must be greater than 0");
require(balances[msg.sender] >= _amount, "Insufficient bank balance");
// 更新用户在银行的余额
balances[msg.sender] -= _amount;
// 从合约转移代币到用户账户
require(token.transfer(msg.sender, _amount), "Transfer failed");
emit Withdraw(msg.sender, _amount);
}
// 执行委托授权存款:被委托人使用授权人的离线签名执行存款
function delegateDepositWithPermit(
address owner, // 代币所有者(授权人)
address delegate, // 被委托人地址
uint256 amount, // 授权金额
uint256 deadline, // 授权截止时间
uint8 v, // permit签名参数 v
bytes32 r, // permit签名参数 r
bytes32 s // permit签名参数 s
) external {
require(owner != address(0), "Invalid owner address");
require(delegate == msg.sender, "Only delegate can execute");
require(amount > 0, "Amount must be greater than 0");
require(deadline > block.timestamp, "Deadline must be in the future");
require(token.balanceOf(owner) >= amount, "Owner has insufficient tokens");
// 生成授权哈希(用于防重放攻击)
bytes32 authHash = keccak256(abi.encodePacked(
owner,
delegate,
amount,
deadline,
block.timestamp
));
// 检查授权是否已被使用
require(!delegateAuthorizations[authHash].used, "Authorization already used");
// 执行 permit 授权:通过离线签名授权 TokenBank 转移 owner 的代币
token.permit(
owner, // 代币所有者
address(this), // 被授权方(TokenBank 合约)
amount, // 授权金额
deadline, // 签名有效期
v, r, s // 签名参数
);
// 从 owner 转移代币到合约
bool success = token.transferFrom(owner, address(this), amount);
require(success, "Transfer failed");
// 更新 owner 在银行的存款余额
balances[owner] += amount;
// 保存授权信息防止重放攻击
delegateAuthorizations[authHash] = DelegateAuthorization({
owner: owner,
delegate: delegate,
amount: amount,
deadline: deadline,
used: true
});
emit DelegateAuthorizationCreated(owner, delegate, amount, deadline, authHash);
emit DelegateDepositExecuted(owner, delegate, amount, authHash);
emit Deposit(owner, amount);
}
}