ERC-4337 與 EIP-7702 實作教學完整指南:智能合約錢包開發、安全設計與最佳實踐

本文從工程師視角出發,提供完整的智能合約錢包實作教學,涵蓋 ERC-4337 與 EIP-7702 的技術實現、程式碼範例、安全設計考量、以及部署最佳實踐。展示如何實現社交恢復、多重簽名、Gas 贊助等進階功能。

ERC-4337 與 EIP-7702 實作教學完整指南:智能合約錢包開發、安全設計與最佳實踐

執行摘要

帳戶抽象(Account Abstraction)是以太坊改善用戶體驗的關鍵技術方向。ERC-4337 作為應用層標準已於 2023 年正式落地採用,而 EIP-7702 作為協議層升級在 Pectra 升級中引入,提供了另一條帳戶抽象路徑。本文從工程師視角出發,提供完整的智能合約錢包實作教學,涵蓋 ERC-4337 與 EIP-7702 的技術實現、程式碼範例、安全設計考量、以及部署最佳實踐。我們將構建一個功能完整的智能合約錢包,展示如何實現社交恢復、多重簽名、Gas 贊助等進階功能。

第一章:帳戶抽象技術架構回顧

1.1 傳統帳戶模型限制

以太坊傳統帳戶模型存在以下限制,這些限制推動了帳戶抽象技術的發展:

外部擁有帳戶(EOA)限制

傳統 EOA 限制:

1. 私鑰管理
   - 私鑰丟失意味著資金永久丟失
   - 無法實現多因素認證
   - 無法設置支出限額

2. 交易簽名
   - 每筆交易都需要用戶簽名
   - 無法定制簽名邏輯
   - 批量操作效率低

3. Gas 支付
   - 必須持有 ETH 才能發起交易
   - 無法由第三方代付 Gas
   - 新用戶上手門檻高

4. 帳戶恢復
   - 無法實現社交恢復
   - 遺產繼承困難
   - 設備丟失風險高

1.2 ERC-4337 與 EIP-7702 架構對比

ERC-4337 架構:

┌─────────────────────────────────────────────────────────────┐
│                      ERC-4337 架構                          │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌──────────────┐    ┌──────────────┐    ┌──────────────┐ │
│  │   DApp       │    │   UserOp     │    │   Bundler   │ │
│  │   (客戶端)    │───▶│  (用戶操作)   │───▶│  (捆綁器)    │ │
│  └──────────────┘    └──────────────┘    └──────────────┘ │
│                                            │              │
│                                            ▼              │
│                                   ┌──────────────┐       │
│                                   │  EntryPoint  │       │
│                                   │   Contract   │       │
│                                   └──────────────┘       │
│                                            │              │
│                                            ▼              │
│  ┌──────────────┐    ┌──────────────┐    ┌──────────────┐ │
│  │  Paymaster   │◀───│   Wallet     │◀───│   Storage    │ │
│  │  Contract    │    │  Contract    │    │   (狀態)      │ │
│  └──────────────┘    └──────────────┘    └──────────────┘ │
│                                                             │
└─────────────────────────────────────────────────────────────┘

EIP-7702 架構:

┌─────────────────────────────────────────────────────────────┐
│                      EIP-7702 架構                          │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌──────────────┐    ┌──────────────┐    ┌──────────────┐ │
│  │   DApp       │    │   Transaction│    │    EVM       │ │
│  │   (客戶端)    │───▶│  (交易)      │───▶│  (執行)      │ │
│  └──────────────┘    └──────────────┘    └──────────────┘ │
│                                            │              │
│                                            ▼              │
│                                   ┌──────────────┐       │
│                                   │  Authorize   │       │
│                                   │   (臨時授權)  │       │
│                                   └──────────────┘       │
│                                            │              │
│                                            ▼              │
│  ┌──────────────┐    ┌──────────────┐    ┌──────────────┐ │
│  │    EOA       │◀───│   合約代碼   │◀───│   Storage    │ │
│  │  (臨時合約化) │    │  (臨時加載)  │    │   (狀態)      │ │
│  └──────────────┘    └──────────────┘    └──────────────┘ │
│                                                             │
└─────────────────────────────────────────────────────────────┘

第二章:ERC-4337 智能合約錢包實作

2.1 EntryPoint 合約接口

ERC-4337 的核心是 EntryPoint 合約,它負責驗證和執行用戶操作。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

