摘要

  • 引入 UserOperation:一种高层“伪交易对象”,替代直接在共识层提交交易

  • 用户将 UserOperation 发送到单独的 mempool

  • Bundler 将多个 UserOperation 打包,通过 handleOps 调用 EntryPoint 合约,再打包进区块

  • 目标是在不修改以太坊底层协议的情况下,实现账户抽象

动机

  1. 实现账户抽象

    • 允许使用智能合约账户作为主账户,支持任意验证逻辑。
    • 用户无需拥有外部账户(EOA)。
  2. 去中心化与开放参与

    • 任何 bundler 都可参与交易打包。
    • 依赖公共 mempool,无需用户直接知晓 bundler 地址。
    • 不增加信任假设。
  3. 无需共识层修改

    • 避免协议层改动,提高快速部署的可能性。
  4. 支持更多应用场景

    • 隐私保护型应用
    • 原子多操作
    • 使用 ERC-20 代币支付手续费或开发者代付
    • 灵活的验证逻辑(不同签名方案、多签、恢复方案)
    • 灵活的手续费支付(第三方支付、跨链支付)
    • 批量操作与打包执行

总结:该提案通过 UserOperation、Bundler 和 EntryPoint 合约实现智能合约账户的完全抽象化,支持多签、代付和批量操作,同时无需修改以太坊共识层


规范

术语定义

术语定义
UserOperation一种描述“代表用户发起一笔交易”的数据结构。为避免与原生交易混淆,特意不叫“transaction”。像交易一样,它包含 to、calldata、maxFeePerGas、maxPriorityFeePerGas、nonce、signature。不像交易,它包含以下所述的几个其他字段。特别注意,signature 字段的使用不由协议定义,而是由智能合约账户实现定义
Sender发起 UserOperation 的智能合约账户
EntryPoint用于执行 UserOperation 批次的单例合约。Bundler 应白名单支持的 EntryPoint
Bundler一个节点(区块构建者),它能处理 UserOperation、创建有效的 entryPoint.handleOps() 交易,并在交易仍有效时将其添加到区块中。这可以通过多种方式实现:Bundler 可以自己充当区块构建者。如果 bundler 不是区块构建者,它应通过 mev-boost 等基础设施或其他提议者-构建者分离机制与区块构建者合作
Paymaster同意代替 sender 支付交易费用的辅助合约
Factory必要时负责部署新 sender 合约的辅助合约
Aggregator也称为“授权合约” - 一个允许多个 UserOperation 共享一次验证的合约。此类合约的完整设计超出本提案范围
Canonical UserOperation mempool一个去中心化、无许可的 P2P 网络,bundler 在其中交换符合同一套共享验证规则的合法 UserOperation。具体规则的完整规范超出本提案范围
Alternative UserOperation mempool任何其他 P2P 内存池,其中 UserOperation 的有效性由不同于共享规则的规则决定,这些规则以任何方式应用于验证代码
DepositSender 或 Paymaster 合约向 EntryPoint 合约转入的一笔以太币(或任何 L2 原生货币),用于支付未来 UserOperation 的 gas 费用

UserOperation 结构定义

字段类型描述
senderaddress发起 UserOperation 的账户
nonceuint256防重放参数(详见“半抽象 Nonce 支持”)
factoryaddress新账户的账户工厂 OR 0x7702 标志用于 EIP-7702 账户,否则 address(0)
factoryDatabytes如果提供 factory,则为账户工厂的数据 OR EIP-7702 初始化数据,或空数组
callDatabytes在主要执行调用期间传递给 sender 的数据
callGasLimituint256为主要执行调用分配的 gas 量
verificationGasLimituint256为验证步骤分配的 gas 量
preVerificationGasuint256额外 gas 用于支付 bundler
maxFeePerGasuint256最大 gas 费(类似于 EIP-1559 max_fee_per_gas)
maxPriorityFeePerGasuint256最大优先费(类似于 EIP-1559 max_priority_fee_per_gas)
paymasteraddresspaymaster 合约地址(或空,如果 sender 自己支付 gas)
paymasterVerificationGasLimituint256为 paymaster 验证代码分配的 gas 量(仅当 paymaster 存在时)
paymasterPostOpGasLimituint256为 paymaster 后操作代码分配的 gas 量(仅当 paymaster 存在时)
paymasterDatabytespaymaster 的数据(仅当 paymaster 存在时)
signaturebytes传入 sender 以验证授权的数据

