EOA委托 - OpenZeppelin文档

本文介绍了EIP-7702提案,该提案允许EOA账户将执行权委托给智能合约,从而实现批量操作、赞助交易和权限降级等功能。文章详细说明了如何通过签名授权和发送SET_CODE_TX_TYPE交易来实现委托,并探讨了与ERC-4337结合使用的方法,以便利用现有的基础设施来处理用户操作。同时,文章也提醒了安全风险,如重放攻击、存储冲突以及与赞助交易中继器的潜在问题。

EOA 委托

EIP-7702 引入了一种新的交易类型 ( 0x4),它授予 外部拥有账户 (EOA,Externally Owned Accounts) 将执行委托给智能合约的能力。 这对于使传统的 EVM 账户能够:

  • 在单个交易中批量执行多个操作(例如 approve + transfer,耶!)

  • 为其他用户赞助交易。

  • 实施权限降级(例如,具有有限权限的子密钥)

本节将引导你完成按照 ERC-7702 将 EOA 委托给合约的过程。 这允许你使用 EOA 的私钥通过自定义执行逻辑来签名和执行操作。 结合 ERC-4337 基础设施,用户可以通过 paymaster 实现 gas 赞助。

委托执行

EIP-7702 使 EOA 能够将其执行能力委托给智能合约,从而有效地弥合了传统账户和 智能账户 之间的差距。 SignerERC7702 实用程序通过根据 EOA 的地址 ( address(this)) 验证签名来促进此委托,从而更容易在智能合约账户中实现 EIP-7702。

// contracts/MyAccountERC7702.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {Account} from "@openzeppelin/community-contracts/account/Account.sol";
import {ERC721Holder} from "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol";
import {ERC1155Holder} from "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol";
import {ERC7821} from "@openzeppelin/community-contracts/account/extensions/ERC7821.sol";
import {SignerERC7702} from "@openzeppelin/community-contracts/utils/cryptography/signers/SignerERC7702.sol";

contract MyAccountERC7702 is Account, SignerERC7702, ERC7821, ERC721Holder, ERC1155Holder {
    /// @dev 允许入口点作为授权的执行者。
    function _erc7821AuthorizedExecutor(
        address caller,
        bytes32 mode,
        bytes calldata executionData
    ) internal view virtual override returns (bool) {
        return caller == address(entryPoint()) || super._erc7821AuthorizedExecutor(caller, mode, executionData);
    }
}
用户可以委托给 ERC-7821 的实例,以获得一个不使用 ERC-4337 相关代码的最小批量执行器。

签名授权

为了授权委托,EOA 所有者签署一条消息,其中包含链 ID、nonce、委托地址和签名组件(即 [chain_id, address, nonce, y_parity, r, s])。 此签名授权有两个目的:它将执行限制为仅委托合约,并防止重放攻击。

EOA 为每个链上的每个授权地址维护一个委托指示符,该指示符指向合约,该合约的代码将在 EOA 的上下文中执行以处理委托的操作。

以下是如何使用 viem 构建授权:

// 记住不要硬编码你的私钥!
const eoa = privateKeyToAccount('<YOUR_PRIVATE_KEY>');
const eoaClient = createWalletClient({
  account: eoa,
  chain: publicClient.chain,
  transport: http(),
});

const walletClient = createWalletClient({
  account, // 查看 Viem 的 `privateKeyToAccount`
  chain, // import { ... } from 'viem/chains';
  transport: http(),
})

const authorization = await eoaClient.signAuthorization({
  account: walletClient.account.address,
  contractAddress: '0x<YOUR_DELEGATE_CONTRACT_ADDRESS>',
  // 如果你的 walletClient 的 account 
  // 是发送交易的 account,则使用此项代替 `account`
  // executor: "self",
});
在实施委托合约时,请确保它们需要避免重放的签名(例如,域名分隔符、nonce)。<br>实施不完善的委托可能会允许恶意行为者几乎完全控制签名者的 EOA。

