From 9f7e122cf208f8cd21421dd038a72a1e7aa096e7 Mon Sep 17 00:00:00 2001 From: Dror Tirosh Date: Thu, 15 Aug 2024 14:52:59 +0300 Subject: [PATCH] Update ERC-4337: erc4337 updates Merged by EIP-Bot. --- ERCS/erc-4337.md | 93 ++++++++++++++++++++++++------------------------ 1 file changed, 47 insertions(+), 46 deletions(-) diff --git a/ERCS/erc-4337.md b/ERCS/erc-4337.md index e4a26772cb..49aadfa97b 100644 --- a/ERCS/erc-4337.md +++ b/ERCS/erc-4337.md @@ -76,14 +76,14 @@ To avoid Ethereum consensus changes, we do not attempt to create new transaction | `paymasterData` | `bytes` | Data for paymaster (only if paymaster exists) | | `signature` | `bytes` | Data passed into the account to verify authorization | -Users send `UserOperation` objects to a dedicated user operation mempool. The are not concerned with the packed version. +Users send `UserOperation` objects to a dedicated user operation mempool. They are not concerned with the packed version. A specialized class of actors called **bundlers** (either block builders running special-purpose code, or users that can relay transactions to block builders eg. through a bundle marketplace such as Flashbots that can guarantee next-block-or-never inclusion) listen in on the user operation mempool, and create **bundle transactions**. A bundle transaction packages up multiple `UserOperation` objects into a single `handleOps` call to a pre-published global **entry point contract**. To prevent replay attacks (both cross-chain and multiple `EntryPoint` implementations), the `signature` should depend on `chainid` and the `EntryPoint` address. ### EntryPoint definition -When passed to on-chain contacts (the EntryPoint contract, and then to account and paymaster), a packed version of the above structure is used: +When passed to on-chain contacts (the EntryPoint contract, and then to the account and paymaster), a packed version of the above structure is used: | Field | Type | Description | |----------------------|-----------|------------------------------------------------------------------------| @@ -132,12 +132,12 @@ The `userOpHash` is a hash over the userOp (except signature), entryPoint and ch The account: * MUST validate the caller is a trusted EntryPoint -* If the account does not support signature aggregation, it MUST validate the signature is a valid signature of the `userOpHash`, and +* If the account does not support signature aggregation, it MUST validate that the signature is a valid signature of the `userOpHash`, and SHOULD return SIG_VALIDATION_FAILED (and not revert) on signature mismatch. Any other error MUST revert. -* MUST pay the entryPoint (caller) at least the "missingAccountFunds" (which might be zero, in case current account's deposit is high enough) +* MUST pay the entryPoint (caller) at least the "missingAccountFunds" (which might be zero, in case the current account's deposit is high enough) * The account MAY pay more than this minimum, to cover future transactions (it can always issue `withdrawTo` to retrieve it) * The return value MUST be packed of `authorizer`, `validUntil` and `validAfter` timestamps. - * authorizer - 0 for valid signature, 1 to mark signature failure. Otherwise, an address of an authorizer contract. This ERC defines "signature aggregator" as authorizer. + * authorizer - 0 for valid signature, 1 to mark signature failure. Otherwise, an address of an authorizer contract. This ERC defines a "signature aggregator" as an authorizer. * `validUntil` is 6-byte timestamp value, or zero for "infinite". The UserOp is valid only up to this time. * `validAfter` is 6-byte timestamp. The UserOp is valid only after this time. @@ -208,7 +208,7 @@ this bundler is supposed to track the `key` and `sequence` pair of the UserOpera In some cases, an account may need to have an "administrative" channel of operations running in parallel to normal operations. - In this case, the account may use specific `key` when calling methods on the account itself: + In this case, the account may use a specific `key` when calling methods on the account itself: ```solidity bytes4 sig = bytes4(userOp.callData[0 : 4]); @@ -224,7 +224,7 @@ this bundler is supposed to track the `key` and `sequence` pair of the UserOpera There are 2 separate entry point methods: `handleOps` and `handleAggregatedOps` -* `handleOps` handle userOps of accounts that don't require any signature aggregator. +* `handleOps` handles userOps of accounts that don't require any signature aggregator. * `handleAggregatedOps` can handle a batch that contains userOps of multiple aggregators (and also requests without any aggregator) * `handleAggregatedOps` performs the same logic below as `handleOps`, but it must transfer the correct aggregator to each userOp, and also must call `validateSignatures` on each aggregator before doing all the per-account validation. The entry point's `handleOps` function must perform the following steps (we first describe the simpler non-paymaster case). It must make two loops, the **verification loop** and the **execution loop**. In the verification loop, the `handleOps` call must perform the following steps for each `UserOperation`: @@ -279,7 +279,7 @@ enum PostOpMode { } ``` -The EntryPoint must implement the following API to let entities like paymasters to have a stake, and thus have more flexibility in their storage access (see [reputation, throttling and banning section](#reputation-scoring-and-throttlingbanning-for-global-entities) for details.) +The EntryPoint must implement the following API to let entities like paymasters have a stake, and thus have more flexibility in their storage access (see [reputation, throttling and banning section](#reputation-scoring-and-throttlingbanning-for-global-entities) for details.) ```solidity // add a stake to the calling entity @@ -295,7 +295,7 @@ function withdrawStake(address payable withdrawAddress) external The paymaster must also have a deposit, which the entry point will charge UserOperation costs from. The deposit (for paying gas fees) is separate from the stake (which is locked). -The EntryPoint must implement the following interface to allow paymasters (and optionally accounts) manage their deposit: +The EntryPoint must implement the following interface to allow paymasters (and optionally accounts) to manage their deposit: ```c++ // return the deposit of an account @@ -313,9 +313,9 @@ function withdrawTo(address payable withdrawAddress, uint256 withdrawAmount) ext When a client receives a `UserOperation`, it must first run some basic sanity checks, namely that: * Either the `sender` is an existing contract, or the `initCode` is not empty (but not both) -* If `initCode` is not empty, parse its first 20 bytes as a factory address. Record whether the factory is staked, in case the later simulation indicates that it needs to be. If the factory accesses global state, it must be staked - see [reputation, throttling and banning section](#reputation-scoring-and-throttlingbanning-for-global-entities) for details. +* If `initCode` is not empty, parse its first 20 bytes as a factory address. Record whether the factory is staked, in case the later simulation indicates that it needs to be. If the factory accesses the global state, it must be staked - see [reputation, throttling and banning section](#reputation-scoring-and-throttlingbanning-for-global-entities) for details. * The `verificationGasLimit` is sufficiently low (`<= MAX_VERIFICATION_GAS`) and the `preVerificationGas` is sufficiently high (enough to pay for the calldata gas cost of serializing the `UserOperation` plus `PRE_VERIFICATION_OVERHEAD_GAS`) -* The `paymasterAndData` is either empty, or start with the **paymaster** address, which is a contract that (i) currently has nonempty code on chain, (ii) has a sufficient deposit to pay for the UserOperation, and (iii) is not currently banned. During simulation, the paymaster's stake is also checked, depending on its storage usage - see [reputation, throttling and banning section](#reputation-scoring-and-throttlingbanning-for-global-entities) for details. +* The `paymasterAndData` is either empty, or starts with the **paymaster** address, which is a contract that (i) currently has nonempty code on chain, (ii) has a sufficient deposit to pay for the UserOperation, and (iii) is not currently banned. During simulation, the paymaster's stake is also checked, depending on its storage usage - see [reputation, throttling and banning section](#reputation-scoring-and-throttlingbanning-for-global-entities) for details. * The callgas is at least the cost of a `CALL` with non-zero value. * The `maxFeePerGas` and `maxPriorityFeePerGas` are above a configurable minimum value that the client is willing to accept. At the minimum, they are sufficiently high to be included with the current `block.basefee`. * The sender doesn't have another `UserOperation` already present in the pool (or it replaces an existing entry with the same sender and nonce, with a higher `maxPriorityFeePerGas` and an equally increased `maxFeePerGas`). Only one `UserOperation` per sender may be included in a single batch. A sender is exempt from this rule and may have multiple `UserOperations` in the pool and in a batch if it is staked (see [reputation, throttling and banning section](#reputation-scoring-and-throttlingbanning-for-global-entities) below), but this exception is of limited use to normal accounts. @@ -340,12 +340,12 @@ interface IAggregator { * An account signifies it uses signature aggregation returning its address from `validateUserOp`. * During `simulateValidation`, this aggregator is returned to the bundler as part of the `aggregatorInfo` struct. -* The bundler should first accept the aggregator (aggregators must be staked. bundler should verify it is not throttle/banned) +* The bundler should first accept the aggregator (aggregators must be staked. bundler should verify it is not throttled/banned) * To accept the UserOp, the bundler must call **validateUserOpSignature()** to validate the userOp's signature. This method returned an alternate signature (usually empty) that should be used during bundling. * The bundler MUST call `validateUserOp` a second time on the account with the UserOperation using that returned signature, and make sure it returns the same value. * **aggregateSignatures()** must aggregate all UserOp signatures into a single value. -* Note that the above methods are helper method for the bundler. The bundler MAY use a native library to perform the same validation and aggregation logic. +* Note that the above methods are helper methods for the bundler. The bundler MAY use a native library to perform the same validation and aggregation logic. * **validateSignatures()** MUST validate the aggregated signature matches for all UserOperations in the array, and revert otherwise. This method is called on-chain by `handleOps()` @@ -353,11 +353,11 @@ interface IAggregator { #### Simulation Rationale -In order to add a UserOperation into the mempool (and later to add it into a bundle) we need to "simulate" its validation to make sure it is valid, and that it is capable of paying for its own execution. +To add a UserOperation into the mempool (and later to add it into a bundle) we need to "simulate" its validation to make sure it is valid, and that it pays for its own execution. In addition, we need to verify that the same will hold true when executed on-chain. For this purpose, a UserOperation is not allowed to access any information that might change between simulation and execution, such as current block time, number, hash etc. -In addition, a UserOperation is only allowed to access data related to this sender address: Multiple UserOperations should not access the same storage, so that it is impossible to invalidate a large number of UserOperations with a single state change. -There are 3 special contracts that interact with the account: the factory (initCode) that deploys the contract, the paymaster that can pay for the gas, and signature aggregator (described later) +In addition, a UserOperation is only allowed to access data related to this sender address: Multiple UserOperations should not access the same storage, so it is impossible to invalidate a large number of UserOperations with a single state change. +There are 3 special contracts that interact with the account: the factory (initCode) that deploys the contract, the paymaster that can pay for the gas, and a signature aggregator (described later) Each of these contracts is also restricted in its storage access, to make sure UserOperation validations are isolated. #### Simulation Specification: @@ -419,15 +419,15 @@ Either return value may contain a "validAfter" and "validUntil" timestamps, whic A node MAY drop a UserOperation if it expires too soon (e.g. wouldn't make it to the next block) by either the account or paymaster. If the `ValidationResult` includes `sigFail`, the client SHOULD drop the `UserOperation`. -In order to prevent DoS attack on bundlers, they must make sure the validation methods above pass the validation rules, which constraint their usage of opcodes and storage. +To prevent DoS attacks on bundlers, they must make sure the validation methods above pass the validation rules, which constrain their usage of opcodes and storage. For the complete procedure see [ERC-7562](./eip-7562.md) ### Alternative Mempools The simulation rules above are strict and prevent the ability of paymasters and signature aggregators to grief the system. -However, there might be use-cases where specific paymasters (and signature aggregators) can be validated +However, there might be use cases where specific paymasters (and signature aggregators) can be validated (through manual auditing) and verified that they cannot cause any problem, while still require relaxing of the opcode rules. -A bundler cannot simply "whitelist" request from a specific paymaster: if that paymaster is not accepted by all +A bundler cannot simply "whitelist" a request from a specific paymaster: if that paymaster is not accepted by all bundlers, then its support will be sporadic at best. Instead, we introduce the term "alternate mempool": a modified validation rules, and procedure of propagating them to other bundlers. @@ -435,7 +435,7 @@ The procedure of using alternate mempools is defined in [ERC-7562](./eip-7562.md ### Bundling -Bundling is the process where a node/bundler collects multiple UserOperations and create a single transaction to submit on-chain. +Bundling is the process where a node/bundler collects multiple UserOperations and creates a single transaction to submit on-chain. During bundling, the bundler should: @@ -475,42 +475,42 @@ When a bundler includes a bundle in a block it must ensure that earlier transact ### Error codes. While performing validation, the EntryPoint must revert on failures. During simulation, the calling bundler MUST be able to determine which entity (factory, account or paymaster) caused the failure. -The attribution of revert to entity is done using the call-tracing: the last entity called by the EntryPoint prior the revert is the entity that caused the revert. +The attribution of a revert to an entity is done using call-tracing: the last entity called by the EntryPoint prior to the revert is the entity that caused the revert. * For diagnostic purposes, the EntryPoint must only revert with explicit FailedOp() or FailedOpWithRevert() errors. * The message of the error starts with event code, AA## -* Event code starting with "AA1" signify an error during account creation -* Event code starting with "AA2" signify an error during account validation (validateUserOp) -* Event code starting with "AA3" signify an error during paymaster validation (validatePaymasterUserOp) +* Event code starting with "AA1" signifies an error during account creation +* Event code starting with "AA2" signifies an error during account validation (validateUserOp) +* Event code starting with "AA3" signifies an error during paymaster validation (validatePaymasterUserOp) ## Rationale -The main challenge with a purely smart contract wallet based account abstraction system is DoS safety: how can a block builder including an operation make sure that it will actually pay fees, without having to first execute the entire operation? +The main challenge with a purely smart contract wallet-based account abstraction system is DoS safety: how can a block builder including an operation make sure that it will actually pay fees, without having to first execute the entire operation? Requiring the block builder to execute the entire operation opens a DoS attack vector, as an attacker could easily send many operations that pretend to pay a fee but then revert at the last moment after a long execution. Similarly, to prevent attackers from cheaply clogging the mempool, nodes in the P2P network need to check if an operation will pay a fee before they are willing to forward it. -The first step is clean separation between validation (acceptance of UserOperation, and acceptance to pay) and execution. -In this proposal, we expect accounts to have a `validateUserOp` method that takes as input a `UserOperation`, and verify the signature and pay the fee. +The first step is a clean separation between validation (acceptance of UserOperation, and acceptance to pay) and execution. +In this proposal, we expect accounts to have a `validateUserOp` method that takes as input a `UserOperation`, verifies the signature and pays the fee. Only if this method returns successfully, the execution will happen. The entry point-based approach allows for a clean separation between verification and execution, and keeps accounts' logic simple. It enforces the simple rule that only after validation is successful (and the UserOp can pay), the execution is done, and also guarantees the fee payment. ### Validation Rules Rationale -The next step is protecting the bundlers from denial-of-service attacks by a mass number of UserOperation that appear to be valid (and pay) but that eventually revert, and thus block the bundler from processing valid UserOperations. +The next step is protecting the bundlers from denial-of-service attacks by a mass number of UserOperations that appear to be valid (and pay) but that eventually revert, and thus block the bundler from processing valid UserOperations. There are two types of UserOperations that can fail validation: -1. UserOperations that succeed in initial validation (and accepted into the mempool), but relay on environment state to fail later when attempting to include them in a block. +1. UserOperations that succeed in initial validation (and accepted into the mempool), but rely on the environment state to fail later when attempting to include them in a block. 2. UserOperations that are valid when checked independently, by fail when bundled together to be put on-chain. To prevent such rogue UserOperations, the bundler is required to follow a set of [restrictions on the validation function](./eip-7562.md), to prevent such denial-of-service attacks. ### Reputation Rationale. -UserOperation's storage access rules prevent them from interfere with each other. -But "global" entities - paymasters, factories and aggregators are accessed by multiple UserOperations, and thus might invalidate multiple previously-valid UserOperations. +UserOperation's storage access rules prevent them from interfering with each other. +But "global" entities - paymasters, factories and aggregators are accessed by multiple UserOperations, and thus might invalidate multiple previously valid UserOperations. -To prevent abuse, we throttle down (or completely ban for a period of time) an entity that causes invalidation of large number of UserOperations in the mempool. -To prevent such entities from "sybil-attack", we require them to stake with the system, and thus make such DoS attack very expensive. -Note that this stake is never slashed, and can be withdrawn any time (after unstake delay) +To prevent abuse, we throttle down (or completely ban for a period of time) an entity that causes invalidation of a large number of UserOperations in the mempool. +To prevent such entities from "Sybil-attack", we require them to stake with the system, and thus make such DoS attack very expensive. +Note that this stake is never slashed, and can be withdrawn at any time (after unstake delay) Unstaked entities are allowed, under the rules below. @@ -521,11 +521,11 @@ The stake value is not enforced on-chain, but specifically by each node while si ### Reputation scoring and throttling/banning for global entities [ERC-7562] defines a set of rules a bundler must follow when accepting UserOperations into the mempool. -It also descrbies the "reputation|" +It also descrbies the "reputation" ### Paymasters -Paymaster contracts allow abstraction of gas: having a contract, that is not the sender of the transaction, pay for the transaction fees. +Paymaster contracts allow the abstraction of gas: having a contract, that is not the sender of the transaction, to pay for the transaction fees. Paymaster architecture allows them to follow the model of "pre-charge, and later refund". E.g. a token-paymaster may pre-charge the user with the max possible price of the transaction, and refund the user with the excess afterwards. @@ -538,7 +538,7 @@ The wallet creation itself is done by a "factory" contract, with wallet-specific The factory is expected to use CREATE2 (not CREATE) to create the wallet, so that the order of creation of wallets doesn't interfere with the generated addresses. The `initCode` field (if non-zero length) is parsed as a 20-byte address, followed by "calldata" to pass to this address. This method call is expected to create a wallet and return its address. -If the factory does use CREATE2 or some other deterministic method to create the wallet, it's expected to return the wallet address even if the wallet has already been created. This is to make it easier for clients to query the address without knowing if the wallet has already been deployed, by simulating a call to `entryPoint.getSenderAddress()`, which calls the factory under the hood. +If the factory does use CREATE2 or some other deterministic method to create the wallet, it's expected to return the wallet address even if the wallet has already been created. This comes to make it easier for clients to query the address without knowing if the wallet has already been deployed, by simulating a call to `entryPoint.getSenderAddress()`, which calls the factory under the hood. When `initCode` is specified, if either the `sender` address points to an existing contract, or (after calling the initCode) the `sender` address still does not exist, then the operation is aborted. The `initCode` MUST NOT be called directly from the entryPoint, but from another address. @@ -547,7 +547,7 @@ For security reasons, it is important that the generated contract address will d This way, even if someone can create a wallet at that address, he can't set different credentials to control it. The factory has to be staked if it accesses global storage - see [reputation, throttling and banning section](#reputation-scoring-and-throttlingbanning-for-global-entities) for details. -NOTE: In order for the wallet to determine the "counterfactual" address of the wallet (prior its creation), +NOTE: In order for the wallet to determine the "counterfactual" address of the wallet (prior to its creation), it should make a static call to the `entryPoint.getSenderAddress()` ### Entry point upgrading @@ -580,7 +580,7 @@ The result `SHOULD` be set to the **userOpHash** if and only if the request pass * The `message` field SHOULD be set to the revert message from the paymaster * The `data` field MUST contain a `paymaster` value * **code: -32502** - transaction rejected because of opcode validation - * **code: -32503** - UserOperation out of time-range: either wallet or paymaster returned a time-range, and it is already expired (or will expire soon) + * **code: -32503** - UserOperation out of time-range: either wallet or paymaster returned a time-range, and it has already expired (or will expire soon) * The `data` field SHOULD contain the `validUntil` and `validAfter` values * The `data` field SHOULD contain a `paymaster` value, if this error was triggered by the paymaster * **code: -32504** - transaction rejected because paymaster (or signature aggregator) is throttled/banned @@ -591,6 +591,7 @@ The result `SHOULD` be set to the **userOpHash** if and only if the request pass * **code: -32506** - transaction rejected because wallet specified unsupported signature aggregator * The `data` field SHOULD contain an `aggregator` value * **code: -32507** - transaction rejected because of wallet signature check failed (or paymaster signature, if the paymaster uses its data as signature) + * **code: -32508** - transaction rejected because paymaster balance can't cover all pending UserOperations. ##### Example: @@ -669,7 +670,7 @@ Response: Estimate the gas values for a UserOperation. Given UserOperation optionally without gas limits and gas prices, return the needed gas limits. -The signature field is ignored by the wallet, so that the operation will not require user's approval. +The signature field is ignored by the wallet, so that the operation will not require the user's approval. Still, it might require putting a "semi-valid" signature (e.g. a signature in the right length) **Parameters**: @@ -733,9 +734,9 @@ Return a UserOperation receipt based on a hash (userOpHash) returned by `eth_sen * **sender** * **nonce** * **paymaster** the paymaster used for this userOp (or empty) -* **actualGasCost** - actual amount paid (by account or paymaster) for this UserOperation +* **actualGasCost** - the actual amount paid (by account or paymaster) for this UserOperation * **actualGasUsed** - total gas used by this UserOperation (including preVerification, creation, validation and execution) -* **success** boolean - did this execution completed without revert +* **success** boolean - did this execution completed without a revert * **reason** in case of revert, this is the revert reason * **logs** the logs generated by this UserOperation (not including logs of other UserOperations in the same bundle) * **receipt** the TransactionReceipt object. @@ -788,7 +789,7 @@ Returns [EIP-155](./eip-155.md) Chain ID. ### RPC methods (debug Namespace) -This api must only be available on testing mode and is required by the compatibility test suite. In production, any `debug_*` rpc calls should be blocked. +This api must only be available in testing mode and is required by the compatibility test suite. In production, any `debug_*` rpc calls should be blocked. #### * debug_bundler_clearState @@ -906,7 +907,7 @@ After setting mode to "manual", an explicit call to debug_bundler_sendBundleNow #### * debug_bundler_setReputation -Sets reputation of given addresses. parameters: +Sets the reputation of given addresses. parameters: **Parameters:** @@ -914,7 +915,7 @@ Sets reputation of given addresses. parameters: * `address` - The address to set the reputation for. * `opsSeen` - number of times a user operations with that entity was seen and added to the mempool - * `opsIncluded` - number of times a user operations that uses this entity was included on-chain + * `opsIncluded` - number of times user operations that use this entity was included on-chain * **EntryPoint** the entrypoint used by eth_sendUserOperation @@ -961,7 +962,7 @@ An array of reputation entries with the fields: * `address` - The address to set the reputation for. * `opsSeen` - number of times a user operations with that entity was seen and added to the mempool -* `opsIncluded` - number of times a user operations that uses this entity was included on-chain +* `opsIncluded` - number of times user operation that use this entity was included on-chain * `status` - (string) The status of the address in the bundler 'ok' | 'throttled' | 'banned'. ```json=