上链时部分字段会被打包,转变成 PackedUserOperation

Entrypoint 接口

PackedUserOperation 结构定义

字段类型描述
senderaddress
nonceuint256
initCodebytesfactory 地址和 factoryData 的连接(或空),或 EIP-7702 数据
callDatabytes
accountGasLimitsbytes32verificationGasLimit(16 字节)和 callGasLimit(16 字节)的连接
preVerificationGasuint256
gasFeesbytes32maxPriorityFeePerGas(16 字节)和 maxFeePerGas(16 字节)的连接
paymasterAndDatabytespaymaster 字段的连接(或空)
signaturebytes

EntryPoint 合约的核心接口如下:

function handleOps(PackedUserOperation[] calldata ops, address payable beneficiary);

beneficiary 是将收到批次执行期间收集的所有 gas 费的地址。

Smart Contract Account 接口

interface IAccount {
  function validateUserOp
      (PackedUserOperation calldata userOp, bytes32 userOpHash, uint256 missingAccountFunds)
      external returns (uint256 validationData);
}

userOpHash 是对 userOp(除 signature)、entryPoint 和 chainId 的哈希

需实现功能

  • 必须验证调用者是可信的 EntryPoint
  • 必须验证 signature 是 userOpHash 的有效签名,并在签名不匹配时返回 SIG_VALIDATION_FAILED (1) 而非 revert。任何其他错误必须 revert
  • 在返回 SIG_VALIDATION_FAILED (1) 时不应提前返回。相反,应完成正常流程以启用验证函数的 gas 估算
  • 必须支付 EntryPoint(调用者)至少 missingAccountFunds(可能为零,如果当前 sender 的存款足够/存在 paymaster 垫付)
  • sender 可以支付超过此最小值以覆盖未来交易。它也可以随时调用 withdrawTo 来取回
  • 返回值必须打包 aggregator/authorizer、validUntil 和 validAfter 时间戳
    • aggregator/authorizer - 0 表示有效签名,1 表示签名失败。除此之外,为 aggregator/authorizer 合约的地址
    • validUntil 是 6 字节时间戳值,或零表示“无限”。UserOperation 仅在此时间之前有效
    • validAfter 是 6 字节时间戳。UserOperation 仅在此时间之后有效
    • 为了使用区块号指定有效范围,validUntil 和 validAfter 都需要将其最高位设置为 1

    注意:有效范围可以用两个区块时间戳或两个区块号表示,但不能在同一个 UserOperation 的有效范围中混合一个时间戳和一个区块号

Smart Contract Account 可以实现 IAccountExecute 接口

interface IAccountExecute {
  function executeUserOp(PackedUserOperation calldata userOp, bytes32 userOpHash) external;
}

此方法将由 EntryPoint 使用当前 UserOperation 调用,而不是直接在 sender 上执行 callData

半抽象 Nonce 支持

在以太坊协议中,顺序交易 nonce 值用作防重放方法,以及确定交易包含在区块中的有效顺序.

它还贡献于交易哈希的唯一性,因为同一 sender 的同一 nonce 交易不能两次包含在链中.

然而,要求单一顺序 nonce 值限制了 sender 定义自定义交易排序和防重放逻辑的能力.

我们实现了 nonce 机制,使用 UserOperation 中的单一 uint256 nonce 值,但将其视为两个值:

  • 192 位“key”
  • 64 位“sequence”

这些值在 EntryPoint 合约中链上表示。我们在 EntryPoint 接口中定义以下方法来暴露这些值:

function getNonce(address sender, uint192 key) external view returns (uint256 nonce);

对于每个 key,EntryPoint 为每个 UserOperation 验证 sequence。如果 nonce 验证失败,UserOperation 被视为无效,批次 revert。sequence 值为 sender 顺序且单调递增。新 key 可以随时引入任意值,其 sequence 从 0 开始.

这种方法在协议层面维护了链上 UserOperation 哈希唯一性的保证,同时允许账户使用 192 位“key”字段实现任何自定义逻辑,同时适合 32 字节字.

读取/验证 nonce

在准备 UserOperation 时,bundler 可以调用此方法来确定 nonce 字段的有效值.

Bundler 对 UserOperation 的验证应以 getNonce 开始,以确保交易具有有效的 nonce 字段.

如果 bundler 愿意接受同一 sender 的多个 UserOperation 进入其内存池,则 bundler 应跟踪已添加内存池的 UserOperation 的 key 和 sequence 对.

使用示例

  1. 经典顺序递增 nonce

为了要求账户具有经典顺序 nonce,验证函数必须执行:

require(userOp.nonce<type(uint64).max)
  1. 有序管理事件

在某些情况下,账户可能需要与正常操作并行的“管理”操作通道.

在这种情况下,账户可以在调用账户自身的方法时使用特定 key:

bytes4 sig = bytes4(userOp.callData[0 : 4]);
uint key = userOp.nonce >> 64;
if (sig == ADMIN_METHODSIG) {
    require(key == ADMIN_KEY, "wrong nonce-key for admin operation");
} else {
    require(key == 0, "wrong nonce-key for normal operation");
}

EntryPoint 合约所需功能

EntryPoint 核心方法是 handleOps,它处理 UserOperation 数组

EntryPoint 的 handleOps 函数必须执行以下步骤(我们首先描述更简单的无 paymaster 情况)。它必须进行两个循环,验证循环和执行循环。在验证循环中,handleOps 调用必须为每个 UserOperation 执行以下步骤:

  • 如果 sender 智能合约账户尚不存在,使用 UserOperation 中提供的 initcode 创建它
    • 如果 factory 地址是“0x7702”,则 sender 必须是具有 EIP-7702 授权指定的 EOA。EntryPoint 验证授权地址与 UserOperation signature 中指定的地址匹配(详见对 [EIP-7702] 授权的支持)
    • 如果 sender 不存在,且 initcode 为空,或未在“sender”地址部署合约,则调用必须失败

    警告:如果 sender 存在,且 initcode 不为空,则忽略 initcode。

  • 基于验证和调用 gas 限制以及当前 gas 值计算 sender 需要支付的最大可能费用
  • 计算 sender 必须添加到 EntryPoint“deposit”的费用
  • 在 sender 合约上调用 validateUserOp,传入 UserOperation、其哈希和所需费用。如果 sender 认为 UserOperation 有效,智能合约账户必须验证 UserOperation 的 signature 参数并支付费用。如果任何 validateUserOp 调用失败,handleOps 必须至少跳过该 UserOperation 的执行,并可能完全 revert
  • 验证 EntryPoint 中账户的 deposit 足够高以覆盖最大可能成本(覆盖已完成的验证和最大执行 gas)

执行流程

  • 使用 UserOperation 的 calldata 调用账户。账户自行决定如何解析 calldata;预期工作流是账户有一个 execute 函数,将剩余 calldata 解析为账户应执行的一个或多个调用序列
  • 如果 calldata 以 IAccountExecute.executeUserOp 的方法签名开头,则 EntryPoint 必须通过编码 executeUserOp(userOp,userOpHash) 构建 calldata,并使用该 calldata 调用账户
  • 调用后,用预收费用的多余 gas 成本退还账户的 deposit
  • 对 callGasLimit 和 paymasterPostOpGasLimit 的剩余未用 gas 施加 10%(UNUSED_GAS_PENALTY_PERCENT)的罚款。

    仅当剩余未用 gas 量大于或等于 40000(PENALTY_GAS_THRESHOLD)时才施加此罚款 此罚款是必要的,以防止 UserOperation 预留批次中的大量 gas 空间但未使用,从而阻止 bundler 包含其他 UserOperation

  • 执行所有调用后,从所有 UserOperation 收集的费用支付给 bundler 提供的 beneficiary 地址