发送 Set Code 交易

签署授权后,你需要发送一个 SET_CODE_TX_TYPE (0x04) 交易,将委托指示符(即 0xef0100 || address) 写入到你的 EOA 的代码中,这会告诉 EVM 在对你的 EOA 执行操作时,从指定的地址加载并执行代码。

// 随同 `data` 一起发送 `authorization`
const receipt = await walletClient
  .sendTransaction({
    authorizationList: [authorization],
    data: '0x&lt;CALLDATA_TO_EXECUTE_IN_THE_ACCOUNT>',
    to: eoa.address,
  })
  .then((txHash) =>
    publicClient.waitForTransactionReceipt({
      hash: txHash,
    })
  );

// 打印 receipt
console.log(userOpReceipt);

要删除委托并将你的 EOA 恢复到其原始状态,你可以发送一个 SET_CODE_TX_TYPE 交易,其中包含一个指向零地址( 0x0000000000000000000000000000000000000000) 的授权元组。 这将清除账户的代码并将其代码哈希重置为空哈希,但是,请注意,它不会自动清理 EOA 存储。

当更改账户的委托时,请确保新委托的代码是经过专门设计和测试的,作为旧代码的升级。 为了确保委托合约之间的安全迁移,遵循 ERC-7201 的命名空间存储可避免意外冲突。

由于潜在的存储冲突,更新委托指示符可能会导致你的 EOA 无法使用。 我们建议遵循类似于 编写可升级智能合约 的做法。

与 ERC-4337 一起使用

在 EOA 上设置代码以执行逻辑的能力允许用户利用 ERC-4337 基础设施来处理用户操作。 开发人员只需要将 Account 合约与 SignerERC7702 结合起来,即可实现开箱即用的 ERC-4337 合规性。

发送 UserOp

一旦你的 EOA 被委托给兼容 ERC-4337 的账户,你就可以通过入口点合约发送用户操作。 用户操作包括执行所需的所有字段,包括 gas 限制、费用以及要执行的实际调用数据。 入口点将验证操作并在你的委托账户的上下文中执行它。

类似于如何为工厂账户实现 发送 UserOp,以下是如何为一个已委托给 Account 合约的 EOA 构建 UserOp。

const userOp = {
  sender: eoa.address,
  nonce: await entrypoint.read.getNonce([eoa.address, 0n]),
  initCode: "0x" as Hex,
  callData: '0x&lt;CALLDATA_TO_EXECUTE_IN_THE_ACCOUNT>',
  accountGasLimits: encodePacked(
    ["uint128", "uint128"],
    [
      100_000n, // verificationGas
      300_000n, // callGas
    ]
  ),
  preVerificationGas: 50_000n,
  gasFees: encodePacked(
    ["uint128", "uint128"],
    [
      0n, // maxPriorityFeePerGas
      0n, // maxFeePerGas
    ]
  ),
  paymasterAndData: "0x" as Hex,
  signature: "0x" as Hex,
};

const userOpHash = await entrypoint.read.getUserOpHash([userOp]);
userOp.signature = await eoa.sign({ hash: userOpHash });

const userOpReceipt = await eoaClient
  .writeContract({
    abi: EntrypointV08Abi,
    address: entrypoint.address,
    authorizationList: [authorization],
    functionName: "handleOps",
    args: [[userOp], eoa.address],
  })
  .then((txHash) =>
    publicClient.waitForTransactionReceipt({
      hash: txHash,
    })
  );

console.log(userOpReceipt);
当使用赞助交易中继器时,请注意,授权账户可能会导致中继器花费 gas 而未得到报销,方法是使授权无效(增加账户的 nonce)或通过清除账户中的相关资产。 中继器可以实施诸如要求保证金或使用信誉系统之类的安全措施。

← 账户

多重签名 →

  • 原文链接: docs.openzeppelin.com/co...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
OpenZeppelin
OpenZeppelin
江湖只有他的大名,没有他的介绍。