/**
 * @title IEntryPoint
 * @dev ERC-4337 EntryPoint 合約接口
 */
interface IEntryPoint {
    
    struct UserOperation {
        address sender;                    // 錢包合約地址
        uint256 nonce;                     // 防重放計數器
        bytes initCode;                   // 初始化代碼(如果需要創建帳戶)
        bytes callData;                   // 調用數據
        uint256 callGasLimit;            // 調用 Gas 限制
        uint256 verificationGasLimit;    // 驗證 Gas 限制
        uint256 preVerificationGas;      // 預驗證 Gas
        uint256 maxFeePerGas;             // 最大 Fee Per Gas
        uint256 maxPriorityFeePerGas;    // 最大優先費用
        bytes paymasterAndData;           // Paymaster 數據
        bytes signature;                  // 用戶簽名
    }
    
    /**
     * @dev 處理單個用戶操作
     * @param op 用戶操作
     * @param beneficiary 受益人地址
     * @return actualGasCost 實際 Gas 成本
     * @return success 是否成功
     */
    function handleOp(
        UserOperation calldata op,
        address payable beneficiary
    ) external returns (uint256 actualGasCost, bool success);
    
    /**
     * @dev 批量處理用戶操作
     * @param ops 用戶操作數組
     * @param beneficiary 受益人地址
     */
    function handleOps(
        UserOperation[] calldata ops,
        address payable beneficiary
    ) external;
    
    /**
     * @dev 模擬用戶操作驗證
     * @param op 用戶操作
     */
    function simulateValidation(
        UserOperation calldata op
    ) external returns (uint256 validationData);
}

2.2 智能合約錢包核心實現

以下是一個完整的 ERC-4337 智能合約錢包實現:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

/**
 * @title ERC4337Wallet
 * @dev 符合 ERC-4337 標準的智能合約錢包
 */
