Alert Source Discuss
🛑 Withdrawn Standards Track: ERC

ERC-7897: 面向智能账户的钱包链接服务

定义一个链接到 ERC-4337 钱包的模块化服务注册表。

Authors Francesco Sullo (@sullof)
Created 2024-04-15
Discussion Link https://ethereum-magicians.org/t/generalized-wallet-linked-services-for-erc-4337-wallets/23028
Requires EIP-165, EIP-1167, EIP-4337, EIP-6551, EIP-7656

摘要

本提案定义了一个链接到智能账户的通用服务注册表,特别关注 ERC-4337 钱包,其中服务是扩展钱包功能的合约,由钱包本身拥有。它利用 ERC-1167 最小代理和确定性寻址来实现无需许可的创新,同时保持与现有 ERC-4337 钱包的向后兼容性。为了实现其目标,它采用了 ERC-6551ERC-7656 标准引入的概念,这些标准适用于 NFT,并将其应用于钱包。

注意:由于可以使用 ERC-7656 标准实现相同的功能,因此不再需要此提案。

动机

ERC-4337 (账户抽象) 引入了可编程的智能账户。现有扩展钱包功能的提案 (例如,ERC-6900) 侧重于内部模块。本提案推广了服务绑定的概念,允许任何 ERC-4337 钱包附加外部服务 (例如,恢复、自动化、合规性),而无需更改钱包的核心逻辑。

通过启用模块化、非侵入式扩展,此标准促进了钱包链接服务的开放生态系统,同时确保与现有 ERC-4337 钱包的向后兼容性。

规范

本文档中的关键词 “MUST”,“MUST NOT”,“REQUIRED”,“SHALL”,“SHALL NOT”,“SHOULD”,“SHOULD NOT”,“RECOMMENDED”,“NOT RECOMMENDED”,“MAY” 和 “OPTIONAL” 按照 RFC 2119 和 RFC 8174 中的描述进行解释。

注册表接口

接口 IERC7897Registry 定义如下:

interface IERC7897Registry {
    /**
     * @notice 当钱包链接服务成功部署时发出。
       * @param deployedService 已部署合约的地址
       * @param serviceImplementation 实现合约的地址
       * @param salt 用于 CREATE2 操作的盐
       * @param chainId 合约部署到的链 ID
       * @param wallet ERC-4337 钱包的地址
       */
    event ServiceDeployed(
        address deployedService,
        address indexed serviceImplementation,
        bytes32 salt,
        uint256 chainId,
        address indexed wallet
    );
    
    /**
     * @notice 当 CREATE2 操作未能部署合约时抛出。
       */
    error DeployFailed();
    
    /**
     * @notice 为 ERC-4337 钱包部署钱包链接的服务。
       * 如果服务已经存在,则返回其地址而不调用 CREATE2。
       * @param serviceImplementation 实现合约的地址
       * @param salt CREATE2 操作使用的盐
       * @param wallet ERC-4337 钱包的地址
       * 发出 {ServiceDeployed} 事件。
       * @return service 钱包链接服务的地址
       */
    function deployService(
        address serviceImplementation,
        bytes32 salt,
        address wallet
    ) external returns (address service);
    
    /**
     * @notice 计算 ERC-4337 钱包的预期钱包链接服务地址,
       * 而不部署它。
       * @param serviceImplementation 实现合约的地址
       * @param salt CREATE2 操作使用的盐
       * @param chainId 服务将要部署到的链 ID
       * @param wallet ERC-4337 钱包的地址
       * @return service 钱包链接服务的计算地址
       */
    function serviceAddress(
        address serviceImplementation,
        bytes32 salt,
        uint256 chainId,
        address wallet
    ) external view returns (address service);
}

部署要求

注册表必须将每个钱包链接的服务部署为 ERC-1167 最小代理,并在字节码附加不可变的常量数据。

每个钱包链接服务的已部署字节码必须具有以下结构:

ERC-1167 Header                      (10 bytes)
<serviceImplementation (address)>    (20 bytes)
ERC-1167 Footer                      (15 bytes)
<salt (bytes32)>                     (32 bytes)
<chainId (uint256)>                  (32 bytes)
<wallet (address)>                   (20 bytes)

推荐的服务接口

使用 ERC7897Registry 创建的任何合约都应该实现 IERC7897Service 接口:

interface IERC7897Service {
  /**
  * @notice 返回链接到合约的钱包
  * @return chainId 钱包的 chainId
  * @return wallet [ERC-4337](/docs/eips/EIPS/eip-4337/) 钱包的地址
  */
  function wallet() external view returns (uint256 chainId, address wallet);
}

访问控制

服务应该实现访问控制,以限制对钱包所有者的关键操作。例如:

function owner() public view returns (address) {
  (, address wallet) = IERC7897Service(address(this)).wallet();
  return wallet;
}

modifier onlyOwner() {
  require(msg.sender == owner(), "Unauthorized");
  _;
}

原理

ERC-7897 的技术基础围绕可与 ERC-4337 钱包关联的合约类型的扩展和概括。关键决策包括:

  • 灵活性:使任何 ERC-4337 钱包都可以在不修改其核心逻辑的情况下附加外部服务。

  • 无需许可的创新:开发人员可以为任何钱包部署服务,从而促进开放的生态系统。

  • 向后兼容性:与现有的 ERC-4337 钱包(包括 Safe、Argent 和 Biconomy)一起使用。

  • 确定性寻址:使用 CREATE2 + salt/chainId/wallet 进行可预测的服务部署。

参考实现

