{"content":{"title":"理解账户抽象 #2：使用Paymaster赞助交易","body":"> * 原文链接： https://www.alchemy.com/blog/account-abstraction-paymasters\r\n> * 译文出自：[登链翻译计划](https://github.com/lbc-team/Pioneer)\r\n> * 译者：[Tiny 熊](https://learnblockchain.cn/people/15)\r\n> * 本文永久链接：[learnblockchain.cn/article…](https://learnblockchain.cn/article/5432)\r\n\r\n\r\n\r\n在[本系列的第一篇](https://learnblockchain.cn/article/5426)中，我们从头开始创建了就有EOA的功能的智能合约钱包，并通过允许用户选择自己的自定义验证逻辑进行改进。但是现在，钱包仍然需要支付Gas，这意味着钱包所有者需要找到一种方法来获得一些ETH，然后才能在链上执行任何操作。\r\n\r\n如果我们想让钱包主人以外的其他人代替我们支付Gas呢？\r\n\r\n**有一些很好的理由支持我们加入这个功能：**\r\n\r\n- 如果钱包所有者是一个区块链新手，那么在执行链上操作之前需要获得ETH是一个巨大的绊脚石。\r\n- 一个dapp可能愿意为其方法支付Gas费，这样Gas费就不会吓跑潜在用户了\r\n- 一些赞助者（或项目方）可能会允许钱包以ETH以外的其他代币支付Gas费用，例如以USDC支付。\r\n- 为了保护隐私，用户可能想从混合器中提取资产到一个新的地址，并将Gas费用记入一个与他们无关的账户中\r\n\r\n## 引入Paymaster\r\n\r\n假设我是一个想为其他人支付Gas费的dapp。我可能不想在任何地方为每个人的Gas付费，所以我需要把自定义逻辑放到链上，它可以查看用户的操作并决定是否要为该操作付费。\r\n\r\n把自定义逻辑放到链上的方法是部署一个合约，我们称之为**paymaster（支付者或付款人）**。\r\n\r\n它将有一个方法来查看用户操作并决定是否愿意为该操作付费：\r\n\r\n```javascript\r\ncontract Paymaster {\r\n  function validatePaymasterOp(UserOperation op);\r\n}\r\n```\r\n\r\n然后，当一个钱包提交一个操作时，他们需要指出他们希望哪个支付方（如果有的话）来支付他们的Gas。\r\n\r\n我们将在 \"用户操作\"中添加一个新的字段来指定支付者。\r\n\r\n我们还将在用户操作中添加一个字段，钱包可以用它来向支付者传递任意的数据，以帮助它说服支付者支付其费用。\r\n\r\n例如，这可能是由支付者的所有者在链外签署的内容。\r\n\r\n```cpp\r\nstruct UserOperation {\r\n  // ...\r\n  address paymaster;\r\n  bytes paymasterData;\r\n}\r\n```\r\n\r\n接下来，我们将改变入口点的`handleOps`，以利用新的paymaster。\r\n\r\n**现在它的行为将是：**\r\n\r\n对于每个操作（op）:\r\n\r\n- 在操作者指定的钱包上调用validateOp。\r\n- 如果该操作有一个paymaster地址，那么对该paymaster调用`validatePaymasterOp`。\r\n- 任何验证失败的操作都会被丢弃。\r\n- 对于每个操作，在操作的发送者钱包上调用`executeOp`，跟踪我们使用了多少Gas，然后将ETH转账到执行者那里以支付这些Gas。如果该操作有一个paymaster字段，那么这个ETH来自paymaster。否则，它就像以前一样来自钱包。\r\n\r\n就像钱包一样，paymaster通过入口点（entrypoint）的存款方法存入他们的ETH，然后才能用于支付操作。\r\n\r\n![img](https://img.learnblockchain.cn/2023/02/22/65477.jpeg)\r\n\r\n>  执行者同时调用支付者合约和用户的智能合约钱包，以确定用户的交易是否可以被赞助。\r\n\r\n这其实是很简单的，对吗？\r\n\r\n我们只要让捆绑器更新它的模拟操作和...\r\n\r\n\r\n\r\n在[上一篇文章](https://learnblockchain.cn/article/5426)中，钱包向捆绑者退款，捆绑者会先模拟操作，试图避免执行验证失败的操作，因为这意味着钱包不会付款，所以捆绑者将承担Gas成本。\r\n\r\n**这里也出现了同样的问题：**\r\n\r\n捆绑者想避免提交验证失败的操作，因为paymaster不会付款，而捆绑者又要负担成本。\r\n\r\n起初，我们似乎可以对`validatePaymasterOp` 施加和`validateOp`同样的限制（即它只能访问钱包和它自己的相关存储，不能使用被禁止的操作代码），然后捆绑者可以在模拟钱包的`validateOp`的同时，简单地为用户操作模拟`validatePaymasterOp`。\r\n\r\n**但是，这里有一个问题**。\r\n\r\n因为存储限制说一个钱包的validateOp只能访问该钱包的相关存储，我们知道一个捆绑程序中的多个操作的验证不能相互干扰，只要它们来自不同的钱包，因为它们访问的共同存储非常少。\r\n\r\n但是一个paymaster的存储在捆绑中所有使用该paymaster的操作中是共享的。\r\n\r\n这意味着一个`validatePaymasterOp`的行为有可能导致使用同一个paymaster的交易包中的许多其他操作的验证失败。\r\n\r\n恶意的paymaster可以利用这一点来破坏系统。\r\n\r\n为了防止这种情况，我们引入了一个信誉系统。\r\n\r\n我们将让捆绑者跟踪每个paymaster最近验证失败的频率，并通过节流或禁止使用该paymaster的操作来惩罚那些经常失败的paymaster。\r\n\r\n如果一个恶意的paymaster可以创建许多自己的实例（ Sybil 攻击），这个信誉系统就不会起作用，所以我们要求paymaster用ETH做抵押。这样一来，它就不能从拥有多个账户中获益。\r\n\r\n\r\n\r\n**让我们在EntryPoint添加新的方法来处理抵押：**\r\n\r\n```javascript\r\ncontract EntryPoint {\r\n  // ...\r\n\r\n  function addStake() payable;\r\n  function unlockStake();\r\n  function withdrawStake(address payable destination);\r\n}\r\n```\r\n\r\n一旦投入了质押，在调用unlockStake后，要经过一定的延迟才能撤出。\r\n\r\n\r\n\r\n这些新方法有别于之前讨论的deposit和withdrawTo，后者是由钱包和paymaster用来添加ETH，这些ETH将被用来支付Gas，并且可以在任何时候立即提取。\r\n\r\n**质押规则有一个例外：**\r\n\r\n如果paymaster只访问过钱包的相关存储，而不是paymaster自己的存储，那么它就不需要质押，因为在此案例中，捆绑的多个操作所访问的存储不会相互重叠，原因与钱包的validateOp调用相同。\r\n\r\n实际上，我不认为信誉系统的详细规则有多么重要，如果想详细了解，你可以[在这里读到它们](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-4337.md#specification-2)，但只要你知道捆绑者将有一个机制来避免从一个“刚刚销毁自己的支付者”那里选择操作（OP），这就足够了。\r\n\r\n另外，每个捆绑者都会在本地跟踪信誉，所以如果一个捆绑者认为自己能做得更好，并且不会给其他捆绑者带来麻烦，那么它可以自由地实现自己的信誉逻辑。\r\n\r\n> 与许多质押模式不同，这里的赌注从未被削减。它们只是作为一种方式存在，要求潜在的攻击者锁定大量的资金来进行大规模的攻击。\r\n\r\n## 改进: paymaster `postOp`\r\n\r\n我们可以做一个小小的改进，让paymaster做得更多。现在，只有在验证步骤中，在操作实际运行之前，才会调用paymaster。\r\n\r\n但是，paymaster也可能需要根据操作的结果做一些不同的事情。\r\n\r\n例如，一个允许用户用美元支付Gas费的支付机构需要知道该操作实际使用了多少Gas，这样它就知道该收取多少美元。\r\n\r\n因此，我们将为paymaster添加一个新的方法`postOp`，在操作完成后，入口点将调用这个方法，并传递给它使用了多少Gas。\r\n\r\n我们还希望paymaster能够 \"向自己传递信息\"，并在`postOp`的步骤中使用这些在验证过程中计算的数据，因此我们将允许验证返回任意的 \"上下文\" 数据，这些数据将在以后被传递给`postOp`。\r\n\r\n**对postOp的第一次尝试将是这样的：**\r\n\r\n```php\r\ncontract Paymaster {\r\n  function validatePaymasterOp(UserOperation op) returns (bytes context);\r\n  function postOp(bytes context, uint256 actualGasCost);\r\n}\r\n```\r\n\r\n但是，对于想在最后以USDC收费的paymaster来说，有一些棘手的事情。\r\n\r\npaymaster在授权执行之前（在`validatePaymasterOp`中）可以检查用户是否有足够的USDC来支付该操作。但是，在执行过程中，执行操作完全有可能将钱包中所有的USDC 转出，这将意味着paymaster无法在最后提取付款。\r\n\r\n> paymaster能否通过在开始时收取最大数额的USDC，然后在结束时退还未使用的部分来避免这种情况？这也是可行的，但它很混乱：它需要两次转账调用，而不是一次，这就增加了Gas成本，并且会发出两个不同的转账事件。我们将看看我们是否能做得更好。\r\n\r\n我们需要一种方法，让paymaster在操作完成后导致操作失败，如果它这样做，它应该能够提取付款，因为无论发生什么，它在验证PaymasterOp时已经同意支付Gas。\r\n\r\n设置这个的方法是让入口点有可能调用`postOp`两次。\r\n\r\n入口点首先调用`postOp`，作为它刚刚运行钱包`executeOp`的同一个执行的一部分，因此，如果postOp 回退（revert），它会导致`executeOp`的所有执行也回退。\r\n\r\n如果发生这种情况，那么入口点就会再次调用`postOp`，但现在我们处于`executeOp`发生之前的情况，并且在这种情况下，我们刚刚检查了`validatePaymasterOp`，paymaster应该能够提取其应得的。\r\n\r\n为了给`postOp`提供更多的背景，我们将给它多一个参数：一个标志，以表明我们是否在它的 \"第二次运行\"中，因为它已经回退了一次。\r\n\r\n```php\r\ncontract Paymaster {\r\n  function validatePaymasterOp(UserOperation op) returns (bytes context);\r\n  function postOp(bool hasAlreadyReverted, bytes context, uint256 actualGasCost);\r\n}\r\n```\r\n\r\n## 回顾一下paymaster如何启用赞助交易\r\n\r\n为了让钱包主人以外的人支付Gas，我们引入了一个新的实体**paymaster**，这是部署了一个具有以下接口的智能合约：\r\n\r\n```php\r\ncontract Paymaster {\r\n  function validatePaymasterOp(UserOperation op) returns (bytes context);\r\n\tfunction postOp(bool hasAlreadyReverted, bytes context, uint256 actualGasCost);\r\n}\r\n```\r\n\r\n在用户操作中增加了新的字段，以允许钱包指定对应的paymaster。\r\n\r\n```cpp\r\nstruct UserOperation {\r\n  // ...\r\n  address paymaster;\r\n  bytes paymasterData;\r\n}\r\n```\r\n\r\npaymaster 将ETH存入EntryPoint，与钱包支付自己的Gas的方式相同。\r\n\r\n入口点合约更新其handleOps方法，以便对每个操作，除了通过钱包的`validateOp`进行钱包验证外，还通过paymaster的`validatePaymasterOp`对操作的paymaster（如果有）进行验证，然后执行操作，最后调用paymaster的`postOp`。\r\n\r\n为了处理模拟paymaster验证的一些作弊的问题，我们需要引入一个质押系统，paymaster会锁定ETH。\r\n\r\n\r\n\r\n这是通过加入一些**新的入口点方法来实现：**\r\n\r\n```javascript\r\ncontract EntryPoint {\r\n  // ...\r\n\r\n  function addStake() payable;\r\n  function unlockStake();\r\n  function withdrawStake(address payable destination);\r\n}\r\n```\r\n\r\n随着paymaster的加入，我们已经实现了大多数人在要求账户抽象时想到的所有功能!\r\n\r\n我们也已经非常接近[ERC-4337](https://eips.ethereum.org/EIPS/eip-4337)，现在已经感觉挺好了，但仍有一些我们需要的功能（请期待），以实现相同的效果。"},"author":{"user":"https://learnblockchain.cn/people/15","address":"0x49c8C7b892f82eedc98551CCb69745005A2FD079"},"history":null,"timestamp":1677037419,"version":1}