contract ERC4337Wallet is EIP712, ReentrancyGuard {
    using ECDSA for bytes32;
    
    // 錯誤定義
    error InvalidSignature();
    error InvalidNonce();
    error NotAuthorized();
    error ExecutionFailed();
    error InitializerAlreadySet();
    error GuardianAlreadyAdded();
    error InvalidGuardian();
    error NotRecovered();
    
    // 錢包所有者
    address public owner;
    
    // EntryPoint 接口
    IEntryPoint public immutable entryPoint;
    
    // 初始化標記
    bool public initialized;
    
    // Nonce 管理
    mapping(uint256 => uint256) public nonces;
    
    // 金額限制
    mapping(address => uint256) public dailyLimit;
    uint256 public constant DAILY_LIMIT_PERIOD = 24 hours;
    mapping(address => uint256) public lastLimitReset;
    mapping(address => uint256) public spentToday;
    
    // 社交恢復功能
    mapping(address => bool) public guardians;
    uint256 public guardianCount;
    uint256 public guardianThreshold;
    uint256 public pendingNewOwner;
    uint256 public recoveryRequestTime;
    uint256 public constant RECOVERY_DELAY = 7 days;
    
    // 多重簽名
    mapping(bytes32 => Confirmation) public transactionConfirmations;
    mapping(address => bool) public signers;
    uint256 public signerCount;
    uint256 public signerThreshold;
    
    // 事件
    event Initialized(address indexed owner);
    event OwnerChanged(address indexed oldOwner, address indexed newOwner);
    event GuardianAdded(address indexed guardian);
    event GuardianRemoved(address indexed guardian);
    event RecoveryInitiated(address indexed newOwner, uint256 executeAfter);
    event RecoveryExecuted(address indexed newOwner);
    event Execution(
        address indexed to, 
        uint256 value, 
        bytes data, 
        bytes result
    );
    event DailyLimitExceeded(address indexed spender, uint256 amount);
    
    struct Confirmation {
        bool confirmed;
        uint256 timestamp;
    }
    
    /**
     * @dev 錢包初始化
     * @param _owner 初始所有者
     * @param _entryPoint EntryPoint 合約地址
     */
    constructor(IEntryPoint _entryPoint) EIP712("ERC4337Wallet", "1.0.0") {
        require(address(_entryPoint) != address(0), "Invalid EntryPoint");
        entryPoint = _entryPoint;
    }
    
    /**
     * @dev 初始化錢包(可由 EntryPoint 在部署時代調用)
     * @param _owner 初始所有者
     * @param _guardians 初始守護人列表
     * @param _guardianThreshold 守護人閾值
     * @param _signers 多重簽名者列表
     * @param _signerThreshold 簽名者閾值
     */
    function initialize(
        address _owner,
        address[] calldata _guardians,
        uint256 _guardianThreshold,
        address[] calldata _signers,
        uint256 _signerThreshold
    ) external {
        require(!initialized, InitializerAlreadySet());
        
        owner = _owner;
        initialized = true;
        
        // 設置守護人
        guardianCount = _guardians.length;
        guardianThreshold = _guardianThreshold;
        for (uint256 i = 0; i < _guardians.length; i++) {
            guardians[_guardians[i]] = true;
        }
        
        // 設置簽名者
        signerCount = _signers.length;
        signerThreshold = _signerThreshold;
        for (uint256 i = 0; i < _signers.length; i++) {
            signers[_signers[i]] = true;
        }
        
        emit Initialized(_owner);
    }
    
    /**
     * @dev ERC-4337 驗證函數
     * @param userOp 用戶操作
     * @param userOpHash 用戶操作哈希
     * @param missingAccountFunds 缺少的帳戶資金
     * @return validationData 驗證數據
     */
    function validateUserOp(
        UserOperation calldata userOp,
        bytes32 userOpHash,
        uint256 missingAccountFunds
    ) external returns (uint256) {
        require(msg.sender == address(entryPoint), NotAuthorized());
        
        // 驗證簽名
        bytes32 hash = _hashUserOp(userOp, userOpHash);
        
        // 如果提供了簽名者閾值,則需要多重簽名
        if (signerThreshold > 0) {
            _validateMultiSig(userOp.signature, hash);
        } else {
            // 單簽名驗證
            _validateSignature(userOp.signature, hash);
        }
        
        // 驗證 nonce
        if (userOp.nonce != nonces[0]) {
            return _packValidationData(true, 0, 0);
        }
        
        // 更新 nonce
        nonces[0]++;
        
        // 如果缺少資金,從錢包轉入(由 EntryPoint 調用)
        if (missingAccountFunds > 0) {
            payable(msg.sender).transfer(missingAccountFunds);
        }
        
        return _packValidationData(false, 0, 0);
    }
    
    /**
     * @dev 執行用戶操作
     * @param to 目標地址
     * @param value 轉帳金額
     * @param data 調用數據
     */
    function execute(
        address to,
        uint256 value,
        bytes calldata data
    ) external nonReentrant {
        require(msg.sender == owner || signers[msg.sender], NotAuthorized());
        
        // 檢查每日限額
        _checkDailyLimit(value);
        
        (bool success, bytes memory result) = to.call{value: value}(data);
        
        if (!success) {
            if (result.length > 0) {
                assembly {
                    let returndata_size := mload(result)
                    revert(add(32, result), returndata_size)
                }
            } else {
                revert ExecutionFailed();
            }
        }
        
        emit Execution(to, value, data, result);
    }
    
    /**
     * @dev 批量執行多個操作
     * @param ops 操作數組
     */
    function executeBatch(
        Call[] calldata ops
    ) external nonReentrant {
        require(msg.sender == owner || signers[msg.sender], NotAuthorized());
        
        for (uint256 i = 0; i < ops.length; i++) {
            Call calldata op = ops[i];
            
            // 檢查每日限額
            _checkDailyLimit(op.value);
            
            (bool success, ) = op.to.call{value: op.value}(op.data);
            
            if (!success) {
                revert ExecutionFailed();
            }
        }
    }
    
    /**
     * @dev 接收 ETH
     */
    receive() external payable {
        // 任何人都可以向錢包轉帳
    }
    
    /**
     * @dev 內部簽名驗證
     */
    function _validateSignature(
        bytes calldata signature,
        bytes32 hash
    ) internal view {
        address signer = hash.toEthSignedMessageHash().recover(signature);
        
        if (signer != owner) {
            revert InvalidSignature();
        }
    }
    
    /**
     * @dev 多重簽名驗證
     */
    function _validateMultiSig(
        bytes calldata signature,
        bytes32 hash
    ) internal {
        // 簽名格式:每個簽名者 65 字節
        require(
            signature.length % 65 == 0, 
            "Invalid signature length"
        );
        
        uint256 numSignatures = signature.length / 65;
        require(
            numSignatures >= signerThreshold, 
            "Not enough signatures"
        );
        
        // 分割簽名
        bytes32 sighash = hash.toEthSignedMessageHash();
        
        // 驗證每個簽名
        for (uint256 i = 0; i < numSignatures; i++) {
            bytes memory sig = signature[i * 65:(i + 1) * 65];
            address signer = sighash.recover(sig);
            
            if (!signers[signer]) {
                revert InvalidSignature();
            }
        }
    }
    
    /**
     * @dev 每日限額檢查
     */
    function _checkDailyLimit(uint256 value) internal {
        if (lastLimitReset[msg.sender] + DAILY_LIMIT_PERIOD < block.timestamp) {
            // 重置限額
            lastLimitReset[msg.sender] = block.timestamp;
            spentToday[msg.sender] = 0;
        }
        
        if (value > dailyLimit[msg.sender]) {
            revert DailyLimitExceeded(msg.sender, value);
        }
        
        spentToday[msg.sender] += value;
    }
    
    /**
     * @dev 用戶操作哈希
     */
    function _hashUserOp(
        UserOperation calldata userOp,
        bytes32 userOpHash
    ) internal pure returns (bytes32) {
        return keccak256(
            abi.encode(
                userOp.sender,
                userOp.nonce,
                keccak256(userOp.initCode),
                keccak256(userOp.callData),
                userOp.callGasLimit,
                userOp.verificationGasLimit,
                userOp.preVerificationGas,
                userOp.maxFeePerGas,
                userOp.maxPriorityFeePerGas,
                keccak256(userOp.paymasterAndData),
                userOpHash
            )
        );
    }
    
    /**
     * @dev 打包驗證數據
     */
    function _packValidationData(
        bool validationFailed,
        uint256 authorizer,
        uint256 salt
    ) internal pure returns (uint256) {
        return
            (validationFailed ? 1 : 0) |
            (authorizer << 1) |
            (salt << 160);
    }
    
    struct Call {
        address to;
        uint256 value;
        bytes data;
    }
    
    // Fallback 功能
    fallback() external payable {
        // 允許錢包接收 ETH
    }
}

