本文深入探讨了ERC-4337中的EntryPoint合约,这是Account Abstraction的重要组成部分。文中详细介绍了EntryPoint的作用、功能实现以及如何处理用户操作,强调了其在整个系统中的关键作用,同时提及了版本更新和安全考虑。
欢迎回到我们对 ERC-4337:使用 Alt Mempool 的账户抽象 的探讨。在我们的上一篇文章中,我们分析了 Bundler,强调了它接收和执行 UserOperations 作为打包交易的角色,介绍了 “Alt Mempool” 的概念。
本部分将重点介绍 ERC-4337 的另一个重要组成部分:EntryPoint。对于新手,我们建议先阅读第一部分以获得全面的理解。
来源:ERC-4337: 使用 Alt Mempool 进行账户抽象
EntryPoint 合约是 ERC-4337 的主要组件之一,作为 Bundler、钱包和支付者的可信单实例,负责验证和处理 UserOperations。
EntryPoint 的主要责任之一是确保一旦验证成功,操作将被执行,钱包或支付者将被收费,并且无论操作结果如何,Bundler 都将获得补偿。
该结构还包括一个 声誉系统,使支付者对后续回退失败负责,并可能受到限流或禁用。
EntryPoint
实现了许多在 ERC 草案 中定义的功能。两个主要的函数,handleOps
和 handleAggregatedOps
,负责执行用户操作的批处理。当不需要签名聚合时使用第一个函数,当操作需要签名聚合器时使用第二个函数。
该函数处理一批用户操作,并用收取的Gas费补偿 beneficiary
账户。
account-abstraction/contracts/core/EntryPoint.sol – Medium
contract EntryPoint {
// ...
function handleOps(
UserOperation[] calldataops,
address payable beneficiary
) public nonReentrant {
uint256 opslen = ops.length;
UserOpInfo[] memory opInfos =newUserOpInfo[](opslen);
unchecked {
for (uint256 i =0; i < opslen; i++) {
UserOpInfo memory opInfo = opInfos[i];
(
uint256validationData,
uint256pmValidationData
) =_validatePrepayment(i, ops[i], opInfo);
_validateAccountAndPaymasterValidationData(
i,
validationData,
pmValidationData,
address(0)
);
}
uint256 collected =0;
emitBeforeExecution();
for (uint256 i =0; i < opslen; i++) {
collected +=_executeUserOp(i, ops[i], opInfos[i]);
}
_compensate(beneficiary, collected);
}
}
// ...
}
查看原文 EntryPoint.sol 由 GitHub 提供支持
在第一个循环内部,它调用 _validatePrepayment
,该函数验证账户和支付者(如果定义)并确保总验证不超过 UserOperation 的 verificationGasLimit
。该函数内部调用 _validateAccountPrepayment
,负责通过 _createSenderIfNeeded
创建钱包合约。有趣的是,这允许 ERC-4337 钱包在部署之前就能在预定的反事实地址接收资产。
调动了第二个验证函数 _validateAccountAndPaymasterValidationData
,若账户或支付者的 validationData
过期则回退。
在第二个循环中,针对每个操作调用 _executeUserOp
,该函数调用 this.innerHandleOps
,在钱包合约上执行 UserOperation calldata
。
account-abstraction/contracts/core/EntryPoint.sol – Medium
contractEntryPoint {
// ...
function innerHandleOp(
bytes memory callData,
UserOpInfo memoryopInfo,
bytescalldatacontext
) external returns (uint256actualGasCost) {
uint256 preGas =gasleft();
require(msg.sender==address(this), "AA92 internal call only");
// ...
// 检查是否 handleOps 调用时 gas 限制过低。中止整个包
// ...
// 在钱包智能合约账户上执行 UserOperation 并若操作回退则触发事件
bool success = Exec.call(mUserOp.sender, 0, callData, callGasLimit);
// ...
// 执行后操作
return _handlePostOp(0, mode, opInfo, context, actualGas);
// ...
}
}
查看原文 EntryPoint.sol 由 GitHub 提供支持
后操作在 calldata
执行后立即被调用。如果定义了支付者且其验证返回非空上下文,则会调用其 postOp
。随后多余的金额会退还给账户(或支付者——如果它在请求中被使用)。
handleAggregatedOps
函数用于处理一批聚合的 UserOperations,并用收取的Gas费补偿 beneficiary
。
account-abstraction/contracts/core/EntryPoint.sol – Medium
contract EntryPoint {
// ...
function handleAggregatedOps(
UserOpsPerAggregator\[\] calldataopsPerAggregator,
address payablebeneficiary
) public nonReentrant {
uint256 opasLen = opsPerAggregator.length;
uint256 totalOps =0;
for (uint256 i =0; i < opasLen; i++) {
UserOpsPerAggregator calldata opa = opsPerAggregator\[i\];
UserOperation\[\] calldata ops = opa.userOps;
IAggregator aggregator = opa.aggregator;
// address(1) 是 “签名错误”的特别标记
require(
address(aggregator) !=address(1),
"AA96 无效的聚合器"
);
if (address(aggregator) !=address(0)) {
// solhint-disable-next-line no-empty-blocks
try aggregator.validateSignatures(ops, opa.signature) {} catch {
revertSignatureValidationFailed(address(aggregator));
}
}
totalOps += ops.length;
}
// ...
for (uint256 a =0; a < opasLen; a++) {
// ... _validatePrepayment
// ... _validateAccountAndPaymasterValidationData
}
// ...
for (uint256 a =0; a < opasLen; a++) {
// ... _executeUserOp
}
// ...
_compensate(beneficiary, collected);
}
// ...
}
EntryPoint.handleAggregatedOps
该函数与 handleOps
非常相似,主要区别在于它尝试使用聚合器的 validateSignatures
函数验证签名,然后再继续进行 _validatePrepayment
、_validateAccountAndPaymasterValidationData
和 _executeUserOp
。
为了确保在 handleOps
和 handleAggregatedOps
的 beneficiary
可以获得补偿,EntryPoint
需要管理实体的存款和保证金。为此,它从 StakeManager
合约扩展。
存款仅是用于支付用户操作的余额(由支付者或账户支付),而保证金则是支付者锁定的至少 unstakeDelay 值,这对于 声誉系统 是必要的。
这开始时可能会让人感到困惑,因为 ERC-4337 的一个主要构思是将账户需要以以太支付交易的要求抽象出来。实际上,所有的 UserOperations 必须在 EntryPoint 上有一个 ETH 存款才能被执行。特别之处在于,这可以通过代表反事实钱包的 depositTo
进行,或者由支付者进行。人们可以想象一个改进的用户体验,即用户通过法定货币支付存款,以便于他们的智能合约钱包得以部署。
ERC-4337 初始发布的 v0.4 将非ces 计算和检查的责任留给了钱包,因为这将为智能合约账户提供最大的灵活性,使其能够包含不同的非ces 方案。虽然鼓励使用非ces 进行重放检查,但这并未在任何地方强制执行。账户可以忽略非ces 字段,发送多个 UserOperations 使用相同的值,并使用不同的机制防止自己受到重放攻击,这将使 userOpHash
变为非唯一值。
这可能会对区块浏览器产生副作用,因为它们将不再有唯一标识符来查找和标记具体操作。也可能会让等待交易响应的应用感到困惑,这可能对应用和钱包的操作方式产生不利影响,正如在 这个 GitHub 问题 中讨论的那样。
因此,新的版本 v0.6 的 EntryPoint
继承了 NonceManager
来 处理账户的非ces,同时仍然保持足够的灵活性以启用不同的重放保护机制。
这也突显了 ERC-4337 的一个重要方面:EntryPoint 被期望随着时间的推移进行更新。Bundler 已经具备这一期望,因为它支持 eth_supportedEntryPoints
RPC 方法,该方法列出支持的 EntryPoint 版本。
作为所有 ERC-4337 的中心信任点,EntryPoint
合约需要进行非常严格的审计和形式化验证。尽管如此,预计这种架构会减少整个生态系统的审计和形式化验证负担。
单个 账户 需要做的工作量大大减少,因为它们只需验证 validateUserOp
函数及其“检查签名、递增非ces 和支付费用”的逻辑,并检查其他函数是否为 msg.sender == ENTRY_POINT
限制。
最后,EntryPoint 还必须确保 只有在 validateUserOp
通过的情况下才调用一个账户,而且如果它调用了 validateUserOp
并且通过了,它还必须进行的通用调用 calldata
等于 op.calldata
。这样做是为了 确保安全性,防范任意劫持和费用耗尽的风险。
在我们对 ERC-4337:使用 Alt Mempool 的账户抽象 的第二部分中,我们深入探讨了 EntryPoint 合约的复杂性——整个系统的基石。作为处理 UserOperations 的中心点,EntryPoint 保证了打包交易的有效执行。此外,从版本 v0.4 到 v0.6 的演变中,EntryPoint 现在强调了更严格的验证系统,同时仍然为自定义账户实现保留了灵活性。
请继续关注我们系列的 第三部分,我们将深入探讨 ERC-4337 的其他组件 及其工作流程。如果你对账户抽象的安全使用有任何问题,请随时与我们联系。
- 原文链接: medium.com/oak-security/...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!