在接受 UserOperation 之前,bundler 应使用 RPC 方法本地调用 EntryPoint 的 handleOps 函数,以验证 signature 正确且 UserOperation 实际支付费用;详见下文Simulation section部分。节点/bundler 必须拒绝验证失败的 UserOperation,即不将其添加到本地内存池,也不传播给其他对等节点

ERC-4337 的 JSON-RPC API

为了支持向 bundler 发送 UserOperation 对象,后者通过 P2P 内存池传播它们,我们引入一组 JSON-RPC API,包括 eth_sendUserOperation 和 eth_getUserOperationReceipt

新 JSON-RPC API 的完整定义超出本提案范围

对 EIP-712 签名的支持

userOpHash 计算为 [EIP-712] 类型消息哈希,具有以下参数:

bytes32 constant TYPE_HASH =
    keccak256(
        "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"
    );

bytes32 constant PACKED_USEROP_TYPEHASH =
    keccak256(
        "PackedUserOperation(address sender,uint256 nonce,bytes initCode,bytes callData,bytes32 accountGasLimits,uint256 preVerificationGas,bytes32 gasFees,bytes paymasterAndData)"
    );

对 EIP-7702 授权的支持

在启用 EIP-7702 的网络上,eth_sendUserOperation 方法接受额外的 eip7702Auth 参数。如果设置此参数,它必须是有效的 EIP-7702 授权元组,并由 sender 地址签名。Bundler 必须将批次中所有 UserOperation 的所有所需 eip7702Auth 添加到 authorizationList,并使用 SET_CODE_TX_TYPE 交易类型执行批次。此外,UserOperation 哈希计算更新以包含所需的 EIP-7702 委托地址

如果 initCode 字段以右填充 18 个零的 0x7702 开头,且此账户使用 EIP-7702 交易部署,则哈希计算如下:

  • 为哈希计算目的,将 UserOperation 的 initCode 字段的前 20 字节设置为账户的 EIP-7702 委托地址(使用 EXTCODECOPY 获取)
  • initCode 不用于调用 factory 合约
  • 如果 initCode 长于 20 字节,则剩余 initCode 用于调用账户自身的初始化函数

注意:UserOperation 可能仍可在无此类 initCode 的情况下执行。在这种情况下,EntryPoint 不哈希当前 EIP-7702 委托,并可能针对修改后的账户执行

此外,EIP-7702 定义执行授权的 gas 成本等于 PER_EMPTY_ACCOUNT_COST = 25000。此 gas 消耗在 EntryPoint 合约链上不可观察,必须包含在 preVerificationGas 值中

扩展:paymaster

我们扩展 EntryPoint 逻辑以支持 paymaster 赞助其他用户的交易。此功能可用于允许应用开发者为用户补贴费用、允许用户使用 [ERC-20] 代币支付费用以及许多其他用例。当 UserOperation 中的 paymasterAndData 字段非空时,EntryPoint 为该 UserOperation 实现不同的流程:

在验证循环期间,除了调用 validateUserOp,handleOps 执行还必须检查 paymaster 在 EntryPoint 有足够的 ETH 存款来支付 UserOperation,然后调用 paymaster 上的 validatePaymasterUserOp 以验证 paymaster 是否愿意支付 UserOperation。请注意,在这种情况下,validateUserOp 以 missingAccountFunds 为 0 调用,以反映账户的存款不用于此 UserOperation 的支付

如果 paymaster 的 validatePaymasterUserOp 返回非空 context 字节数组,则 handleOps 必须在主要执行调用后调用 paymaster 上的 postOp。否则,不调用 postOp 函数