2.3 Paymaster 實現

Paymaster 允許第三方代付 Gas 費用,這對於改善用戶體驗至關重要。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

/**
 * @title TokenPaymaster
 * @dev 支持 ERC-20 代幣支付 Gas 的 Paymaster
 */
contract TokenPaymaster is ReentrancyGuard {
    
    // 錯誤定義
    error InvalidToken();
    error InsufficientTokenBalance();
    error InvalidVerificationData();
    error PostOpFailed();
    
    // 結構定義
    struct SponsorData {
        address token;              // 支付代幣
        uint256 exchangeRate;       // 代幣/ETH 匯率(放大 1e8)
        uint256 premiumRate;        // 溢價率(額外費用百分比)
        uint256 gasLimit;          // 最大 Gas 限制
    }
    
    // 映射:錢包地址 -> 贊助商數據
    mapping(address => SponsorData) public walletSponsors;
    
    // 映射:代幣地址 -> 是否支持
    mapping(address => bool) public supportedTokens;
    
    // 儲備金合約地址
    address public immutable treasury;
    
    // 事件
    event TokenPayment(
        address indexed wallet,
        address indexed token,
        uint256 tokenAmount,
        uint256 ethAmount
    );
    
    /**
     * @dev 構造函數
     * @param _treasury 儲備金地址
     */
    constructor(address _treasury) {
        require(_treasury != address(0), "Invalid treasury");
        treasury = _treasury;
    }
    
    /**
     * @dev 驗證用戶操作
     * @param userOp 用戶操作
     * @param requiredPreFund 所需前期資金
     * @return validationData 驗證數據
     */
    function validatePaymasterUserOp(
        UserOperation calldata userOp,
        bytes32 userOpHash,
        uint256 requiredPreFund
    ) external returns (bytes memory validationData) {
        // 解析 paymasterAndData
        (
            address sponsor,
            address token,
            uint256 exchangeRate,
            uint256 premiumRate
        ) = _parsePaymasterData(userOp.paymasterAndData);
        
        // 驗證贊助商配置
        SponsorData memory sponsorData = walletSponsors[sponsor];
        
        if (sponsorData.token != token) {
            revert InvalidToken();
        }
        
        if (!supportedTokens[token]) {
            revert InvalidToken();
        }
        
        // 計算最大費用
        uint256 maxFee = userOp.maxFeePerGas * (userOp.callGasLimit + 
                                                userOp.verificationGasLimit + 
                                                userOp.preVerificationGas);
        
        // 計算代幣費用
        uint256 tokenFee = (maxFee * sponsorData.exchangeRate) / 1e8;
        tokenFee = tokenFee + (tokenFee * sponsorData.premiumRate) / 100;
        
        // 檢查用戶代幣餘額
        IERC20 tokenContract = IERC20(token);
        uint256 userBalance = tokenContract.balanceOf(userOp.sender);
        
        if (userBalance < tokenFee) {
            revert InsufficientTokenBalance();
        }
        
        // 設置驗證數據
        validationData = abi.encode(
            tokenFee,
            exchangeRate,
            premiumRate
        );
        
        return validationData;
    }
    
    /**
     * @dev Post 執行鉤子
     * @param mode 模式(0: 成功, 1: 失敗)
     * @param context 上下文數據
     * @param actualGasCost 實際 Gas 成本
     */
    function postOp(
        PostOpMode mode,
        bytes calldata context,
        uint256 actualGasCost
    ) external nonReentrant {
        (
            uint256 tokenFee,
            uint256 exchangeRate,
            uint256 premiumRate
        ) = abi.decode(context, (uint256, uint256, uint256));
        
        // 根據實際 Gas 使用調整費用
        uint256 adjustedTokenFee = (actualGasCost * exchangeRate) / 1e8;
        adjustedTokenFee = adjustedTokenFee + 
                          (adjustedTokenFee * premiumRate) / 100;
        
        if (mode == PostOpMode.postOpReverted) {
            // 如果操作失敗,仍然扣除費用
            // 注意:這是一個設計選擇
        }
        
        // 從用戶帳戶轉移代幣
        // 注意:實際實現需要 approve 機制
        // 這裡是簡化版本
    }
    
    /**
     * @dev 添加支持的代幣
     */
    function addSupportedToken(address token) external {
        supportedTokens[token] = true;
    }
    
    /**
     * @dev 設置錢包贊助商
     */
    function setWalletSponsor(
        address wallet,
        address token,
        uint256 exchangeRate,
        uint256 premiumRate,
        uint256 gasLimit
    ) external {
        walletSponsors[wallet] = SponsorData({
            token: token,
            exchangeRate: exchangeRate,
            premiumRate: premiumRate,
            gasLimit: gasLimit
        });
    }
    
    /**
     * @dev 解析 Paymaster 數據
     */
    function _parsePaymasterData(
        bytes calldata data
    ) internal pure returns (
        address sponsor,
        address token,
        uint256 exchangeRate,
        uint256 premiumRate
    ) {
        require(data.length >= 84, "Invalid data length");
        
        sponsor = address(bytes20(data[0:20]));
        token = address(bytes20(data[20:40]));
        exchangeRate = uint256(bytes32(data[40:72]));
        premiumRate = uint256(bytes32(data[72:104]));
    }
    
    enum PostOpMode {
        opMode,        // 成功
        postOpReverted // 失敗
    }
    
    // 接收 ETH
    receive() external payable {}
}

