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);
    }

}