// 此实现是 Jayden Windle @jaydenwindle 和 Vectorized @vectorized 编写的 ERC6551Registry 合约的变体
 
contract ERC7897Registry is IERC7897Registry {
  function deployService(
    address serviceImplementation,
    bytes32 salt,
    address wallet
  ) external override returns (address) {
    // solhint-disable-next-line no-inline-assembly
    assembly {
    // 内存布局:
    // ----
    // 0x00   0xff                           (1 byte)
    // 0x01   registry (address)             (20 bytes)
    // 0x15   salt (bytes32)                 (32 bytes)
    // 0x35   Bytecode Hash (bytes32)        (32 bytes)
    // ----
    // 0x55   ERC-1167 Constructor + Header  (20 bytes)
    // 0x69   implementation (address)       (20 bytes)
    // 0x5D   ERC-1167 Footer                (15 bytes)
    // 0x8C   salt (uint256)                 (32 bytes)
    // 0xAC   chainId (uint256)              (32 bytes)
    // 0xCC   wallet (address)               (20 bytes)

    // 将字节码 + 常量数据复制到内存
      mstore(0x8c, salt) // salt
      mstore(0xac, chainid()) // chainId
      mstore(0xcc, wallet) // wallet address (20 bytes)
      mstore(0x6c, 0x5af43d82803e903d91602b57fd5bf3) // ERC-1167 footer
      mstore(0x5d, serviceImplementation) // implementation
      mstore(0x49, 0x3d60ad80600a3d3981f3363d3d373d3d3d363d73) // ERC-1167 constructor + header

    // 将 create2 计算数据复制到内存
      mstore8(0x00, 0xff) // 0xFF
      mstore(0x35, keccak256(0x55, 0x8b)) // keccak256(bytecode) - 0x8b = 139 bytes
      mstore(0x01, shl(96, address())) // registry address
      mstore(0x15, salt) // salt

    // 计算服务地址
      let computed := keccak256(0x00, 0x55)

    // 如果尚未部署该服务
      if iszero(extcodesize(computed)) {
      // 部署服务合约
        let deployed := create2(0, 0x55, 0x8b, salt) // 0x8b = 139 bytes

      // 如果部署失败,则恢复
        if iszero(deployed) {
          mstore(0x00, 0xd786d393) // `DeployFailed()`
          revert(0x1c, 0x04)
        }

      // 发出 ServiceDeployed 事件
        mstore(0x00, deployed) // deployedService
        mstore(0x20, serviceImplementation) // serviceImplementation
        mstore(0x40, salt) // salt
        mstore(0x60, chainid()) // chainId
        mstore(0x80, wallet) // wallet

        log4(
          0x00, // 数据开始
          0xa0, // 数据长度(160 字节:已部署 + 实现 + 盐 + 链 ID + 钱包)
          0x2f82bd0c129ea2d065cf394fb7760031982c6278372c89e1a059f2478ddf4763, // 事件签名哈希
          deployed, // 索引的 deployedService
          serviceImplementation, // 索引的 serviceImplementation
          salt, // salt
          chainid(), // chainId
          wallet // 索引的 wallet
        )

      // 返回服务地址
        return(0x00, 0x20)
      }

    // 否则,返回计算出的服务地址
      mstore(0x00, computed)
      return(0x00, 0x20)
    }
  }

  function serviceAddress(
    address serviceImplementation,
    bytes32 salt,
    uint256 chainId,
    address wallet
  ) external view override returns (address) {
    // solhint-disable-next-line no-inline-assembly
    assembly {
    // 将字节码 + 常量数据复制到内存
      mstore(0x8c, salt) // salt
      mstore(0xac, chainId) // chainId
      mstore(0xcc, wallet) // wallet address (20 bytes)
      mstore(0x6c, 0x5af43d82803e903d91602b57fd5bf3) // ERC-1167 footer
      mstore(0x5d, serviceImplementation) // implementation
      mstore(0x49, 0x3d60ad80600a3d3981f3363d3d373d3d3d363d73) // ERC-1167 constructor + header

    // 将 create2 计算数据复制到内存
      mstore8(0x00, 0xff) // 0xFF
      mstore(0x35, keccak256(0x55, 0x8b)) // keccak256(bytecode) - 0x8b = 139 bytes
      mstore(0x01, shl(96, address())) // registry address
      mstore(0x15, salt) // salt

    // 计算并返回服务地址
      mstore(0x00, keccak256(0x00, 0x55))
      return(0x00, 0x20)
    }
  }
}

安全考虑

所有权和控制权

钱包链接的服务必须由 ERC-4337 钱包所有者控制,以防止未经授权的访问。实施者应包括针对恶意或未经验证的实施的保护措施。

可升级性风险

如果服务是可升级的,请确保安全升级机制以防止未经授权的更改。例如:

  • 服务的所有者应该是钱包本身。

  • 只有钱包才能升级服务的实现。

  • 实施版本控制以确保升级之间的向后兼容性。

  • 对关键升级使用时间锁或多重签名,以降低恶意更改的风险。

重入和跨合约交互

与外部协议交互的服务应遵循最佳实践以防止重入攻击。

用户教育

应提供清晰的用户界面和警告,以降低网络钓鱼和社会工程风险。

测试

实施者应在测试网上彻底测试注册表和服务,以确保在部署到主网之前的正确性和安全性。

版权

CC0 下放弃版权和相关权利。

Citation

Please cite this document as:

Francesco Sullo (@sullof), "ERC-7897: 面向智能账户的钱包链接服务 [DRAFT]," Ethereum Improvement Proposals, no. 7897, April 2024. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7897.