2.4 社交恢復功能實現

社交恢復是智能合約錢包的核心功能之一:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

/**
 * @title SocialRecoveryModule
 * @dev 社交恢復模組
 */
contract SocialRecoveryModule {
    
    // 錯誤定義
    error NotGuardian();
    error InvalidGuardianCount();
    error RecoveryAlreadyPending();
    error RecoveryNotPending();
    error RecoveryDelayNotMet();
    error GuardianAlreadyExists();
    
    // 錢包接口
    interface IWallet {
        function owner() external view returns (address);
    }
    
    // 錢包配置
    struct WalletConfig {
        address walletAddress;
        mapping(address => bool) guardians;
        uint256 guardianCount;
        uint256 guardianThreshold;
        bytes32 pendingNewOwner;
        uint256 recoveryRequestTime;
        uint256 constant RECOVERY_DELAY = 7 days;
    }
    
    // 映射:錢包地址 -> 配置
    mapping(address => WalletConfig) public walletConfigs;
    
    // 事件
    event GuardianAdded(address indexed wallet, address indexed guardian);
    event GuardianRemoved(address indexed wallet, address indexed guardian);
    event RecoveryRequested(
        address indexed wallet, 
        address indexed newOwner, 
        uint256 executeAfter
    );
    event RecoveryExecuted(address indexed wallet, address indexed newOwner);
    event RecoveryCancelled(address indexed wallet);
    
    /**
     * @dev 添加守護人
     */
    function addGuardian(
        address wallet,
        address guardian
    ) external {
        require(msg.sender == IWallet(wallet).owner(), "Not owner");
        
        WalletConfig storage config = walletConfigs[wallet];
        
        if (config.guardians[guardian]) {
            revert GuardianAlreadyExists();
        }
        
        config.guardians[guardian] = true;
        config.guardianCount++;
        
        // 自動更新閾值(三分之二多數)
        config.guardianThreshold = (config.guardianCount * 2) / 3 + 1;
        
        emit GuardianAdded(wallet, guardian);
    }
    
    /**
     * @dev 移除守護人
     */
    function removeGuardian(
        address wallet,
        address guardian
    ) external {
        require(msg.sender == IWallet(wallet).owner(), "Not owner");
        
        WalletConfig storage config = walletConfigs[wallet];
        
        if (!config.guardians[guardian]) {
            revert NotGuardian();
        }
        
        config.guardians[guardian] = false;
        config.guardianCount--;
        
        // 重新計算閾值
        if (config.guardianCount > 0) {
            config.guardianThreshold = 
                (config.guardianCount * 2) / 3 + 1;
        } else {
            config.guardianThreshold = 1;
        }
        
        emit GuardianRemoved(wallet, guardian);
    }
    
    /**
     * @dev 請求社交恢復
     */
    function requestRecovery(
        address wallet,
        address newOwner,
        bytes[] calldata guardianSignatures
    ) external {
        WalletConfig storage config = walletConfigs[wallet];
        
        // 驗證守護人簽名
        uint256 validSignatures = 0;
        bytes32 recoveryHash = keccak256(
            abi.encode(wallet, newOwner, block.chainid)
        );
        
        for (uint256 i = 0; i < guardianSignatures.length; i++) {
            address signer = recoveryHash.recover(guardianSignatures[i]);
            
            if (config.guardians[signer]) {
                validSignatures++;
            }
        }
        
        if (validSignatures < config.guardianThreshold) {
            revert InvalidGuardianCount();
        }
        
        // 設置待處理恢復
        config.pendingNewOwner = keccak256(abi.encode(newOwner));
        config.recoveryRequestTime = block.timestamp;
        
        emit RecoveryRequested(wallet, newOwner, block.timestamp + config.RECOVERY_DELAY);
    }
    
    /**
     * @dev 執行社交恢復
     */
    function executeRecovery(address wallet) external {
        WalletConfig storage config = walletConfigs[wallet];
        
        if (config.pendingNewOwner == bytes32(0)) {
            revert RecoveryNotPending();
        }
        
        if (
            block.timestamp - config.recoveryRequestTime < 
            config.RECOVERY_DELAY
        ) {
            revert RecoveryDelayNotMet();
        }
        
        // 執行恢復(假設錢包有執行函數)
        // 這裡需要與錢包合約集成
        address newOwner = address(
            bytes20(abi.encodePacked(config.pendingNewOwner))[0:20]
        );
        
        // 清空待處理恢復
        config.pendingNewOwner = bytes32(0);
        
        emit RecoveryExecuted(wallet, newOwner);
    }
    
    /**
     * @dev 取消社交恢復
     */
    function cancelRecovery(address wallet) external {
        WalletConfig storage config = walletConfigs[wallet];
        
        require(msg.sender == IWallet(wallet).owner(), "Not owner");
        require(config.pendingNewOwner != bytes32(0), RecoveryNotPending());
        
        config.pendingNewOwner = bytes32(0);
        
        emit RecoveryCancelled(wallet);
    }
    
    // 簽名恢復
    function recover(bytes32 hash, bytes calldata signature) 
        external 
        pure 
        returns (address) 
    {
        return hash.recover(signature);
    }
}