恶意制作的 paymaster 可能对系统造成 DoS 攻击风险,bundler 应采取步骤缓解。作为缓解,bundler 应为其服务的合约使用声誉系统,且 paymaster 必须限制其存储使用,或在声誉系统中存入 stake。此类声誉系统的完整规范超出本提案范围

paymasterAndData 字段编码和 paymasterSignature

paymasterAndData 字段是一个字节数组,包含以下字段的非标准编码:

  • paymasterAddress - 20 字节 — paymaster 合约地址

  • paymasterVerificationGasLimit - 16 字节 - 验证函数的 gas 限制

  • postOpGasLimit - 16 字节 - postOp 函数的 gas 限制

  • paymasterData - paymaster 合约将在 validatePaymasterUserOp 调用中接收的数据 以下数据可以选择附加到 paymasterAndData 字段:

  • paymasterSignature - paymaster 合约要检查的“signature”值字节数组;此值可以提供而不影响 UserOperation 哈希

  • paymasterSignatureLength - 2 字节 - paymasterSignature 参数字节数组的精确长度

  • PAYMASTER_SIG_MAGIC (0x22e325a297439656) - 附加的魔术值,表示 UserOperation 使用 paymasterSignature 功能

注意,由于 signature 和 paymasterSignature 字段都不影响 UserOperation 哈希,Sender 和 Paymaster 的签名可以并行执行

paymaster 接口如下:

function validatePaymasterUserOp
    (PackedUserOperation calldata userOp, bytes32 userOpHash, uint256 maxCost)
    external returns (bytes memory context, uint256 validationData);

function postOp
    (PostOpMode mode, bytes calldata context, uint256 actualGasCost, uint256 actualUserOpFeePerGas)
    external;

enum PostOpMode {
    opSucceeded, // UserOperation 成功
    opReverted // UserOperation revert。paymaster 仍需支付 gas。
}
EntryPoint 必须实现以下 API 以允许 paymaster 等实体 stake从而在存储访问中具有更多灵活性

// 为调用实体添加 stake
function addStake(uint32 _unstakeDelaySec) external payable;

// 解锁 stake(必须等待 unstakeDelay 才能取回)
function unlockStake() external;

// 取回解锁的 stake
function withdrawStake(address payable withdrawAddress) external;
paymaster 还必须有 depositEntryPoint 将从中收取 UserOperation 成本deposit用于支付 gas 费用 stake被锁定分开

EntryPoint 必须实现以下接口以允许 Paymaster(可选地 Accounts)管理其 deposit:

// 返回账户的 deposit
function balanceOf(address account) public view returns (uint256);

// 添加到给定账户的 deposit
function depositTo(address account) public payable;

// 添加到调用账户的 deposit
receive() external payable;

// 从当前账户的 deposit 取回
function withdrawTo(address payable withdrawAddress, uint256 withdrawAmount) external;

// 获取当前执行的 UserOperation 哈希,或如果不在执行期间调用则为 0
function getCurrentUserOpHash() public view returns (bytes32);
 Bundler 在收到 UserOperation 时的行为

类似于以太坊交易,UserOperation 的链外流程可以描述如下:

  • 客户端通过 RPC 调用 eth_sendUserOperation 向 bundler 发送 UserOperation
  • 在将 UserOperation 包含到内存池之前,bundler 对新收到的 UserOperation 运行第一次验证。如果 UserOperation 验证失败,bundler 丢弃它并在 eth_sendUserOperation 响应中返回错误
  • 稍后,一旦构建批次,bundler 从内存池取出 UserOperation 并对每个运行单个 UserOperation 的第二次验证。如果成功,则计划包含在下一个批次中,否则丢弃
  • 在链上提交新批次之前,bundler 执行整个 UserOperation 批次的第三次验证。如果任何 UserOperation 验证失败,bundler 丢弃它们。bundler 应跟踪对等节点的声誉。此类声誉系统的完整设计超出本提案范围

