ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

OpenZeppelin Contracts 的 ERC-4337 担保式 Paymaster 修复:有效预存款(Effective Prefund)如何在扩展组合中正确传播

OpenZeppelin Contracts 的 ERC-4337 担保式 Paymaster 修复:有效预存款(Effective Prefund)如何在扩展组合中正确传播 OpenZeppelin Contracts 的 ERC-4337 担保式 Paymaster 修复有效预存款Effective Prefund如何在扩展组合中正确传播【免费下载链接】openzeppelin-contractsOpenZeppelin Contracts is a library for secure smart contract development.项目地址: https://gitcode.com/GitHub_Trending/op/openzeppelin-contracts导读本篇文章围绕 OpenZeppelin Contracts 仓库中一条 release changeset.changeset/paymaster-guarantor-effective-prefund.md展开剖析其对PaymasterERC20GuarantorERC-20 代币付费 第三方担保的 ERC-4337 Paymaster 扩展的一次关键修复_prefund改为返回由super._prefund传播回来的有效预存款金额而非调用方传入的输入金额。读完本文你将理解该修复解决的实际问题——下游扩展实际拉取的代币少于请求量时postOp 上下文中序列化的金额必须与真实扣款一致否则会导致退款路径超付或事件记账失真——并掌握从 changeset 回溯到源码实现与测试用例的完整证据链。一、这条 changeset 是什么一次 patch 级别的行为修复在 OpenZeppelin Contracts 仓库中.changeset/目录存放由 Changesets 工具管理的发布说明文件.changeset/config.json中changelog使用changesets/changelog-githubaccess为publicbaseBranch为master。每条文件在发布时会被合并进 CHANGELOG并驱动语义化版本号计算。本文件的 front matter 声明了版本影响--- openzeppelin-solidity: patch ---即这是一次patch补丁级变更代表向后兼容的缺陷修复不引入破坏性 API 变化。正文如下PaymasterERC20Guarantor: Return the effectiveprefundAmountpropagated bysuper._prefundinstead of the input amount, so extensions composed below the guarantor that pull less than requested serialize the actual pulled amount into the postOp context.翻译过来PaymasterERC20Guarantor._prefund现在返回由super._prefund传播回来的有效prefundAmount而不再返回输入金额这样位于担保人之下、实际拉取金额少于请求量的下游扩展就能把真实拉取量序列化进 postOp 上下文。这条变更关联的模块位于 contracts/account/paymaster/extensions/PaymasterERC20Guarantor.solv5.7.0。要理解它需要先弄清 Paymaster 的预扣–退款资金模型。二、背景PaymasterERC20 的预扣–退款模型与担保人扩展2.1 基础资金流先预扣最大成本执行后按实际成本退款PaymasterERC20contracts/account/paymaster/extensions/PaymasterERC20.sol让用户用 ERC-20 代币支付 gas遵循两阶段模型验证阶段_validatePaymasterUserOp按maxCost与 postOp 估算开销预扣最大可能成本_prefund执行transferFrom执行后阶段_postOp→_refund按 EntryPoint 报告的actualGasCost结算实际成本把prefundAmount - actualAmount退回。基础_prefund的实现PaymasterERC20.sol L161-L170恰好拉取请求的金额function _prefund( PackedUserOperation calldata /* userOp */, bytes32 /* userOpHash */, IERC20 token, uint256 /* tokenPerNative */, address prefunder_, uint256 prefundAmount_ ) internal virtual returns (bool success, address prefunder, uint256 prefundAmount, bytes memory prefundContext) { return (token.trySafeTransferFrom(prefunder_, address(this), prefundAmount_), prefunder_, prefundAmount_, ); }注意它的返回约定注释明确说明prefundAmount是effective prefund actually pulled实际拉取的有效金额。基础实现中请求量与拉取量一致但在扩展组合中二者可能分叉——这正是本次修复的切入点。2.2 担保人Guarantor扩展第三方代付 gasPaymasterERC20Guarantor是PaymasterERC20的扩展允许第三方担保人替用户预付 gas典型场景是空投领取担保人预先支付最大可能 gas 成本用户在操作执行中领到空投代币用户从空投所得中偿还担保人若用户无法偿还担保人吸收成本。担保人通过_fetchGuarantor(userOp)识别返回address(0)表示不使用担保功能。当存在担保人时_prefund会把预扣金额膨胀_guaranteedPostOpCost()默认 15_000 gas按maxFeePerGas折算成代币对应的成本覆盖担保人在_refund中的额外 postOp 工作向用户trySafeTransferFrom 向担保人trySafeTransfer将担保人设为 prefunder在prefundContext尾部追加userOp.sender供退款阶段识别发起者。修复后的_prefundPaymasterERC20Guarantor.sol L47-L84function _prefund( PackedUserOperation calldata userOp, bytes32 userOpHash, IERC20 token, uint256 tokenPrice, address prefunder_, uint256 prefundAmount_ ) internal virtual override returns (bool success, address prefunder, uint256 prefundAmount, bytes memory prefundContext) { address guarantor _fetchGuarantor(userOp); bool isGuaranteed guarantor ! address(0); if (isGuaranteed) { // _erc20Cost 可能返回 type(uint256).max 作为溢出哨兵saturatingAdd 保留它 // 让坏值到达 trySafeTransferFrom 时失败而不是在此处 revert。 uint256 guaranteedPostOpCost _erc20Cost(_guaranteedPostOpCost() * userOp.maxFeePerGas(), tokenPrice); prefundAmount_ prefundAmount_.saturatingAdd(guaranteedPostOpCost); prefunder_ guarantor; } (success, prefunder, prefundAmount, prefundContext) super._prefund( userOp, userOpHash, token, tokenPrice, prefunder_, prefundAmount_ ); if (prefunder guarantor) { emit UserOperationGuaranteed(userOpHash, prefunder, prefundAmount); } return (success, prefunder, prefundAmount, abi.encodePacked(prefundContext, userOp.sender)); }三、修复的核心返回有效金额而非输入金额3.1 问题所在扩展组合中拉取量可能少于请求量PaymasterERC20家族是典型的可组合扩展开发者可以在PaymasterERC20Guarantor之上再叠加其他扩展继承顺序如Mock → PaymasterERC20Guarantor → PaymasterERC20ReducingMock → PaymasterERC20。这些位于担保人之下super方向的扩展可能在_prefund中实际拉取的金额少于传入的请求量。仓库中的PaymasterERC20ReducingMockcontracts/mocks/account/paymaster/PaymasterERC20Mock.sol L194-L234就是这样一个模拟它在调用super._prefund时把金额减 1模拟一个固定减免fixed-credit策略——合法地以低于请求量的价格结算。修复前PaymasterERC20Guarantor._prefund的返回值把prefundAmount原样设为输入值prefundAmount_。一旦下游扩展实际只拉取了prefundAmount_ - 1担保人返回给上层调用者的却是完整的请求量。这一失真的金额随后被_validatePaymasterUserOpPaymasterERC20.sol L122-L145序列化进 contextabi.encodePacked(userOpHash, token, tokenPerNative, prefundAmount, prefunder, penaltyGas, prefundContext)一路传递到_postOpPaymasterERC20.sol L181-L217解码出prefundAmount作为退款基数。结果就是postOp 上下文记录的预存款比 Paymaster 账户里真实进入的代币多退款路径可能按虚高的基数执行例如向担保人退回超过实际持有的金额或者UserOperationSponsored事件记录的tokenAmount与真实结算不符。3.2 修复内容传播super._prefund的返回值修复只需一行关键改动将返回语句中的金额改为super._prefund传播回来的值——即上面代码中的(success, prefunder, prefundAmount, prefundContext) super._prefund(...)之后直接返回这个prefundAmount它已经是下游扩展层层修正后的有效值而不是构造返回值时重新使用输入变量prefundAmount_。由于PaymasterERC20._prefund的契约本身就是返回实际拉取的有效金额这个传播保证了整个组合链上的金额始终是真实进入 Paymaster 的代币量与prefundContext一起被序列化进 postOp context供_refund精确结算。从源码结构看这一修复同时保持了与PaymasterERC20._prefund文档契约的一致性PaymasterERC20.sol L149-L160Extensions may inflate the amount ... and must return the effective value修复让担保人扩展真正履行了返回有效值的约定。四、对称修复_refund同样传播有效结算金额值得注意本次 changeset 只提到_prefund但仓库中_refund已经实现了对称的传播逻辑PaymasterERC20Guarantor.sol L102-L146二者共同保证整条资金链路金额不失真非担保分支prefunder userOp.sender调用super._refund后返回returnedEffectiveAmount下游扩展实际结算的金额保证UserOperationSponsored.tokenAmount与真实结算一致担保分支prefunder ! userOp.sender把actualAmount膨胀上_guaranteedPostOpCost() * actualUserOpFeePerGas的代币成本先尝试从用户从prefundContext尾部读出的userOp.sendertrySafeTransferFrom拉取成功则把actualAmount归零让super把全部prefundAmount退给担保人失败则保留该金额由担保人吸收注意担保人吸收的是膨胀后的担保成本而非基础成本。Math.ternary(prefunder ! userOpSender, effectiveAmount, returnedEffectiveAmount)确保了返回值语义精确只有非担保分支才传播下游扩展的真实收费担保分支返回的是事件里记录的担保人膨胀金额。五、测试验证证据链完整闭环仓库测试 test/account/paymaster/PaymasterERC20Guarantor.test.js 为本次修复提供了直接验证其中propagates effective amounts from downstream extensions测试组L432-L516专门构造了组合Mock → PaymasterERC20Guarantor → PaymasterERC20ReducingMock → PaymasterERC205.1_prefund必须返回 super 实际拉取的金额测试_prefund returns the amount pulled by super, not the inputL454-L481请求requested 100nreducing 扩展实际拉取effective 99n断言$_prefund的返回值是(true, this.other.address, 99n, ...)即有效值并核对代币余额other归零、Paymaster 恰好持有99n。测试注释明确指出没有该修复时返回值会是requested100比真实进入 Paymaster 的多一枚代币这个虚高值会被序列化进 postOp context——与 changeset 描述的问题完全对应。5.2_refund对非担保操作传播实际收费测试_refund returns the amount charged by super for non-guaranteed operationsL483-L515验证对称路径输入actualAmount 40nreducing 扩展实际结算39n断言返回值是(true, 39n)且 Paymaster 余额恰好等于有效结算额。否则UserOperationSponsored.tokenAmount会与真实结算不一致。5.3 真实场景测试同一文件还覆盖了端到端流程用户成功偿还担保人L165-L235操作中先给用户铸币再授权 Paymaster用户偿还后担保人余额不变factor 0并断言UserOperationGuaranteed/UserOperationSponsored事件与代币、ETH 余额变动一致用户未能偿还L237-L299用户拿不到资产担保人余额减少factor -1Paymaster 吸收担保成本冷存储担保人L301-L354余额与授权全部是冷状态验证trySafeTransferFrom路径无效担保人签名L357-L371EntryPoint 以AA34 signature error拒绝担保人余额/授权不足L399-L429同样以AA34 signature error拒绝印证_prefund中不 revert、返回失败的设计失败以SIG_VALIDATION_FAILED形式体现避免在验证阶段 revert 损害 Paymaster 信誉——见 PaymasterERC20.sol L157-L159 的 NOTE。六、理解这条修复对使用者的意义6.1 对 Paymaster 开发者的影响升级成本patch 级变更无需改动接口但若你已基于PaymasterERC20Guarantor组合了拉取量少于请求量的下游扩展如折扣、减免策略此修复会让链上记账与真实资金流严格对齐属于正确性修复组合顺序担保人必须位于会削减金额的扩展之上super方向有效金额才能被正确传播回 context担保成本覆盖_guaranteedPostOpCost()默认 15_000 gas与PaymasterERC20._postOpCost()默认 30_000类似gas 更重的代币应覆盖为更高值否则会持续低估并损耗 Paymaster 存款。6.2 资金流全局回顾修复后完整链路为_validatePaymasterUserOp计算maxTokenCost含 postOp 成本与_postOpGasPenaltyPaymasterERC20Guarantor._prefund膨胀金额并调用super._prefund下游扩展削减实际拉取量super链逐层传播有效金额担保人把有效金额与prefundContext尾部附userOp.sender一并返回_validatePaymasterUserOp将其序列化进 context_postOp解码并调用_refund按有效prefundAmount精确退款、按有效actualAmount发出UserOperationSponsored。正是 changeset 中的这一行改动堵住了第 34 步之间金额失真的漏洞让整个组合链上的预存款始终如实入账。七、结论这条 patch changeset 是 OpenZeppelin Contracts 中小改动、深影响的典型表面只改了一处返回值实质修复了可组合扩展架构下资金记账失真的正确性问题。透过它可以看到PaymasterERC20家族设计的两个关键契约——_prefund返回有效拉取量、_refund返回有效结算量——以及它们在测试中的严格验证。相关实现与验证可继续参阅变更说明.changeset/paymaster-guarantor-effective-prefund.md担保人扩展实现contracts/account/paymaster/extensions/PaymasterERC20Guarantor.sol基础 ERC-20 Paymastercontracts/account/paymaster/extensions/PaymasterERC20.sol基础 Paymaster存款/质押/EntryPoint 约束contracts/account/paymaster/Paymaster.sol组合扩展测试桩Reducing/GurantorReducing Mockcontracts/mocks/account/paymaster/PaymasterERC20Mock.sol端到端与单元测试test/account/paymaster/PaymasterERC20Guarantor.test.js【免费下载链接】openzeppelin-contractsOpenZeppelin Contracts is a library for secure smart contract development.项目地址: https://gitcode.com/GitHub_Trending/op/openzeppelin-contracts创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表