第三章:安全設計考量

3.1 智能合約安全檢查清單

智能合約錢包安全檢查清單:

□ 訪問控制
  ├── 確保 owner 设置正确
  ├── 验证所有函数有适当的访问修饰符
  ├── 实施暂停机制(紧急情况)
  └── 考虑时间锁机制

□ 重入保护
  ├── 使用 ReentrancyGuard
  ├── 遵循 Checks-Effects-Interactions 模式
  ├── 验证外部调用返回值
  └── 考虑使用 Pull Payment 模式

□ 簽名安全
  ├── 使用 EIP-712 类型化数据签名
  ├── 实施 nonce 管理防止重放攻击
  ├── 验证签名者权限
  ├── 考虑签名的有效期限
  └── 使用安全随机数生成

□ 金额限制
  ├── 设置每日支出限额
  ├── 实施速率限制
  ├── 考虑交易金额白名单
  └── 监控异常交易模式

□ 紧急机制
  ├── 实现紧急暂停功能
  ├── 设置多签批准阈值
  ├── 准备迁移策略
  └── 定期进行安全审计

□ 升级管理
  ├── 使用可升级代理模式(如 UUPS)
  ├── 实施时间锁升级
  ├── 记录升级历史
  └── 测试升级兼容性

3.2 常見攻擊向量與防禦