当 bundler 收到 UserOperation 时,它必须首先运行一些基本 sanity 检查,即:

  • sender 是现有合约,或 initCode 非空(但不能两者皆有)
  • 如果 initCode 非空,解析其前 20 字节为 factory 地址或 EIP-7702 标志
  • 记录 factory 是否 stake,在后续模拟表明需要时。如果 factory 访问全局状态,它必须 stake
  • verificationGasLimit 和 paymasterVerificationGasLimits 低于 MAX_VERIFICATION_GAS (500000),且 preVerificationGas 足够高以支付序列化 UserOperation 的 calldata gas 成本加上 PRE_VERIFICATION_OVERHEAD_GAS (50000)
  • paymasterAndData 为空,或以 paymaster 地址开头,该合约 (i) 当前链上有非空代码,(ii) 有足够 deposit 支付 UserOperation,(iii) 当前未被禁。在模拟期间,还根据其存储使用检查 paymaster 的 stake
  • callGasLimit 至少是具有非零值的 CALL 成本
  • maxFeePerGas 和 maxPriorityFeePerGas 高于 bundler 愿意接受的可配置最小值。至少,它们足够高以包含在即将到来的 block.basefee 中
  • sender 在内存池中没有另一个 UserOperation(或它替换现有条目,具有相同 sender 和 nonce,具有更高的 maxPriorityFeePerGas 和同等增加的 maxFeePerGas)。单个批次中每个 sender 仅一个 UserOperation。如果 sender stake,则免除此规则,并可在内存池和批次中有多个 UserOperation

UserOperation Simulation

我们定义 UserOperation 模拟,作为对 EntryPoint 合约的链外视图调用(或跟踪调用)与 UserOperation,以及对验证代码应用的共享规则集的强制,作为 UserOperation 验证的一部分

Simulation 逻辑依据

要验证正常以太坊交易 tx,bundler 执行静态检查,如:

  1. ecrecover(tx.v, tx.r, tx.s) 必须返回有效 EOA
  2. tx.nonce 必须是恢复的 EOA 的当前 nonce
  3. 恢复的 EOA 的余额必须足够支付交易
  4. tx.gasLimit 必须足够覆盖交易的内在 gas 成本
  5. chainId 必须匹配当前链

所有这些检查不依赖 EVM 状态,且不能受其他账户交易影响

相反,UserOperation 验证依赖 EVM 状态(对 validateUserOp、validatePaymasterUserOp 的调用),可被其他 UserOperation(或正常以太坊交易)改变。因此,我们引入模拟作为检查其有效性的新机制。直观地说,模拟的目标是确保 UserOperation 的链上验证代码被沙箱化,与同一批次中的其他 UserOperation 隔离

Simulation 规范:

要模拟 UserOperation 验证,bundler 对 handleOps() 方法进行视图调用,使用要检查的 UserOperation

模拟应仅运行在 sender 和 paymaster 的验证部分,且不要求 UserOperation 的执行。Bundler 可以向批次添加第二个“总是失败”的 UserOperation,以便模拟在第一个 UserOperation 的验证完成后结束

如果模拟 revert,bundler 必须丢弃 UserOperation

模拟调用执行完整验证,通过调用:

  1. 如果存在 initCode,创建 sender 账户
  2. account.validateUserOp。
  3. 如果指定 paymaster:paymaster.validatePaymasterUserOp

sender 或 paymaster 可以返回时间范围(validAfter/validUntil)。UserOperation 在当前时间必须有效才能被视为有效,定义为 validAfter<=block.timestamp

Bundler 必须丢弃过期太快且可能在下一个区块前失效的 UserOperation。要解码返回的时间范围,bundler 必须使用跟踪运行验证,以从 validateUserOp 和 validatePaymasterUserOp 方法解码返回值

为了防止对 bundler 的 DoS 攻击,它们必须确保上述验证方法通过验证规则,这些规则约束其 opcode 和存储的使用。此类共享规则集的完整设计超出本提案范围

Estimate preVerificationGas