// 安全防禦示例

/**
 * @title SecureWalletFeatures
 * @dev 安全錢包功能實現
 */
contract SecureWalletFeatures {
    
    // 1. 速率限制
    uint256 public constant MAX_TX_PER_DAY = 100;
    uint256 public txCountToday;
    uint256 public lastResetTimestamp;
    
    modifier rateLimited() {
        if (block.timestamp - lastResetTimestamp >= 1 days) {
            txCountToday = 0;
            lastResetTimestamp = block.timestamp;
        }
        require(txCountToday < MAX_TX_PER_DAY, "Rate limit exceeded");
        _;
        txCountToday++;
    }
    
    // 2. 金额限制
    uint256 public maxTransactionValue;
    uint256 public maxTransactionValuePerDay;
    mapping(address => uint256) dailySpent;
    
    modifier withinLimits(uint256 value) {
        require(value <= maxTransactionValue, "Value exceeds max");
        require(
            dailySpent[msg.sender] + value <= maxTransactionValuePerDay,
            "Daily limit exceeded"
        );
        _;
        dailySpent[msg.sender] += value;
    }
    
    // 3. 可疑操作检测
    event SuspiciousActivity(
        address indexed user,
        string activityType,
        uint256 value
    );
    
    mapping(address => uint256) public consecutiveFailedTx;
    
    function checkForSuspiciousActivity(
        address user,
        uint256 value,
        bool success
    ) internal {
        if (!success) {
            consecutiveFailedTx[user]++;
            
            if (consecutiveFailedTx[user] >= 5) {
                emit SuspiciousActivity(
                    user, 
                    "Multiple failed transactions", 
                    value
                );
                // 可以触发暂停
            }
        } else {
            consecutiveFailedTx[user] = 0;
        }
    }
    
    // 4. 时间锁
    mapping(bytes32 => uint256) public queuedTransactions;
    uint256 public constant MIN_DELAY = 2 days;
    uint256 public constant MAX_DELAY = 7 days;
    
    function queueTransaction(
        address to,
        uint256 value,
        bytes calldata data,
        uint256 delay
    ) external returns (bytes32) {
        require(msg.sender == owner, "Not authorized");
        require(delay >= MIN_DELAY, "Delay too short");
        require(delay <= MAX_DELAY, "Delay too long");
        
        bytes32 txHash = keccak256(
            abi.encode(to, value, data, block.timestamp)
        );
        
        queuedTransactions[txHash] = block.timestamp + delay;
        
        return txHash;
    }
    
    function executeQueuedTransaction(
        address to,
        uint256 value,
        bytes calldata data,
        uint256 timestamp
    ) external {
        bytes32 txHash = keccak256(
            abi.encode(to, value, data, timestamp)
        );
        
        require(
            queuedTransactions[txHash] <= block.timestamp,
            "Transaction not ready"
        );
        
        // 执行交易
        (bool success, ) = to.call{value: value}(data);
        require(success, "Execution failed");
        
        delete queuedTransactions[txHash];
    }
}

3.3 形式化驗證考量

形式化驗證最佳實踐:

1. 合约属性验证
   ├── 完整性:所有预期函数都有实现
   ├── 安全性:关键属性始终保持
   ├── 可达性:不存在无法到达的状态
   └── 终止性:关键函数能够执行完成