本文档不指定估算此值的规范方式,因为它取决于非永久网络属性,如操作和数据 gas 定价以及预期批次大小。

然而,要求估算值足够覆盖以下成本:

  • 基本批次交易成本。在以太坊上,21000 gas 除以 UserOperation 数量。
  • 与 UserOperation 相关的 calldata gas 成本,如 EIP-2028 定义。
  • 静态 EntryPoint 合约代码执行。
  • 加载 UserOperation 的固定大小字段到 EVM 内存的静态内存成本
  • 由于 paymaster validatePaymasterUserOp 函数返回的 context 导致的内存成本(包括扩展成本),如果相关。
    • 到 innerHandleOp() 函数的外部调用,这是 EntryPoint 实现的主要部分。请注意,此值非静态,取决于 UserOperation 在批次中的位置。
  • [EIP-7702] 授权成本,如果有。
  • EIP-7623 calldata 底价增加估算如下:
    • 应用 tx.gasUsed 的新公式,用此 UserOperation 的估算值替换 execution_gas_used 值。
    • 估算是模拟期间使用的所有验证 gas(账户创建、验证和 paymaster 验证)的总和加上执行和 postOp gas 限制总和的 10%。

Bundler 必须要求 PreVerificationGas 值有 slack(松弛值),以适应未来批次中的内存扩展成本,以及 UserOperation 在其中的预期位置

Paymaster

Paymaster 合约允许 gas 抽象:有一个合约,不是交易的 sender,来支付交易费用

Paymaster 架构允许它们遵循“预收费,后退款”的模型。例如,token-paymaster 可以用交易的最大可能价格预收费用户,并在之后退款多余部分

首次智能合约账户创建

注意:对于使用 EIP-7702 的合约,此流程在对 [EIP-7702] 授权的支持中描述

本提案的重要设计目标是复制 EOA 的关键属性,即用户无需执行自定义操作或依赖现有用户创建其智能合约账户;他们可以本地生成地址并立即开始接受资金

智能合约账户创建本身由“factory”合约完成,带有一些账户特定数据。Factory 预期使用 CREATE2 0xF5(而非 CREATE 0xF0)创建账户,以便账户创建顺序不干扰生成地址。initCode 字段(如果非零长度)解析为 20 字节 factory 地址,后跟传递给此地址的 calldata。此方法调用预期创建账户并返回其地址。如果 factory 使用 CREATE2 0xF5 或其他确定性方法创建账户,预期即使已创建也返回账户地址。这使得 bundler 更容易查询地址,而无需知道账户是否已部署,通过模拟调用 entryPoint.getSenderAddress(),它在底层调用 factory。当指定 initCode 时,如果 sender 地址指向现有合约或调用 initCode 后 sender 地址仍不存在,则操作中止。initCode 不得直接从 EntryPoint 调用,而从另一个地址调用。此 factory 方法创建的合约必须接受 validateUserOp 调用以验证 UserOperation 的签名。如果 factory 访问全局存储,则必须 stake。注意:为了让钱包应用在创建前确定账户的“反事实”地址,它应静态调用 entryPoint.getSenderAddress()

安全考量

Factory 合约

所有 factory 合约必须检查对 createAccount() 函数的所有调用源于 entryPoint.senderCreator() 地址

Paymaster 合约

所有 paymaster 合约必须检查对 validatePaymasterUserOp() 和 postOp() 函数的所有调用源于 EntryPoint

Aggregator 合约

所有 aggregator 合约必须检查对 validateSignatures() 函数的所有调用源于 EntryPoint

EIP-7702 委托智能合约账户

所有 EIP-7702 委托智能合约账户实现必须检查对初始化函数的所有调用源于 entryPoint.senderCreator() 地址

EntryPoint 合约无法知道 EIP-7702 账户是否已初始化,因此 EIP-7702 账户初始化代码可以通过 EntryPoint 多次调用。账户代码应仅允许调用一次,且钱包应用不应重复传递 initCode