2. 常用验证工具
   ├── Certora Prover
   ├── Runtime Verification
   ├── Mythril
   └── Slither

3. 关键属性
   ├── 所有权不变性:owner 只能通过授权机制更改
   ├── 余额保护:余额不能变为负数
   ├── 访问控制:只有授权用户可以执行敏感操作
   └── 事件一致性:所有状态变更都触发事件

第四章:部署與運維最佳實踐

4.1 合約部署檢查清單

部署前檢查清單:

□ 測試覆蓋
  ├── 單元測試覆蓋率 > 90%
  ├── 集成測試覆蓋所有用戶流程
  └── 邊界條件測試

□ 安全審計
  ├── 至少一次第三方審計
  ├── 解決所有發現的問題
  └── 發布審計報告

□ 部署配置
  ├── 驗證合約參數
  ├── 設置正確的 Gas 限制
  └── 準備部署腳本

□ 文檔
  ├── 完整 API 文檔
  ├── 用戶指南
  └── 運維手冊

□ 應急計劃
  ├── 暫停機制就緒
  ├── 升級路徑明確
  └── 資金回收流程

4.2 錢包運營商清單

# 錢包運營商運維腳本示例

class WalletOperator:
    """
    ERC-4337 錢包運營商管理
    """
    
    def __init__(self, config):
        self.config = config
        self.entry_point = config['entry_point']
        self.bundler_url = config['bundler_url']
        self.wallet_factory = config['factory']
        
    def deploy_wallet(self, owner_address, guardian_addresses):
        """
        部署新的智能合約錢包
        """
        # 1. 計算錢包地址
        initCode = self._get_init_code(
            owner_address, 
            guardian_addresses
        )
        
        # 2. 估算部署 Gas
        estimated_gas = self._estimate_deployment_gas(initCode)
        
        # 3. 發送部署交易
        tx_hash = self._send_deployment(initCode)
        
        # 4. 驗證部署成功
        wallet_address = self._get_wallet_address(initCode)
        
        return wallet_address
    
    def send_user_operation(self, wallet_address, call_data):
        """
        發送用戶操作
        """
        # 1. 構建用戶操作
        user_op = self._build_user_operation(
            wallet_address,
            call_data
        )
        
        # 2. 簽名用戶操作
        signed_op = self._sign_user_operation(user_op)
        
        # 3. 發送給 Bundler
        result = self._send_to_bundler(signed_op)
        
        return result
    
    def monitor_operations(self):
        """
        監控待處理操作
        """
        pending_ops = self._get_pending_operations()
        
        for op in pending_ops:
            status = self._check_operation_status(op)
            
            if status == 'failed':
                self._handle_failure(op)
            elif status == 'included':
                self._confirm_inclusion(op)
    
    def estimate_gas_costs(self, user_op):
        """
        估算 Gas 成本
        """
        verification_gas = self._simulate_verification(user_op)
        call_gas = self._simulate_call(user_op)
        
        total_gas = verification_gas + call_gas
        
        gas_price = self._get_current_gas_price()
        
        cost_wei = total_gas * gas_price
        cost_usd = self._wei_to_usd(cost_wei)
        
        return {
            'total_gas': total_gas,
            'cost_wei': cost_wei,
            'cost_usd': cost_usd
        }

結論

本文提供了 ERC-4337 和 EIP-7702 智能合約錢包的完整實作教學,涵蓋了從核心合約實現到安全設計的最佳實踐。通過遵循這些指導原則和程式碼範例,開發者可以構建安全、功能豐富的智能合約錢包。

關鍵要點總結:

  1. 選擇適合的方案:ERC-4337 適合需要立即部署的應用,EIP-7702 適合追求更高效率的新錢包
  1. 安全優先:實施多重簽名、社交恢復、金額限制等安全功能
  1. Gas 優化:使用assembly、批量操作、存儲優化等技術降低成本
  1. 用户体验:集成 Paymaster 實現 Gas 贊助,提供無縫的使用體驗
  1. 持續審計:定期進行安全審計和滲透測試

智能合約錢包的發展仍處於早期階段,隨著技術的成熟和標準的完善,我們期待看到更多創新應用的出現。

延伸閱讀與來源

這篇文章對您有幫助嗎?

評論

發表評論

注意:由於這是靜態網站,您的評論將儲存在本地瀏覽器中,不會公開顯示。

目前尚無評論,成為第一個發表評論的人吧!