From 012dd72683d1668a39981ed7d54d3ef2231b7cba Mon Sep 17 00:00:00 2001 From: Dror Tirosh Date: Mon, 18 Dec 2023 20:23:45 +0200 Subject: [PATCH 01/20] update validation rules --- erc/ERCS/erc-4337.md | 26 +++++--- erc/ERCS/erc-7562.md | 145 ++++++++++++++++++++++++++++++++++++------- 2 files changed, 139 insertions(+), 32 deletions(-) diff --git a/erc/ERCS/erc-4337.md b/erc/ERCS/erc-4337.md index 9d4a3f7e..7e038ac0 100644 --- a/erc/ERCS/erc-4337.md +++ b/erc/ERCS/erc-4337.md @@ -8,7 +8,7 @@ status: Draft type: Standards Track category: ERC created: 2021-09-29 -requires: eip-aa-rules +requires: 7562 --- ## Abstract @@ -29,8 +29,8 @@ This proposal takes a different approach, avoiding any adjustments to the consen * **Do not require any Ethereum consensus changes**: Ethereum consensus layer development is focusing on the merge and later on scalability-oriented features, and there may not be any opportunity for further protocol changes for a long time. Hence, to increase the chance of faster adoption, this proposal avoids Ethereum consensus changes. * **Try to support other use cases** * Privacy-preserving applications - * Atomic multi-operations (similar goal to [EIP-3074](./eip-3074.md)) - * Pay tx fees with [ERC-20](./eip-20.md) tokens, allow developers to pay fees for their users, and [EIP-3074](./eip-3074.md)-like **sponsored transaction** use cases more generally + * Atomic multi-operations (similar goal to [EIP-3074]) + * Pay tx fees with [ERC-20](./erc-20.md) tokens, allow developers to pay fees for their users, and [EIP-3074]-like **sponsored transaction** use cases more generally * Support aggregated signature (e.g. BLS) ## Specification @@ -245,7 +245,7 @@ A node/bundler SHOULD drop (not add to the mempool) a `UserOperation` that fails ### Extension: paymasters -We extend the entry point logic to support **paymasters** that can sponsor transactions for other users. This feature can be used to allow application developers to subsidize fees for their users, allow users to pay fees with [ERC-20](./eip-20.md) tokens and many other use cases. When the paymasterAndData field in the UserOp is not empty, the entry point implements a different flow for that UserOperation: +We extend the entry point logic to support **paymasters** that can sponsor transactions for other users. This feature can be used to allow application developers to subsidize fees for their users, allow users to pay fees with [ERC-20](./erc-20.md) tokens and many other use cases. When the paymasterAndData field in the UserOp is not empty, the entry point implements a different flow for that UserOperation: ![](../assets/erc-4337/bundle-seq-pm.svg) @@ -385,7 +385,7 @@ A node MAY drop a UserOperation if it expires too soon (e.g. wouldn't make it to 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. -For the complete procedure see [eip-aa-validation-rules](./eip-aa-rules.md) +For the complete procedure see [ERC-7652](./erc-7652.md) #### Alternative Mempools @@ -396,7 +396,7 @@ A bundler cannot simply "whitelist" request from a specific paymaster: if that p 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. -The procedure of using alternate mempools is defined in ../eip-aa-rules.md#Alt-mempools-rules +The procedure of using alternate mempools is defined in ./erc-7652.md#Alt-mempools-rules ### Bundling @@ -434,6 +434,16 @@ Otherwise, attackers may be able to use the banned opcodes to detect running on- When a bundler includes a bundle in a block it must ensure that earlier transactions in the block don't make any UserOperation fail. It should either use access lists to prevent conflicts, or place the bundle as the first transaction in the block. + +## 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? 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. + +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. This method is required to be almost-pure: it is only allowed to access the storage of the account itself, cannot use environment opcodes (eg. `TIMESTAMP`), and can only edit the storage of the account, and can also send out ETH (needed to pay the entry point). The method is gas-limited by the `verificationGasLimit` of the `UserOperation`; nodes can choose to reject operations whose `verificationGasLimit` is too high. These restrictions allow block builders and network nodes to simulate the verification step locally, and be confident that the result will match the result when the operation actually gets included into a block. + +The entry point-based approach allows for a clean separation between verification and execution, and keeps accounts' logic simple. The alternative would be to require accounts to follow a template where they first self-call to verify and then self-call to execute (so that the execution is sandboxed and cannot cause the fee payment to revert); template-based approaches were rejected due to being harder to implement, as existing code compilation and verification tooling is not designed around template verification. + + ### Reputation scoring and throttling/banning for global entities #### Reputation Rationale. @@ -939,7 +949,7 @@ Assume the given UserOperations all pass validation (without actually validating ## Backwards Compatibility -This EIP does not change the consensus layer, so there are no backwards compatibility issues for Ethereum as a whole. Unfortunately it is not easily compatible with pre-[ERC-4337](./eip-4337.md) accounts, because those accounts do not have a `validateUserOp` function. If the account has a function for authorizing a trusted op submitter, then this could be fixed by creating an [ERC-4337](./eip-4337.md) compatible account that re-implements the verification logic as a wrapper and setting it to be the original account's trusted op submitter. +This ERC does not change the consensus layer, so there are no backwards compatibility issues for Ethereum as a whole. Unfortunately it is not easily compatible with pre-[ERC-4337](./eip-4337.md) accounts, because those accounts do not have a `validateUserOp` function. If the account has a function for authorizing a trusted op submitter, then this could be fixed by creating an [ERC-4337](./eip-4337.md) compatible account that re-implements the verification logic as a wrapper and setting it to be the original account's trusted op submitter. ## Reference Implementation @@ -947,7 +957,7 @@ See `https://github.com/eth-infinitism/account-abstraction/tree/main/contracts` ## Security Considerations -The entry point contract will need to be very heavily audited and formally verified, because it will serve as a central trust point for _all_ [ERC-4337](./eip-4337.md). In total, this architecture reduces auditing and formal verification load for the ecosystem, because the amount of work that individual _accounts_ have to do becomes much smaller (they need only verify the `validateUserOp` function and its "check signature and pay fees" logic) and check that other functions are `msg.sender == ENTRY_POINT` gated (perhaps also allowing `msg.sender == self`), but it is nevertheless the case that this is done precisely by concentrating security risk in the entry point contract that needs to be verified to be very robust. +The entry point contract will need to be very heavily audited and formally verified, because it will serve as a central trust point for _all_ [ERC-4337]. In total, this architecture reduces auditing and formal verification load for the ecosystem, because the amount of work that individual _accounts_ have to do becomes much smaller (they need only verify the `validateUserOp` function and its "check signature and pay fees" logic) and check that other functions are `msg.sender == ENTRY_POINT` gated (perhaps also allowing `msg.sender == self`), but it is nevertheless the case that this is done precisely by concentrating security risk in the entry point contract that needs to be verified to be very robust. Verification would need to cover two primary claims (not including claims needed to protect paymasters, and claims needed to establish p2p-level DoS resistance): diff --git a/erc/ERCS/erc-7562.md b/erc/ERCS/erc-7562.md index c75a3306..1ffeeb5b 100644 --- a/erc/ERCS/erc-7562.md +++ b/erc/ERCS/erc-7562.md @@ -1,51 +1,102 @@ --- -eip: 0 +eip: 7562 title: Account Abstraction Validation Scope Rules -description: A set of limitations on validation EVM code that protects Account Abstraction nodes from exponentially complex computations without compensation +description: A set of limitations on validation EVM code to protect Account Abstraction nodes from computation without compensation author: Yoav Weiss (@yoavw), Dror Tirosh (@drortirosh), Alex Forshtat (@forshtat), Shahaf Nacson (@shahafn) -discussions-to: https://www.google.com/ +discussions-to: https://ethereum-magicians.org/t/erc-7562-account-abstraction-validation-scope-rules/16683 status: Draft type: Standards Track -category: Core +category: ERC created: 2023-09-01 -requires: 6780 --- ## Abstract This document describes the validation rules we impose on the validation context of Account Abstraction transactions, -such as [ERC-4337](./eip-4337) `UserOperation` or [EIP-9999](./eip-9999), which are enforced off-chain by a +such as [ERC-4337](./eip-4337) `UserOperation` or RIP-7560 (Native Account Abstraction), which are enforced off-chain by a block builder or a standalone bundler, and the rationale behind each one of them. ## Motivation -All transactions initiated by EOAs have an implicit validation phase where balance, nonce, and signature are -checked to be valid for the current state of the Ethereum blockchain. -Once the transaction is checked to be valid by a node, only then another transaction by the same EOA can modify the Ethereum -state in a way that makes the first transaction invalid. +With Account-Abstraction, instead of hard-coded logic for processing a transaction (validation, gas-payment and execution), this logic is executed by EVM code. +The benefits for account are countless - +- abstracting the validation allows contreact to use different signature schemes, multisig configuration, custom recovery and more. +- abstracting gas payments allows easy on-boarding by 3rd party payments, paying with tokens, cross-chain gas payments +- abstracting execution allows batch transations -With Account Abstraction, however, the validation can also include an arbitrary EVM code and rely on storage as well, -which means that unrelated `UserOperations` or transactions may invalidate each other. +All of the above are missing from the EOA account model. -If not addressed, this would make the job of maintaining a mempool of valid `UserOperations` and producing valid -bundles computationally infeasible and susceptible to DoS attacks. +However, there is one rule a transaction must follow to preserve the decentralized network: once submitted into the network (the mempool), the transaction is guaranteed to pay. This comes to prevent denial of service attack on the network. -This document describes a set of validation rules that, if applied by a bundler before accepting a `UserOperation` -into the mempool can prevent such attacks. +The EOA model implicitly follows the rule: a valid transaction can't become invalid without payment by the account: e.g account balance can't be reduced (except with a higher paying transaction) -## Specification +For Account-Abstraction system to be sustainable and DoS-protected, we define a set of rules to protect it. + +For the actual interfaces of those contract-based accounts see the defintions in ERC-4337 and RIP-7560. + +This documentation uses the terminology "UserOperation" for a transaction created by a smart contract account, and closely follows [ERC-4337](./erc-4337.md) terminology. +However, the rules apply to any account-abstraction framework that uses ERC code to perform transaction validation + +## Rationale + +### The high level goal + +The purpose of this specification is to define a concensus between nodes (bundlers or block-builders) when processing incoming UserOperations from external source. +This external source for UserOperations is either an end-user node (via RPC) or another node in the p2p network. + +The protocol tries to detect "spam" - which are large bursts of UserOperations that cannot be included on chain (that thus can't pay). +The network is protected by throtltling down requessts from such spammer nodes. + +It is important that all nodes in the network all have the same notion of a "spam": if some nodes accepts some type of UserOperations while other consider them a spam, quickly those "forgiving" nodes will be considered "spammers" by the rest of the nodes, and the network effectively gets splitted. + +### The processing flow of a UserOperation + +- First, a UserOperation is received - either via RPC (submitted on behalf of a single application) or via the p2p protocol, from another node in the mempool. +- The node performs validation on the UserOperation, and then add it to its in-memory mempool, and submit it to its peers. +- Lastly, when building a block, a node collect UserOperations from the mempool, performs a 2nd validation to make sure they all still valid as a bundle, and submit them into the next block. + +### The need for 2nd validation before submitting a block + +A normal ethereum transaction in the mempool can be invalidated if another transaction was received with the same nonce. That other transaction had to increase the gas price in order to replace the first one, so it satisfy the rule of "must pay to get included into the mempool" +With contract-based accounts, since the account depends on some storage, other transactions may cause a given UserOperation to get invalidated, so we must check it before inclusion + +### Rationale of limiting opcodes: + +- the validation is performed off-chain, before creating a block. Some opcodes access information that is known only when creating the block. +- using those opcode while validating a transaction can easily create a validation rule that will succeed off-chain, but always revert on-chain, and thus cause a DoS attack. +- a simple example is `require block.number==12345`. It can be valid when validating the UserOperation and adding it to the mempool, + but will be invalid when attempting to include it on-chain. + +### Rationale of limiting storage access + +- We need UserOperation validation to be self-contained, so that single storage change can't easily invalidate a large number of UserOperations in the mempool. By limiting UserOperations to access storage associated with the account itself, we know that we can for sure include a single UserOperation for each account in a bundle +- (A bundler MAY include more than one UserOperation of an account in a bundle, MUST first validate them together) + +### Rationalte of requiring a stake + +We want to be able to allow globally-used contracts (paymasters, factories) to use less restrictive storage, but still prevent them from +spamming the mempool. +If a contract causes UserOperation failure in their second validation, we can mark such contract as a spammer. +By requiring such contract to have a stake, we prevent a "sybil attack", by making it expensive to create a large number of such paymasters to continue the spam attack. + +By following the validation rules, we can find out contracts that create spam UserOperations, and block them. +The stake comes to prevent fast re-creation of malicious entities. +The stake is never slashed (since it only used for off-chain detection), but is locked for a period of time, and makes such attack much more expensive. + +The stake is never slashed, but must be left on a given contract for a period of time. ### Definition of the `mass invalidation attack` A possible set of actions is considered to be a `mass invalidation attack` on the network if a large number of `UserOperations` that did pass the initial validation and were accepted by nodes and propagated further into the -mempool to all bundlers in the network, becomes invalid and not eligible for inclusion in a block. +mempool to other bundlers in the network, becomes invalid and not eligible for inclusion in a block. There are 3 ways to perform such an attack: -1. Create a `UserOperation` that passes the initial validation, but later fails the re-validation +1. Submit `UserOperation`s that pass the initial validation, but later fail the re-validation that is performed during the bundle creation. -2. Submit `UserOperation`s that are valid in isolation during validation, but when bundled together become invalid. +2. Submit `UserOperation`s that are valid in isolation during validation, but when bundled + together become invalid. 3. Submit valid `UserOperation`s but "front-run" them by executing a state change on the network that causes them to become invalid. The "front-run" in question must be economically viable. @@ -59,9 +110,35 @@ A `UserOperation` that fails the initial validation by a receiving node without considered an attack. The node is expected to apply web2 security measures and throttle requests based on API key, source IP address, etc. RPC nodes already do that to prevent being spammed with invalid transactions which also have a validation cost. +P2P nodes already have (and should apply) scoring mechanism to determine spammer nodes. -### Constants: +Also, if the invalidation of "n" UserOperations from the mempool has a cost of "n*X", (for X large enough), it is also not considered an attack. + +- The minimum change to cause an invalidation is a storage change (5k gas) +- Assuming a Node can sustain processing 2000 invalid userops per block, the cost of DoS attack is 10M gas per block. +- The above value is high, but we take further measures to make such attack more expensive. + + +## Specification + +### Rule types + +There are two types of rules: +- **Validation rules** rules that should be applied to each UserOperation before accepting it into the local mempool. + These rules include the opcode and storage rules. + - Failing these validation rules SHOULD drop the UserOperation + - Failing these validations during 2nd validation phase (before submitting a bundle) SHOULD trigger + reputation drop for the ofending entity + - Bundler MUST NOT propagate UserOperations that fail the validation rules, otherwise + it will be cosidered a "spammer" by other bundlers in the mempool, and get disconnected. + +- **Reputation rules** + These are "soft" rules, based on the reputation of entities. + These rules come to protect the bundler itself from spamming attacks. + Bundlers SHOULD not propagate such UserOperations to other bundlers + +### Constants: | Title | Value | Comment | |--------------------------------------|-----------------------------|---------------------------------------------------------------------------------| @@ -193,7 +270,7 @@ To help make sense of these params, note that a malicious paymaster can at most * **[OP-062]** Precompiles: * Only allowed the core 9 precompiles.\ Specifically the computation precompiles that do not access anything in the blockchain state or environment. -* **[OP-070] Transient Storage slots defined in [EIP-1153](./eip-1153.md) and accessed using `TLOAD` (`0x5c`) and `TSTORE` (`0x5d`) opcodes +* **[OP-070]** Transient Storage slots defined in [EIP-1153](./eip-1153) and accessed using `TLOAD` (`0x5c`) and `TSTORE` (`0x5d`) opcodes are treated exactly like persistent storage (SLOAD/SSTORE) ### Code Rules @@ -239,8 +316,7 @@ This means that a `Paymaster` and `Factory` contracts cannot practically be an " * TODO: allow "fractional reserve" based on reputation? * **[EREP-020]** A staked factory is "accountable" for account breaking the rules. \ That is, if the `validateUserOp()` is rejected for any reason, it is treated as if the factory caused this failure, and thus this affects its reputation. -* **[EREP-030]** A Staked Account is accountable for failures in other entities (`paymaster`, `aggregator`) even if they are staked. \ -(see [https://github.com/eth-infinitism/account-abstraction/issues/274](https://github.com/eth-infinitism/account-abstraction/issues/274)) +* **[EREP-030]** A Staked Account is accountable for failures in other entities (`paymaster`, `aggregator`) even if they are staked. * **[EREP-040]** An `aggregator` must be staked, regardless of storage usage. * **[EREP-050]** An unstaked `paymaster` may not return a `context`. * TODO: can be removed once we remove the "2nd postOp". @@ -268,6 +344,23 @@ Alternate mempool is an agreed-upon rule that the bundlers may opt in to. The receiving bundler validates the `UserOperations`, and based on the above rules (and subscribed alt-mempools) decides for which alt-mempools to "tag" it. +## Rationale + +All transactions initiated by EOAs have an implicit validation phase where balance, nonce, and signature are +checked to be valid for the current state of the Ethereum blockchain. +Once the transaction is checked to be valid by a node, only then another transaction by the same EOA can modify the Ethereum +state in a way that makes the first transaction invalid. + +With Account Abstraction, however, the validation can also include an arbitrary EVM code and rely on storage as well, +which means that unrelated `UserOperations` or transactions may invalidate each other. + +If not addressed, this would make the job of maintaining a mempool of valid `UserOperations` and producing valid +bundles computationally infeasible and susceptible to DoS attacks. + +This document describes a set of validation rules that, if applied by a bundler before accepting a `UserOperation` +into the mempool can prevent such attacks. + + ## Security Considerations ### Possible Attacks @@ -342,3 +435,7 @@ effectively censoring the other sender. and the only mitigation at the moment is to bump the gas price of the censored `UserOperation`. 2. There should be at least one "proper" bundler in the network, that follows rule [STO-041], and prefers the sender over the other `UserOperation` which attempts to censor it. + +## Copyright + +Copyright and related rights waived via [CC0](../LICENSE.md). From 9b7e780545d05e8e536791c45f8925c76786e34f Mon Sep 17 00:00:00 2001 From: Dror Tirosh Date: Tue, 19 Dec 2023 16:26:22 +0200 Subject: [PATCH 02/20] spellings --- erc/ERCS/erc-4337.md | 4 +- erc/ERCS/erc-7562.md | 202 ++++++++++++++++++++++--------------------- 2 files changed, 104 insertions(+), 102 deletions(-) diff --git a/erc/ERCS/erc-4337.md b/erc/ERCS/erc-4337.md index 7e038ac0..121b68df 100644 --- a/erc/ERCS/erc-4337.md +++ b/erc/ERCS/erc-4337.md @@ -385,7 +385,7 @@ A node MAY drop a UserOperation if it expires too soon (e.g. wouldn't make it to 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. -For the complete procedure see [ERC-7652](./erc-7652.md) +For the complete procedure see [ERC-7562](./erc-7562.md) #### Alternative Mempools @@ -396,7 +396,7 @@ A bundler cannot simply "whitelist" request from a specific paymaster: if that p 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. -The procedure of using alternate mempools is defined in ./erc-7652.md#Alt-mempools-rules +The procedure of using alternate mempools is defined in [ERC-7562](./erc-7562.md#Alt-mempools-rules) ### Bundling diff --git a/erc/ERCS/erc-7562.md b/erc/ERCS/erc-7562.md index 1ffeeb5b..122ba793 100644 --- a/erc/ERCS/erc-7562.md +++ b/erc/ERCS/erc-7562.md @@ -18,78 +18,93 @@ block builder or a standalone bundler, and the rationale behind each one of them ## Motivation -With Account-Abstraction, instead of hard-coded logic for processing a transaction (validation, gas-payment and execution), this logic is executed by EVM code. -The benefits for account are countless - -- abstracting the validation allows contreact to use different signature schemes, multisig configuration, custom recovery and more. -- abstracting gas payments allows easy on-boarding by 3rd party payments, paying with tokens, cross-chain gas payments -- abstracting execution allows batch transations +With Account-Abstraction, instead of hard-coded logic for processing a transaction (validation, gas-payment, and execution), this logic is executed by EVM code. +The benefits for the account are countless - +- abstracting the validation allows the contract to use different signature schemes, multisig configuration, custom recovery, and more. +- abstracting gas payments allows easy onboarding by 3rd party payments, paying with tokens, cross-chain gas payments +- abstracting execution allows batch transactions All of the above are missing from the EOA account model. -However, there is one rule a transaction must follow to preserve the decentralized network: once submitted into the network (the mempool), the transaction is guaranteed to pay. This comes to prevent denial of service attack on the network. +However, there is one rule a transaction must follow to preserve the decentralized network: once submitted into the network (the mempool), the transaction is guaranteed to pay. This comes to prevent denial of service attacks on the network. The EOA model implicitly follows the rule: a valid transaction can't become invalid without payment by the account: e.g account balance can't be reduced (except with a higher paying transaction) For Account-Abstraction system to be sustainable and DoS-protected, we define a set of rules to protect it. -For the actual interfaces of those contract-based accounts see the defintions in ERC-4337 and RIP-7560. +For the actual interfaces of those contract-based accounts see the definitions in ERC-4337 and RIP-7560. This documentation uses the terminology "UserOperation" for a transaction created by a smart contract account, and closely follows [ERC-4337](./erc-4337.md) terminology. However, the rules apply to any account-abstraction framework that uses ERC code to perform transaction validation + ## Rationale -### The high level goal +All transactions initiated by EOAs have an implicit validation phase where balance, nonce, and signature are +checked to be valid for the current state of the Ethereum blockchain. +Once the transaction is checked to be valid by a node, only then another transaction by the same EOA can modify the Ethereum +state in a way that makes the first transaction invalid. + +With Account Abstraction, however, the validation can also include an arbitrary EVM code and rely on storage as well, +which means that unrelated `UserOperations` or transactions may invalidate each other. + +If not addressed, this would make the job of maintaining a mempool of valid `UserOperations` and producing valid +bundles computationally infeasible and susceptible to DoS attacks. + +This document describes a set of validation rules that if applied by a bundler before accepting a `UserOperation` +into the mempool can prevent such attacks. + +### The high-level goal -The purpose of this specification is to define a concensus between nodes (bundlers or block-builders) when processing incoming UserOperations from external source. +The purpose of this specification is to define a consensus between nodes (bundlers or block-builders) when processing incoming UserOperations from an external source. This external source for UserOperations is either an end-user node (via RPC) or another node in the p2p network. -The protocol tries to detect "spam" - which are large bursts of UserOperations that cannot be included on chain (that thus can't pay). -The network is protected by throtltling down requessts from such spammer nodes. +The protocol tries to detect "spam" - which are large bursts of UserOperations that cannot be included on-chain (and thus can't pay). +The network is protected by throttling down requests from such spammer nodes. -It is important that all nodes in the network all have the same notion of a "spam": if some nodes accepts some type of UserOperations while other consider them a spam, quickly those "forgiving" nodes will be considered "spammers" by the rest of the nodes, and the network effectively gets splitted. +All nodes in the network must have the same notion of "spam": otherwise, if some nodes accept some type of UserOperations while others consider them spam, those "forgiving" nodes will be considered "spammers" by the rest of the nodes, and the network effectively gets split. ### The processing flow of a UserOperation - First, a UserOperation is received - either via RPC (submitted on behalf of a single application) or via the p2p protocol, from another node in the mempool. -- The node performs validation on the UserOperation, and then add it to its in-memory mempool, and submit it to its peers. -- Lastly, when building a block, a node collect UserOperations from the mempool, performs a 2nd validation to make sure they all still valid as a bundle, and submit them into the next block. +- The node performs validation on the UserOperation, and then adds it to its in-memory mempool, and submits it to its peers. +- Lastly, when building a block, a node collects UserOperations from the mempool, performs a 2nd validation to make sure they are all still valid as a bundle and submits them into the next block. ### The need for 2nd validation before submitting a block -A normal ethereum transaction in the mempool can be invalidated if another transaction was received with the same nonce. That other transaction had to increase the gas price in order to replace the first one, so it satisfy the rule of "must pay to get included into the mempool" +A normal Ethereum transaction in the mempool can be invalidated if another transaction was received with the same nonce. That other transaction had to increase the gas price in order to replace the first one, so it satisfied the rule of "must pay to get included into the mempool" With contract-based accounts, since the account depends on some storage, other transactions may cause a given UserOperation to get invalidated, so we must check it before inclusion ### Rationale of limiting opcodes: - the validation is performed off-chain, before creating a block. Some opcodes access information that is known only when creating the block. -- using those opcode while validating a transaction can easily create a validation rule that will succeed off-chain, but always revert on-chain, and thus cause a DoS attack. -- a simple example is `require block.number==12345`. It can be valid when validating the UserOperation and adding it to the mempool, +- using those opcodes while validating a transaction can easily create a validation rule that will succeed off-chain, but always revert on-chain, and thus cause a DoS attack. +- a simple example is `require block.number==12345`. It can be valid when validating the UserOperation and adding it to the mempool but will be invalid when attempting to include it on-chain. -### Rationale of limiting storage access +### Rationale for limiting storage access -- We need UserOperation validation to be self-contained, so that single storage change can't easily invalidate a large number of UserOperations in the mempool. By limiting UserOperations to access storage associated with the account itself, we know that we can for sure include a single UserOperation for each account in a bundle +- We need UserOperation validation to be self-contained so that a single storage change can't easily invalidate a large number of UserOperations in the mempool. By limiting UserOperations to access storage associated with the account itself, we know that we can for sure include a single UserOperation for each account in a bundle - (A bundler MAY include more than one UserOperation of an account in a bundle, MUST first validate them together) ### Rationalte of requiring a stake We want to be able to allow globally-used contracts (paymasters, factories) to use less restrictive storage, but still prevent them from spamming the mempool. -If a contract causes UserOperation failure in their second validation, we can mark such contract as a spammer. -By requiring such contract to have a stake, we prevent a "sybil attack", by making it expensive to create a large number of such paymasters to continue the spam attack. +If a contract causes UserOperation failure in their second validation, we can mark such a contract as a spammer. +By requiring such a contract to have a stake, we prevent a "Sybil attack", by making it expensive to create a large number of such paymasters to continue the spam attack. By following the validation rules, we can find out contracts that create spam UserOperations, and block them. -The stake comes to prevent fast re-creation of malicious entities. -The stake is never slashed (since it only used for off-chain detection), but is locked for a period of time, and makes such attack much more expensive. +The stake comes to prevent the fast re-creation of malicious entities. +The stake is never slashed (since it is only used for off-chain detection) but is locked for a period of time, which makes such an attack much more expensive. -The stake is never slashed, but must be left on a given contract for a period of time. +The stake is never slashed but must be left on a given contract for a while. ### Definition of the `mass invalidation attack` A possible set of actions is considered to be a `mass invalidation attack` on the network if a large number of `UserOperations` that did pass the initial validation and were accepted by nodes and propagated further into the -mempool to other bundlers in the network, becomes invalid and not eligible for inclusion in a block. +mempool to other bundlers in the network becomes invalid and not eligible for inclusion in a block. There are 3 ways to perform such an attack: @@ -110,13 +125,13 @@ A `UserOperation` that fails the initial validation by a receiving node without considered an attack. The node is expected to apply web2 security measures and throttle requests based on API key, source IP address, etc. RPC nodes already do that to prevent being spammed with invalid transactions which also have a validation cost. -P2P nodes already have (and should apply) scoring mechanism to determine spammer nodes. +P2P nodes already have (and should apply) a scoring mechanism to determine spammer nodes. -Also, if the invalidation of "n" UserOperations from the mempool has a cost of "n*X", (for X large enough), it is also not considered an attack. +Also, if the invalidation of "n" UserOperations from the mempool has a cost of "n*X", (for X large enough), it is not considered an attack. - The minimum change to cause an invalidation is a storage change (5k gas) -- Assuming a Node can sustain processing 2000 invalid userops per block, the cost of DoS attack is 10M gas per block. -- The above value is high, but we take further measures to make such attack more expensive. +- Assuming a Node can sustain processing 2000 invalid UserOps per block, the cost of a DoS attack is 10M gas per block. +- The above value is high, but we take further measures to make such an attack more expensive. ## Specification @@ -126,17 +141,17 @@ Also, if the invalidation of "n" UserOperations from the mempool has a cost of " There are two types of rules: - **Validation rules** rules that should be applied to each UserOperation before accepting it into the local mempool. - These rules include the opcode and storage rules. + These rules include the opcode and storage rules. - Failing these validation rules SHOULD drop the UserOperation - Failing these validations during 2nd validation phase (before submitting a bundle) SHOULD trigger - reputation drop for the ofending entity + reputation drop for the offending entity - Bundler MUST NOT propagate UserOperations that fail the validation rules, otherwise - it will be cosidered a "spammer" by other bundlers in the mempool, and get disconnected. + it will be considered a "spammer" by other bundlers in the mempool, and get disconnected. - **Reputation rules** - These are "soft" rules, based on the reputation of entities. - These rules come to protect the bundler itself from spamming attacks. - Bundlers SHOULD not propagate such UserOperations to other bundlers + These are "soft" rules, based on the reputation of entities. + These rules come to protect the bundler itself from spamming attacks. + Bundlers SHOULD not propagate such UserOperations to other bundlers ### Constants: @@ -158,11 +173,11 @@ There are two types of rules: ### **Definitions**: 1. **Validation Phases**: there are up to 3 phases of validation - 1. smart account deployment - 2. smart account validation - 3. paymaster validation. + 1. smart account deployment + 2. smart account validation + 3. paymaster validation. 2. **Entity**: a contract that is explicitly used by the `UserOperation`. - Includes the `factory`, `paymaster`, `aggregator` and staked `account`, as discussed below. \ + Includes the `factory`, `paymaster`, `aggregator`, and staked `account`, as discussed below. \ Each "validation phase" is attributed to a single entity. \ Entity contracts must have non-empty code on-chain. 3. **Canonical Mempool**: The rules defined in this document apply to the main mempool shared by all bundlers on the network. @@ -177,8 +192,8 @@ There are two types of rules: ### Reputation Definitions: 1. **opsSeen**: a per-entity counter of how many times a unique valid `UserOperation` referencing this entity was received by this bundler. - This includes `UserOperation` received via an incoming RPC calls or through a P2P mempool protocol. - * For a `paymaster`, this value is not incremented if factory or account validation fails. + This includes `UserOperation` received via incoming RPC calls or through a P2P mempool protocol. + * For a `paymaster`, this value is not incremented if factory or account validation fails. 2. **opsIncluded**: a per-entity counter of how many times a unique valid `UserOperation` referencing this entity appeared in an actual included `UserOperation`. \ @@ -206,10 +221,10 @@ To help make sense of these params, note that a malicious paymaster can at most ### Running the Validation Rules 1. A block builder or a bundler should perform a full validation before accepting a `UserOperation` into its mempool. -2. During validation phase, the bundler should trace the execution and apply all the rules defined in this document. +2. During the validation phase, the bundler should trace the execution and apply all the rules defined in this document. 3. A bundler should also perform a full validation of the entire bundle before submission. 4. The validation rules prevent an unstaked entity from detecting the bundle validation. - However, it is possible for a malicious staked entity to detect the bundle validation and cause a revert. + However, a malicious staked entity can detect it runs in a bundle validation and cause a revert. 5. The failed `UserOperation` should be dropped from the bundle. 6. The staked entity that caused a revert violated the Account Abstraction rules and should be marked as `THROTTLED`. @@ -221,7 +236,7 @@ To help make sense of these params, note that a malicious paymaster can at most 2. Once a `UserOperation` is received from another bundler it should be verified locally by a receiving bundler. 3. A received `UserOperation` may fail any of the reasonable static checks, such as: \ invalid format, values below minimum, submitted with a blockhash that isn't recent, etc. \ - In this case the bundler should drop this particular `UserOperation` but keep the connection. + In this case, the bundler should drop this particular `UserOperation` but keep the connection. 4. The bundler should validate the `UserOperation` against the nonces of last-included bundles. \ Silently drop `UserOperations` with `nonce` that was recently included. This invalidation is likely attributable to a network racing condition and should not cause a reputation change. @@ -257,11 +272,11 @@ To help make sense of these params, note that a malicious paymaster can at most * **[OP-031]** `CREATE2` is allowed exactly once in the deployment phase and must deploy code for the "sender" address. * Access to an address without a deployed code is forbidden: * **[OP-041]** For `EXTCODE*` and `*CALL` opcodes - * **[OP-042]** Exception: access to "sender" address is allowed. + * **[OP-042]** Exception: access to the "sender" address is allowed. This is only possible in `factory` code during the deployment phase. * Allowed access to the `EntryPoint` address: * **[OP-051]** May call `EXTCODESIZE ISZERO`\ - This pattern is used to check destination has code before `depositTo` function is called. + This pattern is used to check destination has a code before the `depositTo` function is called. * **[OP-052]** May call `depositTo(sender)` with any value from either the `sender` or `factory` * **[OP-053]** May call the fallback function from the `sender` with any value * **[OP-054]** Any other access to the `EntryPoint` is forbidden @@ -269,19 +284,19 @@ To help make sense of these params, note that a malicious paymaster can at most * **[OP-061]** `CALL` with `value` is forbidden. The only exception is a call to the `EntryPoint` described above. * **[OP-062]** Precompiles: * Only allowed the core 9 precompiles.\ - Specifically the computation precompiles that do not access anything in the blockchain state or environment. + Specifically, the computation precompiles that do not access anything in the blockchain state or environment. * **[OP-070]** Transient Storage slots defined in [EIP-1153](./eip-1153) and accessed using `TLOAD` (`0x5c`) and `TSTORE` (`0x5d`) opcodes - are treated exactly like persistent storage (SLOAD/SSTORE) + are treated exactly like persistent storage (SLOAD/SSTORE) ### Code Rules * **[COD-010]** Between the first and the second validations, the `EXTCODEHASH` value of any visited address, -entity or referenced library, may not be changed.\ -If the code is modified, the UserOperation is considered invalid. + entity, or referenced library, may not be changed.\ + If the code is modified, the UserOperation is considered invalid. ### Storage rules. -The permanent storage access with `SLOAD` and `SSTORE` instructions within each phase are limited as follows: +The permanent storage access with `SLOAD` and `SSTORE` instructions within each phase is limited as follows: * **[STO-010]** Access to the "account" storage is always allowed. * Access to associated storage of the account in an external (non-entity contract) is allowed if either: @@ -289,10 +304,10 @@ The permanent storage access with `SLOAD` and `SSTORE` instructions within each * **[STO-022]** There is an `initCode` and the `factory` contract is staked. * If the entity (`paymaster`, `factory`) is staked, then it is also allowed: * **[STO-031]** Access the entity's own storage. - * **[STO-032]** Read/Write Access to storage slots that is associated with the entity, in any non-entity contract. + * **[STO-032]** Read/Write Access to storage slots that are associated with the entity, in any non-entity contract. * **[STO-033]** Read-only access to any storage in non-entity contract. * **[STO-040]** `UserOperation` may not use an entity address (`factory`/`paymaster`/`aggregator`) that is used as an "account" in another `UserOperation` in the mempool. \ -This means that a `Paymaster` and `Factory` contracts cannot practically be an "account" contract as well. + This means that `Paymaster` and `Factory` contracts cannot practically be an "account" contract as well. * **[STO-041]** A contract whose Associated Storage slot is accessed during a `UserOperation` may not be an address of a "sender" in another `UserOperation` in the mempool. ### Staked Entities Reputation Rules @@ -315,7 +330,7 @@ This means that a `Paymaster` and `Factory` contracts cannot practically be an " * Do not add a `UserOperation` to the mempool if the maximum total gas usage, including the new `UserOperation`, is above the deposit of the `paymaster` at the current gas price. * TODO: allow "fractional reserve" based on reputation? * **[EREP-020]** A staked factory is "accountable" for account breaking the rules. \ -That is, if the `validateUserOp()` is rejected for any reason, it is treated as if the factory caused this failure, and thus this affects its reputation. + That is, if the `validateUserOp()` is rejected for any reason, it is treated as if the factory caused this failure, and thus this affects its reputation. * **[EREP-030]** A Staked Account is accountable for failures in other entities (`paymaster`, `aggregator`) even if they are staked. * **[EREP-040]** An `aggregator` must be staked, regardless of storage usage. * **[EREP-050]** An unstaked `paymaster` may not return a `context`. @@ -325,61 +340,48 @@ That is, if the `validateUserOp()` is rejected for any reason, it is treated as * Definitions: * **`opsSeen`, `opsIncluded`, and reputation calculation** are defined above. - * `UnstakedReputation` of an entity is a maximum number of entries using this entity allowed in the mempool. - * `opsAllowed` is a reputation based calculation for an unstaked entity, representing how many `UserOperations` it is allowed to have in the mempool. -* **[UREP-010]** `UserOperation` with unstaked sender are only allowed up to `SAME_SENDER_MEMPOOL_COUNT` times in the mempool. + * `UnstakedReputation` of an entity is the maximum number of entries using this entity allowed in the mempool. + * `opsAllowed` is a reputation-based calculation for an unstaked entity, representing how many `UserOperations` it is allowed to have in the mempool. +* **[UREP-010]** `UserOperation` with unstaked sender is only allowed up to `SAME_SENDER_MEMPOOL_COUNT` times in the mempool. * **[UREP-020]** For other entities: \ - `opsAllowed = SAME_UNSTAKED_ENTITY_MEMPOOL_COUNT + (inclusionRate * INCLUSION_RATE_FACTOR) + (min(opsIncluded, 10000)`. - * This is a default of `SAME_UNSTAKED_ENTITY_MEMPOOL_COUNT` for new entity + `opsAllowed = SAME_UNSTAKED_ENTITY_MEMPOOL_COUNT + (inclusionRate * INCLUSION_RATE_FACTOR) + (min(opsIncluded, 10000)`. + * This is a default of `SAME_UNSTAKED_ENTITY_MEMPOOL_COUNT` for a new entity * **[UREP-030]** If an unstaked entity causes an invalidation of a bundle, its `opsSeen` is set to `1000`, effectively blocking it from inclusion for 24 hours. ### Alt-mempools rules: Alternate mempool is an agreed-upon rule that the bundlers may opt in to. +The alt-mempool "topic" is a unique identifier. By convention, this is the IPFS hash of the document describing (in clear test and YAML file) the specifics of this alt mempool * **[ALT-010]** The bundler listens to the alt-mempool "topic" over the P2P protocol * **[ALT-020]** The alt mempool rules MUST be checked only when a canonical rule is violated * That is, if validation follows the canonical rules above, it is not considered part of an alt-mempool. * **[ALT-030]** Bundlers SHOULD forward `UserOperations` to other bundlers only once, regardless of how many (shared) alt-mempools they have. \ -The receiving bundler validates the `UserOperations`, and based on the above rules (and subscribed alt-mempools) decides for which alt-mempools to "tag" it. - - -## Rationale - -All transactions initiated by EOAs have an implicit validation phase where balance, nonce, and signature are -checked to be valid for the current state of the Ethereum blockchain. -Once the transaction is checked to be valid by a node, only then another transaction by the same EOA can modify the Ethereum -state in a way that makes the first transaction invalid. - -With Account Abstraction, however, the validation can also include an arbitrary EVM code and rely on storage as well, -which means that unrelated `UserOperations` or transactions may invalidate each other. - -If not addressed, this would make the job of maintaining a mempool of valid `UserOperations` and producing valid -bundles computationally infeasible and susceptible to DoS attacks. - -This document describes a set of validation rules that, if applied by a bundler before accepting a `UserOperation` -into the mempool can prevent such attacks. + The receiving bundler validates the `UserOperations`, and based on the above rules (and subscribed alt-mempools) decides for which alt-mempools to "tag" it. ## Security Considerations +This document describes the security considerations bundlers must take to protect themselves (and the entire mempool network) +from denial-of-service attacks. + ### Possible Attacks Below is a list of possible attacks that were considered and a reference to the above rule that prevents it. This list does not attempt to be an exhaustive list of attacks. -These attacks are examples provided to describe and rationalise the reason for the above rules. +These attacks are examples provided to describe and rationalize the reason for the above rules. #### Sample Known (expensive) attack: * This attack can't be fully mitigated by the above validation rules. - But the rules made sure it is expensive to maintain. + But the rules made sure it was expensive to maintain. * Deploy a paymaster which has the following line in the validation function: \ -`require((perSenderMap[userOp.sender]&1)==0);` + `require((perSenderMap[userOp.sender]&1)==0);` * Submit a large number of `UserOperations` with different senders into the mempool. * Front-run the mempool, and modify the `perSenderMap` for each sender. * By the time the `UserOperations` are validated the second time they are all invalid. -* The cost of first run is 20000 gas per account, but the second run is 5000 gas per account. +* The cost of the first run is 20000 gas per account, but the second run is 5000 gas per account. * NOTE: Because of the rule **[STO-021]** "forbid associated storage during creation", the accounts have to be pre-deployed. This introduces a one-time cost of ~40000 gas per account. * Calculate the cost of blocking the network: * Assuming a bundler can process 1000 `UserOperations`/second @@ -392,14 +394,14 @@ These attacks are examples provided to describe and rationalise the reason for t * Total attack cost * Polygon (@150 gwei) - $18 + $1.5 per block ($2700/hour) * Mainnet: (@40 gwei) - $51840 + $5040 per block ($1.5M/hour) - * Note that the above costs ignore the gas-price increase because the underlying network is also overloaded. + * Note that the above costs ignore the gas price increase because the underlying network is also overloaded. * Mitigations - * A **staked factory:** + * A **staked factory:** * Can attempt such an attack once, since after invalidating a single bundle creation its reputation drops and another staked factory has to be used. * So a successful attack needs to deploy and stake a new paymaster on every block. The old paymaster's stake is locked and can be re-used only after 24 hours. * For **unstaked factory**: * As per **[UREP-020]**, it is initially allowed only 10 `UserOperations` in the mempool. - * That value increases fast, but drops (to negative) on invalidations. + * That value increases fast but drops (to negative) on invalidations. * It needs to successfully deploy 10 `UserOperations` to get a reputation to deploy a batch of 1000 (which attack). * After a single "attack", its reputation is dropped, and it gets banned for a day, so another factory needs to be deployed. * So the cost of the attack is 10 actual deployments for 1000 failures. @@ -407,26 +409,26 @@ These attacks are examples provided to describe and rationalise the reason for t #### List of attacks: 1. **Use volatile data**\ -Send a large number of `UserOperations` with `initCode` to deploy an account with a validation that contains `require((block.number&1)==0)` into the mempool.\ -They all get invalidated at no cost to the sender when tried to be included in the next block. \ -**Blocked by: [OP-011]** "opcode banning" rule. + Send a large number of `UserOperations` with `initCode` to deploy an account with a validation that contains `require((block.number&1)==0)` into the mempool.\ + They all get invalidated at no cost to the sender when tried to be included in the next block. \ + **Blocked by: [OP-011]** "opcode banning" rule. 2. **Use uninitialized code**\ -The validation function includes the code: `tempaddr.call(..);` and execution does `new {salt}()` where the tempaddr is -initialized to the address returned by the `new`.\ -This code passes validation since the generated contract does not exist, but other `UserOperations` with the same code -will revert since the target is already created, and revert on this calldata.\ -**Blocked by: [OP-041]** "reference uninitialized code" rule. + The validation function includes the code: `tempaddr.call(..);` and execution does `new {salt}()` where the tempaddr is + initialized to the address returned by the `new`.\ + This code passes validation since the generated contract does not exist, but other `UserOperations` with the same code + will revert since the target is already created, and revert on this calldata.\ + **Blocked by: [OP-041]** "reference uninitialized code" rule. 3. **Use inner contact creation**\ -The validation function includes the code: `require(new {salt:0}SomeContract() !=0)`\ -When submitting multiple such `UserOperations`, all pass the validation and enter the mempool, and even pass the second validation.\ -But when creating a bundle, the first one will succeed and the rest will revert. \ -**Blocked by: **[OP-031]** "`CREATE`/`CREATE2` is blocked"** + The validation function includes the code: `require(new {salt:0}SomeContract() !=0)`\ + When submitting multiple such `UserOperations`, all pass the validation and enter the mempool, and even pass the second validation.\ + But when creating a bundle, the first one will succeed and the rest will revert. \ + **Blocked by: **[OP-031]** "`CREATE`/`CREATE2` is blocked"** 4. **Censorship attack**\ -`UserOperation` sender can access associated storage in another `UserOperation` sender that is -supposed to be in the bundle (e.g. `anotherSender.balanceOf(this)`), provide a higher gas price than the other sender, -effectively censoring the other sender. + `UserOperation` sender can access associated storage in another `UserOperation` sender that is + supposed to be in the bundle (e.g. `anotherSender.balanceOf(this)`), provide a higher gas price than the other sender, + effectively censoring the other sender. 1. Assumes the attacked account has a method `getData(xx)`, where `xx` can be set to `this`. - Many account do have such a method, e.g. `ERC721Y` have generic `getData` method. + Many accounts do have such a method, e.g. `ERC721Y` has a generic `getData` method. 2. By rule **[STO-041]**, senders cannot access each other's storage, making bundlers choose only one `UserOperation` between those, probably the more profitable one. Currently, the bundlers have no incentive to prevent such censorship attacks. From 38d8a157c3355afb2a7f507078e72e878504543e Mon Sep 17 00:00:00 2001 From: Dror Tirosh Date: Sun, 31 Dec 2023 17:59:48 +0200 Subject: [PATCH 03/20] merge changes from davidinsuomi:update-eip-4337-description-paymater from external PR https://github.com/eth-infinitism/account-abstraction/pull/378 --- erc/ERCS/erc-4337.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erc/ERCS/erc-4337.md b/erc/ERCS/erc-4337.md index 121b68df..4d0bb7ce 100644 --- a/erc/ERCS/erc-4337.md +++ b/erc/ERCS/erc-4337.md @@ -251,7 +251,7 @@ We extend the entry point logic to support **paymasters** that can sponsor trans During the verification loop, in addition to calling `validateUserOp`, the `handleOps` execution also must check that the paymaster has enough ETH deposited with the entry point to pay for the operation, and then call `validatePaymasterUserOp` on the paymaster to verify that the paymaster is willing to pay for the operation. Note that in this case, the `validateUserOp` is called with a `missingAccountFunds` of 0 to reflect that the account's deposit is not used for payment for this userOp. -If the paymaster's validatePaymasterUserOp returns a "context", then `handleOps` must call `postOp` on the paymaster after making the main execution call. It must guarantee the execution of `postOp`, by making the main execution inside an inner call context, and if the inner call context reverts attempting to call `postOp` again in an outer call context. +If the paymaster's validatePaymasterUserOp returns a "context", then `handleOps` must call `postOp` on the paymaster after making the main execution call. Maliciously crafted paymasters _can_ DoS the system. To prevent this, we use a reputation system. paymaster must either limit its storage usage, or have a stake. see the [reputation, throttling and banning section](#reputation-scoring-and-throttlingbanning-for-global-entities) for details. @@ -468,7 +468,7 @@ Paymasters facilitate transaction sponsorship, allowing third-party-designed mec * No possibility for "passive" paymasters (eg. that accept fees in some ERC-20 token at an exchange rate pulled from an on-chain DEX) * Paymasters run the risk of getting griefed, as users could send ops that appear to pay the paymaster but then change their behavior after a block -The paymaster scheme allows a contract to passively pay on users' behalf under arbitrary conditions. It even allows ERC-20 token paymasters to secure a guarantee that they would only need to pay if the user pays them: the paymaster contract can check that there is sufficient approved ERC-20 balance in the `validatePaymasterUserOp` method, and then extract it with `transferFrom` in the `postOp` call; if the op itself transfers out or de-approves too much of the ERC-20s, the inner `postOp` will fail and revert the execution and the outer `postOp` can extract payment (note that because of storage access restrictions the ERC-20 would need to be a wrapper defined within the paymaster itself). +The paymaster scheme allows a contract to passively pay on users' behalf under arbitrary conditions. for example, a TokenPaymaster uses the precharge refund model. It can precharge the maximum cost with transferFrom in the validatePaymasterUserOp method and then refund the unused portion in the postOp method. ### First-time account creation From 4063d8178f521414b312d3b6df3650439cf235f8 Mon Sep 17 00:00:00 2001 From: Dror Tirosh Date: Thu, 21 Dec 2023 00:10:24 +0200 Subject: [PATCH 04/20] AA-222 add delegateAndRevert helper, to simulate code execution from entryPoint. (#365) Minimal "static" call from EntryPoint to simulate special cases. Equivalent to the EntryPointSimulations in purpose, but doesn't require stateOverride. Executes code on behalf of entryPoint using delegateCall, and revert with the result --- reports/gas-checker.txt | 44 +++++++++++++---------------------------- 1 file changed, 14 insertions(+), 30 deletions(-) diff --git a/reports/gas-checker.txt b/reports/gas-checker.txt index 3fbf000b..25d1dcea 100644 --- a/reports/gas-checker.txt +++ b/reports/gas-checker.txt @@ -2,9 +2,9 @@ the destination is "account.entryPoint()", which is known to be "hot" address used by this account it little higher than EOA call: its an exec from entrypoint (or account owner) into account contract, verifying msg.sender and exec to target) ╔══════════════════════════╤════════╗ -║ gas estimate "simple" │ 28979 ║ +║ gas estimate "simple" │ 29014 ║ ╟──────────────────────────┼────────╢ -║ gas estimate "big tx 5k" │ 125224 ║ +║ gas estimate "big tx 5k" │ 125260 ║ ╚══════════════════════════╧════════╝ ╔════════════════════════════════╤═══════╤═══════════════╤════════════════╤═════════════════════╗ @@ -12,44 +12,28 @@ ║ │ │ │ (delta for │ (compared to ║ ║ │ │ │ one UserOp) │ account.exec()) ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ simple │ 1 │ 81570 │ │ ║ +║ simple │ 1 │ 81930 │ │ ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ simple - diff from previous │ 2 │ │ 43842 │ 14863 ║ +║ simple - diff from previous │ 2 │ │ 44187 │ 15173 ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ simple │ 10 │ 476227 │ │ ║ +║ simple │ 10 │ 479742 │ │ ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ simple - diff from previous │ 11 │ │ 43873 │ 14894 ║ +║ simple - diff from previous │ 11 │ │ 44247 │ 15233 ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ simple paymaster │ 1 │ 87750 │ │ ║ +║ simple paymaster │ 1 │ 88187 │ │ ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ simple paymaster with diff │ 2 │ │ 42716 │ 13737 ║ +║ simple paymaster with diff │ 2 │ │ 43114 │ 14100 ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ simple paymaster │ 10 │ 472423 │ │ ║ +║ simple paymaster │ 10 │ 476706 │ │ ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ simple paymaster with diff │ 11 │ │ 42781 │ 13802 ║ +║ simple paymaster with diff │ 11 │ │ 43173 │ 14159 ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ big tx 5k │ 1 │ 182627 │ │ ║ +║ big tx 5k │ 1 │ 182987 │ │ ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ big tx - diff from previous │ 2 │ │ 144341 │ 19117 ║ +║ big tx - diff from previous │ 2 │ │ 144698 │ 19438 ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ big tx 5k │ 10 │ 1481819 │ │ ║ +║ big tx 5k │ 10 │ 1485386 │ │ ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ big tx - diff from previous │ 11 │ │ 144445 │ 19221 ║ -╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ paymaster+postOp │ 1 │ 89483 │ │ ║ -╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ paymaster+postOp with diff │ 2 │ │ 44463 │ 15484 ║ -╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ paymaster+postOp │ 10 │ 489783 │ │ ║ -╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ paymaster+postOp with diff │ 11 │ │ 44508 │ 15529 ║ -╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ token paymaster │ 1 │ 147976 │ │ ║ -╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ token paymaster with diff │ 2 │ │ 72659 │ 43680 ║ -╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ token paymaster │ 10 │ 802242 │ │ ║ -╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ token paymaster with diff │ 11 │ │ 72727 │ 43748 ║ +║ big tx - diff from previous │ 11 │ │ 144759 │ 19499 ║ ╚════════════════════════════════╧═══════╧═══════════════╧════════════════╧═════════════════════╝ From f6a7434765df6ed2fd999bb82190bee199c55dc1 Mon Sep 17 00:00:00 2001 From: Alex Forshtat Date: Mon, 1 Jan 2024 10:20:43 +0100 Subject: [PATCH 05/20] TokenPaymaster "gas-calc" test (#300) * Initial commit for TokenPaymaster "gas-calc" test * Deploy all TokenPaymaster contracts with Create2Factory for "gas-calc" * Make TestPaymaster use postOp * Storage-Optimization for TokenPaymaster - change gas and timestamps to 48 bits - change blanaces and multipliers to 128 bit - saves 10kgas (and since 10-op batch uses the same TokenPaymaster, the * add initial balance to paymaster --- contracts/test/TestPaymasterAcceptAll.sol | 11 +- gascalc/4-token-paymaster.gas.ts | 132 ++++++++++++++++++++++ reports/gas-checker.txt | 24 ++-- 3 files changed, 158 insertions(+), 9 deletions(-) create mode 100644 gascalc/4-token-paymaster.gas.ts diff --git a/contracts/test/TestPaymasterAcceptAll.sol b/contracts/test/TestPaymasterAcceptAll.sol index 7db7dc2c..f97ccd4a 100644 --- a/contracts/test/TestPaymasterAcceptAll.sol +++ b/contracts/test/TestPaymasterAcceptAll.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.12; import "../core/BasePaymaster.sol"; +/* solhint-disable no-empty-blocks */ /** * test paymaster, that pays for everything, without any check. @@ -21,6 +22,14 @@ contract TestPaymasterAcceptAll is BasePaymaster { internal virtual override view returns (bytes memory context, uint256 validationData) { (userOp, userOpHash, maxCost); - return ("", 0); + // return a context, as it is used for EntryPoint gas checking. + return ("1", 0); + } + + function _postOp( + PostOpMode mode, + bytes calldata context, + uint256 actualGasCost + ) internal override { } } diff --git a/gascalc/4-token-paymaster.gas.ts b/gascalc/4-token-paymaster.gas.ts new file mode 100644 index 00000000..1d2dfd96 --- /dev/null +++ b/gascalc/4-token-paymaster.gas.ts @@ -0,0 +1,132 @@ +import { parseEther } from 'ethers/lib/utils' +import { + TestERC20__factory, TestOracle2__factory, + TestUniswap__factory, + TestWrappedNativeToken__factory, TokenPaymaster, + TokenPaymaster__factory +} from '../typechain' +import { ethers } from 'hardhat' +import { GasCheckCollector, GasChecker } from './GasChecker' +import { Create2Factory } from '../src/Create2Factory' +import { hexValue } from '@ethersproject/bytes' +import { + OracleHelper as OracleHelperNamespace, + UniswapHelper as UniswapHelperNamespace +} from '../typechain/contracts/samples/TokenPaymaster' +import { BigNumber } from 'ethers' +import { createAccountOwner } from '../test/testutils' +// const ethersSigner = ethers.provider.getSigner() + +context('Token Paymaster', function () { + this.timeout(60000) + const g = new GasChecker() + + let paymasterAddress: string + before(async () => { + await GasCheckCollector.init() + const globalSigner = ethers.provider.getSigner() + const create2Factory = new Create2Factory(ethers.provider, globalSigner) + + const ethersSigner = createAccountOwner() + await globalSigner.sendTransaction({ to: ethersSigner.getAddress(), value: parseEther('10') }) + + const minEntryPointBalance = 1e17.toString() + const initialPriceToken = 100000000 // USD per TOK + const initialPriceEther = 500000000 // USD per ETH + const priceDenominator = BigNumber.from(10).pow(26) + + const tokenInit = await new TestERC20__factory(ethersSigner).getDeployTransaction(6) + const tokenAddress = await create2Factory.deploy(tokenInit, 0) + const token = TestERC20__factory.connect(tokenAddress, ethersSigner) + + const wethInit = await new TestWrappedNativeToken__factory(ethersSigner).getDeployTransaction() + const wethAddress = await create2Factory.deploy(wethInit, 0) + const testUniswapInit = await new TestUniswap__factory(ethersSigner).getDeployTransaction(wethAddress) + const testUniswapAddress = await create2Factory.deploy(testUniswapInit, 0) + + const tokenPaymasterConfig: TokenPaymaster.TokenPaymasterConfigStruct = { + priceMaxAge: 86400, + refundPostopCost: 40000, + minEntryPointBalance, + priceMarkup: priceDenominator.mul(15).div(10) // +50% + } + + const nativeAssetOracleInit = await new TestOracle2__factory(ethersSigner).getDeployTransaction(initialPriceEther, 8) + const nativeAssetOracleAddress = await create2Factory.deploy(nativeAssetOracleInit, 0, 10_000_000) + const tokenOracleInit = await new TestOracle2__factory(ethersSigner).getDeployTransaction(initialPriceToken, 8) + const tokenOracleAddress = await create2Factory.deploy(tokenOracleInit, 0, 10_000_000) + + const oracleHelperConfig: OracleHelperNamespace.OracleHelperConfigStruct = { + cacheTimeToLive: 0, + nativeOracle: nativeAssetOracleAddress, + nativeOracleReverse: false, + priceUpdateThreshold: 200_000, // +20% + tokenOracle: tokenOracleAddress, + tokenOracleReverse: false, + tokenToNativeOracle: false + } + + const uniswapHelperConfig: UniswapHelperNamespace.UniswapHelperConfigStruct = { + minSwapAmount: 1, + slippage: 5, + uniswapPoolFee: 3 + } + + const owner = await ethersSigner.getAddress() + + const paymasterInit = hexValue(new TokenPaymaster__factory(ethersSigner).getDeployTransaction( + tokenAddress, + g.entryPoint().address, + wethAddress, + testUniswapAddress, + tokenPaymasterConfig, + oracleHelperConfig, + uniswapHelperConfig, + owner + ).data!) + paymasterAddress = await create2Factory.deploy(paymasterInit, 0) + const paymaster = TokenPaymaster__factory.connect(paymasterAddress, ethersSigner) + await paymaster.addStake(1, { value: 1 }) + await g.entryPoint().depositTo(paymaster.address, { value: parseEther('10') }) + await paymaster.updateCachedPrice(true) + await g.createAccounts1(11) + await token.sudoMint(await ethersSigner.getAddress(), parseEther('20')) + await token.transfer(paymaster.address, parseEther('0.1')) + for (const address of g.createdAccounts) { + await token.transfer(address, parseEther('1')) + await token.sudoApprove(address, paymaster.address, ethers.constants.MaxUint256) + } + + console.log('==addresses:', { + ethersSigner: await ethersSigner.getAddress(), + paymasterAddress, + nativeAssetOracleAddress, + tokenOracleAddress, + tokenAddress, + owner, + createdAccounts: g.createdAccounts + }) + }) + + it('token paymaster', async function () { + await g.addTestRow({ title: 'token paymaster', count: 1, paymaster: paymasterAddress, diffLastGas: false }) + await g.addTestRow({ + title: 'token paymaster with diff', + count: 2, + paymaster: paymasterAddress, + diffLastGas: true + }) + }) + + it('token paymaster 10', async function () { + if (g.skipLong()) this.skip() + + await g.addTestRow({ title: 'token paymaster', count: 10, paymaster: paymasterAddress, diffLastGas: false }) + await g.addTestRow({ + title: 'token paymaster with diff', + count: 11, + paymaster: paymasterAddress, + diffLastGas: true + }) + }) +}) diff --git a/reports/gas-checker.txt b/reports/gas-checker.txt index 25d1dcea..3fac0df2 100644 --- a/reports/gas-checker.txt +++ b/reports/gas-checker.txt @@ -12,28 +12,36 @@ ║ │ │ │ (delta for │ (compared to ║ ║ │ │ │ one UserOp) │ account.exec()) ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ simple │ 1 │ 81930 │ │ ║ +║ simple │ 1 │ 81918 │ │ ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ ║ simple - diff from previous │ 2 │ │ 44187 │ 15173 ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ simple │ 10 │ 479742 │ │ ║ +║ simple │ 10 │ 479730 │ │ ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ ║ simple - diff from previous │ 11 │ │ 44247 │ 15233 ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ simple paymaster │ 1 │ 88187 │ │ ║ +║ simple paymaster │ 1 │ 89813 │ │ ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ simple paymaster with diff │ 2 │ │ 43114 │ 14100 ║ +║ simple paymaster with diff │ 2 │ │ 44796 │ 15782 ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ simple paymaster │ 10 │ 476706 │ │ ║ +║ simple paymaster │ 10 │ 493254 │ │ ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ simple paymaster with diff │ 11 │ │ 43173 │ 14159 ║ +║ simple paymaster with diff │ 11 │ │ 44820 │ 15806 ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ big tx 5k │ 1 │ 182987 │ │ ║ +║ big tx 5k │ 1 │ 182975 │ │ ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ ║ big tx - diff from previous │ 2 │ │ 144698 │ 19438 ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ big tx 5k │ 10 │ 1485386 │ │ ║ +║ big tx 5k │ 10 │ 1485374 │ │ ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ ║ big tx - diff from previous │ 11 │ │ 144759 │ 19499 ║ +╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ +║ token paymaster │ 1 │ 148244 │ │ ║ +╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ +║ token paymaster with diff │ 2 │ │ 72920 │ 43906 ║ +╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ +║ token paymaster │ 10 │ 804795 │ │ ║ +╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ +║ token paymaster with diff │ 11 │ │ 73015 │ 44001 ║ ╚════════════════════════════════╧═══════╧═══════════════╧════════════════╧═════════════════════╝ From 0e5acfc08278dacfd14a377842c85b2d8c411b8d Mon Sep 17 00:00:00 2001 From: Dror Tirosh Date: Sun, 7 Jan 2024 18:08:30 +0200 Subject: [PATCH 06/20] editorial changes by yoav Co-authored-by: Yoav Weiss --- erc/ERCS/erc-7562.md | 81 ++++++++++++++++++++++---------------------- 1 file changed, 41 insertions(+), 40 deletions(-) diff --git a/erc/ERCS/erc-7562.md b/erc/ERCS/erc-7562.md index 122ba793..639a5e5d 100644 --- a/erc/ERCS/erc-7562.md +++ b/erc/ERCS/erc-7562.md @@ -1,7 +1,7 @@ --- eip: 7562 title: Account Abstraction Validation Scope Rules -description: A set of limitations on validation EVM code to protect Account Abstraction nodes from computation without compensation +description: A set of limitations on validation EVM code to protect Account Abstraction nodes from denial-of-service attacks through unpaid computation. author: Yoav Weiss (@yoavw), Dror Tirosh (@drortirosh), Alex Forshtat (@forshtat), Shahaf Nacson (@shahafn) discussions-to: https://ethereum-magicians.org/t/erc-7562-account-abstraction-validation-scope-rules/16683 status: Draft @@ -12,7 +12,7 @@ created: 2023-09-01 ## Abstract -This document describes the validation rules we impose on the validation context of Account Abstraction transactions, +This document describes the rules we impose on the validation context of Account Abstraction transactions, such as [ERC-4337](./eip-4337) `UserOperation` or RIP-7560 (Native Account Abstraction), which are enforced off-chain by a block builder or a standalone bundler, and the rationale behind each one of them. @@ -35,14 +35,14 @@ For Account-Abstraction system to be sustainable and DoS-protected, we define a For the actual interfaces of those contract-based accounts see the definitions in ERC-4337 and RIP-7560. This documentation uses the terminology "UserOperation" for a transaction created by a smart contract account, and closely follows [ERC-4337](./erc-4337.md) terminology. -However, the rules apply to any account-abstraction framework that uses ERC code to perform transaction validation +However, the rules apply to any account-abstraction framework that uses EVM code to perform transaction validation and makes a distinction between validation and execution. ## Rationale All transactions initiated by EOAs have an implicit validation phase where balance, nonce, and signature are checked to be valid for the current state of the Ethereum blockchain. -Once the transaction is checked to be valid by a node, only then another transaction by the same EOA can modify the Ethereum +Once the transaction is checked to be valid by a node, only another transaction by the same EOA can modify the Ethereum. state in a way that makes the first transaction invalid. With Account Abstraction, however, the validation can also include an arbitrary EVM code and rely on storage as well, @@ -62,7 +62,7 @@ This external source for UserOperations is either an end-user node (via RPC) or The protocol tries to detect "spam" - which are large bursts of UserOperations that cannot be included on-chain (and thus can't pay). The network is protected by throttling down requests from such spammer nodes. -All nodes in the network must have the same notion of "spam": otherwise, if some nodes accept some type of UserOperations while others consider them spam, those "forgiving" nodes will be considered "spammers" by the rest of the nodes, and the network effectively gets split. +All nodes in the network must have the same definition of "spam": otherwise, if some nodes accept some type of UserOperations and propagate them while others consider them spam, those "forgiving" nodes will be considered "spammers" by the rest of the nodes, and the network effectively gets split. ### The processing flow of a UserOperation @@ -73,32 +73,31 @@ All nodes in the network must have the same notion of "spam": otherwise, if some ### The need for 2nd validation before submitting a block A normal Ethereum transaction in the mempool can be invalidated if another transaction was received with the same nonce. That other transaction had to increase the gas price in order to replace the first one, so it satisfied the rule of "must pay to get included into the mempool" -With contract-based accounts, since the account depends on some storage, other transactions may cause a given UserOperation to get invalidated, so we must check it before inclusion +With contract-based accounts, since the UserOperation validity may depend on mutable state, other transactions may invalidate a previously valid UserOperation, so we must check it before inclusion ### Rationale of limiting opcodes: - the validation is performed off-chain, before creating a block. Some opcodes access information that is known only when creating the block. - using those opcodes while validating a transaction can easily create a validation rule that will succeed off-chain, but always revert on-chain, and thus cause a DoS attack. - a simple example is `require block.number==12345`. It can be valid when validating the UserOperation and adding it to the mempool - but will be invalid when attempting to include it on-chain. + but will be invalid when attempting to include it on-chain at a later block. ### Rationale for limiting storage access -- We need UserOperation validation to be self-contained so that a single storage change can't easily invalidate a large number of UserOperations in the mempool. By limiting UserOperations to access storage associated with the account itself, we know that we can for sure include a single UserOperation for each account in a bundle +- We need UserOperation validations not to overlap so that a single storage change can't easily invalidate a large number of UserOperations in the mempool. By limiting UserOperations to access storage associated with the account itself, we know that we can for sure include a single UserOperation for each account in a bundle - (A bundler MAY include more than one UserOperation of an account in a bundle, MUST first validate them together) -### Rationalte of requiring a stake +### Rationale of requiring a stake -We want to be able to allow globally-used contracts (paymasters, factories) to use less restrictive storage, but still prevent them from +We want to be able to allow globally-used contracts (paymasters, factories) to use storage not associated with the account, but still prevent them from spamming the mempool. -If a contract causes UserOperation failure in their second validation, we can mark such a contract as a spammer. +If a contract causes too many UserOperations to fail in their second validation after succeeding in their first, we can throttle its use in the mempool. By requiring such a contract to have a stake, we prevent a "Sybil attack", by making it expensive to create a large number of such paymasters to continue the spam attack. -By following the validation rules, we can find out contracts that create spam UserOperations, and block them. +By following the validation rules, we can detect contracts that cause spam UserOperations, and throttle them. The stake comes to prevent the fast re-creation of malicious entities. The stake is never slashed (since it is only used for off-chain detection) but is locked for a period of time, which makes such an attack much more expensive. -The stake is never slashed but must be left on a given contract for a while. ### Definition of the `mass invalidation attack` @@ -127,7 +126,7 @@ source IP address, etc. RPC nodes already do that to prevent being spammed with invalid transactions which also have a validation cost. P2P nodes already have (and should apply) a scoring mechanism to determine spammer nodes. -Also, if the invalidation of "n" UserOperations from the mempool has a cost of "n*X", (for X large enough), it is not considered an attack. +Also, if the invalidation of `N` UserOperations from the mempool costs `N*X` with a sufficiently large `X`, it is not considered an economically viable attack. - The minimum change to cause an invalidation is a storage change (5k gas) - Assuming a Node can sustain processing 2000 invalid UserOps per block, the cost of a DoS attack is 10M gas per block. @@ -140,18 +139,20 @@ Also, if the invalidation of "n" UserOperations from the mempool has a cost of " There are two types of rules: -- **Validation rules** rules that should be applied to each UserOperation before accepting it into the local mempool. +- **Network-wide rules** rules that MUST be applied to each UserOperation before accepting it into the local mempool and propagating it. These rules include the opcode and storage rules. - Failing these validation rules SHOULD drop the UserOperation - - Failing these validations during 2nd validation phase (before submitting a bundle) SHOULD trigger - reputation drop for the offending entity + - Failing these validations during 2nd validation phase (before submitting a bundle) SHOULD degrade + the reputation of the offending entity - Bundler MUST NOT propagate UserOperations that fail the validation rules, otherwise it will be considered a "spammer" by other bundlers in the mempool, and get disconnected. -- **Reputation rules** +- **Local rules** These are "soft" rules, based on the reputation of entities. These rules come to protect the bundler itself from spamming attacks. - Bundlers SHOULD not propagate such UserOperations to other bundlers + - Bundlers SHOULD drop such UserOperations without performing validation. + - Bundlers SHOULD NOT propagate such UserOperations to other bundlers. + - Bundlers SHOULD NOT consider another bundler a "spammer" if it does. ### Constants: @@ -176,7 +177,7 @@ There are two types of rules: 1. smart account deployment 2. smart account validation 3. paymaster validation. -2. **Entity**: a contract that is explicitly used by the `UserOperation`. +2. **Entity**: a contract that is explicitly specified by the `UserOperation`. Includes the `factory`, `paymaster`, `aggregator`, and staked `account`, as discussed below. \ Each "validation phase" is attributed to a single entity. \ Entity contracts must have non-empty code on-chain. @@ -224,7 +225,7 @@ To help make sense of these params, note that a malicious paymaster can at most 2. During the validation phase, the bundler should trace the execution and apply all the rules defined in this document. 3. A bundler should also perform a full validation of the entire bundle before submission. 4. The validation rules prevent an unstaked entity from detecting the bundle validation. - However, a malicious staked entity can detect it runs in a bundle validation and cause a revert. + However, a malicious staked entity can detect that it is running in a bundle validation and cause a revert. 5. The failed `UserOperation` should be dropped from the bundle. 6. The staked entity that caused a revert violated the Account Abstraction rules and should be marked as `THROTTLED`. @@ -239,7 +240,7 @@ To help make sense of these params, note that a malicious paymaster can at most In this case, the bundler should drop this particular `UserOperation` but keep the connection. 4. The bundler should validate the `UserOperation` against the nonces of last-included bundles. \ Silently drop `UserOperations` with `nonce` that was recently included. - This invalidation is likely attributable to a network racing condition and should not cause a reputation change. + This invalidation is likely attributable to a network race condition and should not cause a reputation change. 5. If a received `UserOperation` fails against the current block: 1. Retry the validation against the block the `UserOperation` was originally verified against. 2. If it succeeds, silently drop the `UserOperation` and keep the connection. @@ -271,22 +272,22 @@ To help make sense of these params, note that a malicious paymaster can at most * Contract creation: * **[OP-031]** `CREATE2` is allowed exactly once in the deployment phase and must deploy code for the "sender" address. * Access to an address without a deployed code is forbidden: - * **[OP-041]** For `EXTCODE*` and `*CALL` opcodes + * **[OP-041]** For `EXTCODE*` and `*CALL` opcodes. * **[OP-042]** Exception: access to the "sender" address is allowed. This is only possible in `factory` code during the deployment phase. * Allowed access to the `EntryPoint` address: * **[OP-051]** May call `EXTCODESIZE ISZERO`\ This pattern is used to check destination has a code before the `depositTo` function is called. - * **[OP-052]** May call `depositTo(sender)` with any value from either the `sender` or `factory` - * **[OP-053]** May call the fallback function from the `sender` with any value - * **[OP-054]** Any other access to the `EntryPoint` is forbidden + * **[OP-052]** May call `depositTo(sender)` with any value from either the `sender` or `factory`. + * **[OP-053]** May call the fallback function from the `sender` with any value. + * **[OP-054]** Any other access to the `EntryPoint` is forbidden. * `*CALL` opcodes: * **[OP-061]** `CALL` with `value` is forbidden. The only exception is a call to the `EntryPoint` described above. * **[OP-062]** Precompiles: * Only allowed the core 9 precompiles.\ Specifically, the computation precompiles that do not access anything in the blockchain state or environment. * **[OP-070]** Transient Storage slots defined in [EIP-1153](./eip-1153) and accessed using `TLOAD` (`0x5c`) and `TSTORE` (`0x5d`) opcodes - are treated exactly like persistent storage (SLOAD/SSTORE) + are treated exactly like persistent storage (SLOAD/SSTORE). ### Code Rules @@ -299,8 +300,8 @@ To help make sense of these params, note that a malicious paymaster can at most The permanent storage access with `SLOAD` and `SSTORE` instructions within each phase is limited as follows: * **[STO-010]** Access to the "account" storage is always allowed. -* Access to associated storage of the account in an external (non-entity contract) is allowed if either: - * **[STO-021]** The account already exists +* Access to associated storage of the account in an external (non-entity) contract is allowed if either: + * **[STO-021]** The account already exists. * **[STO-022]** There is an `initCode` and the `factory` contract is staked. * If the entity (`paymaster`, `factory`) is staked, then it is also allowed: * **[STO-031]** Access the entity's own storage. @@ -327,10 +328,10 @@ The permanent storage access with `SLOAD` and `SSTORE` instructions within each ### Entity-specific rules: * **[EREP-010]** For each `paymaster`, the mempool must maintain the total gas `UserOperations` using this `paymaster` may consume. - * Do not add a `UserOperation` to the mempool if the maximum total gas usage, including the new `UserOperation`, is above the deposit of the `paymaster` at the current gas price. + * Do not add a `UserOperation` to the mempool if the maximum total gas cost, including the new `UserOperation`, is above the deposit of the `paymaster` at the current gas price. * TODO: allow "fractional reserve" based on reputation? * **[EREP-020]** A staked factory is "accountable" for account breaking the rules. \ - That is, if the `validateUserOp()` is rejected for any reason, it is treated as if the factory caused this failure, and thus this affects its reputation. + That is, if the `validateUserOp()` is rejected for any reason in a `UserOperation` that has an `initCode`, it is treated as if the factory caused this failure, and thus this affects its reputation. * **[EREP-030]** A Staked Account is accountable for failures in other entities (`paymaster`, `aggregator`) even if they are staked. * **[EREP-040]** An `aggregator` must be staked, regardless of storage usage. * **[EREP-050]** An unstaked `paymaster` may not return a `context`. @@ -340,9 +341,9 @@ The permanent storage access with `SLOAD` and `SSTORE` instructions within each * Definitions: * **`opsSeen`, `opsIncluded`, and reputation calculation** are defined above. - * `UnstakedReputation` of an entity is the maximum number of entries using this entity allowed in the mempool. + * `UnstakedReputation` of an entity determines the maximum number of entries using this entity allowed in the mempool. * `opsAllowed` is a reputation-based calculation for an unstaked entity, representing how many `UserOperations` it is allowed to have in the mempool. -* **[UREP-010]** `UserOperation` with unstaked sender is only allowed up to `SAME_SENDER_MEMPOOL_COUNT` times in the mempool. +* **[UREP-010]** An unstaked sender is only allowed to have `SAME_SENDER_MEMPOOL_COUNT` `UserOperation`s in the mempool. A staked sender is only limited by the SREP rules. * **[UREP-020]** For other entities: \ `opsAllowed = SAME_UNSTAKED_ENTITY_MEMPOOL_COUNT + (inclusionRate * INCLUSION_RATE_FACTOR) + (min(opsIncluded, 10000)`. * This is a default of `SAME_UNSTAKED_ENTITY_MEMPOOL_COUNT` for a new entity @@ -350,14 +351,14 @@ The permanent storage access with `SLOAD` and `SSTORE` instructions within each ### Alt-mempools rules: -Alternate mempool is an agreed-upon rule that the bundlers may opt in to. +Alternate mempool is an agreed-upon rule that the bundlers may opt into, in addition to the canonical mempool The alt-mempool "topic" is a unique identifier. By convention, this is the IPFS hash of the document describing (in clear test and YAML file) the specifics of this alt mempool * **[ALT-010]** The bundler listens to the alt-mempool "topic" over the P2P protocol * **[ALT-020]** The alt mempool rules MUST be checked only when a canonical rule is violated * That is, if validation follows the canonical rules above, it is not considered part of an alt-mempool. * **[ALT-030]** Bundlers SHOULD forward `UserOperations` to other bundlers only once, regardless of how many (shared) alt-mempools they have. \ - The receiving bundler validates the `UserOperations`, and based on the above rules (and subscribed alt-mempools) decides for which alt-mempools to "tag" it. + The receiving bundler validates the `UserOperations`, and based on the above rules (and subscribed alt-mempools) decides which alt-mempools to propagate it to. ## Security Considerations @@ -367,7 +368,7 @@ from denial-of-service attacks. ### Possible Attacks -Below is a list of possible attacks that were considered and a reference to the above rule that prevents it. +Below are examples of possible attacks that were considered and a reference to the above rule that prevents them. This list does not attempt to be an exhaustive list of attacks. These attacks are examples provided to describe and rationalize the reason for the above rules. @@ -375,13 +376,13 @@ These attacks are examples provided to describe and rationalize the reason for t #### Sample Known (expensive) attack: * This attack can't be fully mitigated by the above validation rules. - But the rules made sure it was expensive to maintain. + But the rules make it expensive to sustain. * Deploy a paymaster which has the following line in the validation function: \ `require((perSenderMap[userOp.sender]&1)==0);` * Submit a large number of `UserOperations` with different senders into the mempool. * Front-run the mempool, and modify the `perSenderMap` for each sender. * By the time the `UserOperations` are validated the second time they are all invalid. -* The cost of the first run is 20000 gas per account, but the second run is 5000 gas per account. +* The cost of the first iteration is 20000 gas per account, and the second one is 5000 gas per account. * NOTE: Because of the rule **[STO-021]** "forbid associated storage during creation", the accounts have to be pre-deployed. This introduces a one-time cost of ~40000 gas per account. * Calculate the cost of blocking the network: * Assuming a bundler can process 1000 `UserOperations`/second @@ -394,10 +395,10 @@ These attacks are examples provided to describe and rationalize the reason for t * Total attack cost * Polygon (@150 gwei) - $18 + $1.5 per block ($2700/hour) * Mainnet: (@40 gwei) - $51840 + $5040 per block ($1.5M/hour) - * Note that the above costs ignore the gas price increase because the underlying network is also overloaded. + * Note that the above costs ignore the gas price increase caused by the increased load on the network. * Mitigations * A **staked factory:** - * Can attempt such an attack once, since after invalidating a single bundle creation its reputation drops and another staked factory has to be used. + * Can only run one iteration of this attack, since after invalidating a single bundle creation its reputation drops and another staked factory has to be used. * So a successful attack needs to deploy and stake a new paymaster on every block. The old paymaster's stake is locked and can be re-used only after 24 hours. * For **unstaked factory**: * As per **[UREP-020]**, it is initially allowed only 10 `UserOperations` in the mempool. From 2d5de10f067229ce69bb478f627f36b86e32cf25 Mon Sep 17 00:00:00 2001 From: Dror Tirosh Date: Sun, 7 Jan 2024 18:34:55 +0200 Subject: [PATCH 07/20] review changes --- erc/ERCS/erc-4337.md | 17 ++++++++++------- erc/ERCS/erc-7562.md | 18 ++++++++++-------- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/erc/ERCS/erc-4337.md b/erc/ERCS/erc-4337.md index 4d0bb7ce..58cb6c3b 100644 --- a/erc/ERCS/erc-4337.md +++ b/erc/ERCS/erc-4337.md @@ -439,9 +439,14 @@ When a bundler includes a bundle in a block it must ensure that earlier transact 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. -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. This method is required to be almost-pure: it is only allowed to access the storage of the account itself, cannot use environment opcodes (eg. `TIMESTAMP`), and can only edit the storage of the account, and can also send out ETH (needed to pay the entry point). The method is gas-limited by the `verificationGasLimit` of the `UserOperation`; nodes can choose to reject operations whose `verificationGasLimit` is too high. These restrictions allow block builders and network nodes to simulate the verification step locally, and be confident that the result will match the result when the operation actually gets included into a block. +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. +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. The alternative would be to require accounts to follow a template where they first self-call to verify and then self-call to execute (so that the execution is sandboxed and cannot cause the fee payment to revert); template-based approaches were rejected due to being harder to implement, as existing code compilation and verification tooling is not designed around template verification. +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. + +The last 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. +For this purpose, the bundler requires a set of [restrictions on the validation function](./erc-7526.md), to prevent such denial-of-service attacks. ### Reputation scoring and throttling/banning for global entities @@ -463,12 +468,10 @@ The stake value is not enforced on-chain, but specifically by each node while si ### Paymasters -Paymasters facilitate transaction sponsorship, allowing third-party-designed mechanisms to pay for transactions. Many of these mechanisms _could_ be done by having the paymaster wrap a `UserOperation` with their own, but there are some important fundamental limitations to that approach: - -* No possibility for "passive" paymasters (eg. that accept fees in some ERC-20 token at an exchange rate pulled from an on-chain DEX) -* Paymasters run the risk of getting griefed, as users could send ops that appear to pay the paymaster but then change their behavior after a block +Paymaster contracts allow abstraction of gas: having a contract, that is not the sender of the transaction, pay for the transaction fees. -The paymaster scheme allows a contract to passively pay on users' behalf under arbitrary conditions. for example, a TokenPaymaster uses the precharge refund model. It can precharge the maximum cost with transferFrom in the validatePaymasterUserOp method and then refund the unused portion in the postOp method. +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. ### First-time account creation diff --git a/erc/ERCS/erc-7562.md b/erc/ERCS/erc-7562.md index 639a5e5d..701df8cf 100644 --- a/erc/ERCS/erc-7562.md +++ b/erc/ERCS/erc-7562.md @@ -37,7 +37,6 @@ For the actual interfaces of those contract-based accounts see the definitions i This documentation uses the terminology "UserOperation" for a transaction created by a smart contract account, and closely follows [ERC-4337](./erc-4337.md) terminology. However, the rules apply to any account-abstraction framework that uses EVM code to perform transaction validation and makes a distinction between validation and execution. - ## Rationale All transactions initiated by EOAs have an implicit validation phase where balance, nonce, and signature are @@ -284,8 +283,9 @@ To help make sense of these params, note that a malicious paymaster can at most * `*CALL` opcodes: * **[OP-061]** `CALL` with `value` is forbidden. The only exception is a call to the `EntryPoint` described above. * **[OP-062]** Precompiles: - * Only allowed the core 9 precompiles.\ - Specifically, the computation precompiles that do not access anything in the blockchain state or environment. + * Only allow known accepted precompiles on the network, that do not access anything in the blockchain state or environment. + * The core precompiles 0x1 .. 0x9 + * The [RIP-7212](./rip-7212.md) sec256r1 precompile, on networks that accepted it. * **[OP-070]** Transient Storage slots defined in [EIP-1153](./eip-1153) and accessed using `TLOAD` (`0x5c`) and `TSTORE` (`0x5d`) opcodes are treated exactly like persistent storage (SLOAD/SSTORE). @@ -307,9 +307,13 @@ The permanent storage access with `SLOAD` and `SSTORE` instructions within each * **[STO-031]** Access the entity's own storage. * **[STO-032]** Read/Write Access to storage slots that are associated with the entity, in any non-entity contract. * **[STO-033]** Read-only access to any storage in non-entity contract. + +### Local Rules + +These are storage rules that help to protect the bundler from denial of service: * **[STO-040]** `UserOperation` may not use an entity address (`factory`/`paymaster`/`aggregator`) that is used as an "account" in another `UserOperation` in the mempool. \ This means that `Paymaster` and `Factory` contracts cannot practically be an "account" contract as well. -* **[STO-041]** A contract whose Associated Storage slot is accessed during a `UserOperation` may not be an address of a "sender" in another `UserOperation` in the mempool. +* **[STO-041]** A contract whose storage is associated with any entity of a `UserOperation` may not be a "sender" of another UserOperation in the mempool. ### Staked Entities Reputation Rules @@ -323,19 +327,17 @@ The permanent storage access with `SLOAD` and `SSTORE` instructions within each * **[SREP-040]** An `OK` staked entity is unlimited by the reputation rule. * Allowed in unlimited numbers in the mempool. * Allowed in unlimited numbers in a bundle. -* **[SREP-050]** If a staked entity fails the second validation or fails bundle creation, its `opsSeen` is incremented by `10000`, causing it to be `BANNED`. +* **[SREP-050]** If a staked entity fails the second validation or fails bundle creation, its `opsSeen` set to `10000`, and `opsIncluded` to zero, causing it to be `BANNED`. ### Entity-specific rules: * **[EREP-010]** For each `paymaster`, the mempool must maintain the total gas `UserOperations` using this `paymaster` may consume. * Do not add a `UserOperation` to the mempool if the maximum total gas cost, including the new `UserOperation`, is above the deposit of the `paymaster` at the current gas price. - * TODO: allow "fractional reserve" based on reputation? + * **[EREP-011]** a staked paymaster is required to have only TODO: allow "fractional reserve" based on reputation? * **[EREP-020]** A staked factory is "accountable" for account breaking the rules. \ That is, if the `validateUserOp()` is rejected for any reason in a `UserOperation` that has an `initCode`, it is treated as if the factory caused this failure, and thus this affects its reputation. * **[EREP-030]** A Staked Account is accountable for failures in other entities (`paymaster`, `aggregator`) even if they are staked. * **[EREP-040]** An `aggregator` must be staked, regardless of storage usage. -* **[EREP-050]** An unstaked `paymaster` may not return a `context`. - * TODO: can be removed once we remove the "2nd postOp". ### Unstaked Entities Reputation Rules From 7d30f00619a65b75ed5ab4cd7211427206189e01 Mon Sep 17 00:00:00 2001 From: Dror Tirosh Date: Sun, 7 Jan 2024 18:41:49 +0200 Subject: [PATCH 08/20] merge fixes --- contracts/test/TestPaymasterAcceptAll.sol | 11 +- gascalc/4-token-paymaster.gas.ts | 132 ---------------------- reports/gas-checker.txt | 44 +++++--- 3 files changed, 27 insertions(+), 160 deletions(-) delete mode 100644 gascalc/4-token-paymaster.gas.ts diff --git a/contracts/test/TestPaymasterAcceptAll.sol b/contracts/test/TestPaymasterAcceptAll.sol index f97ccd4a..7db7dc2c 100644 --- a/contracts/test/TestPaymasterAcceptAll.sol +++ b/contracts/test/TestPaymasterAcceptAll.sol @@ -2,7 +2,6 @@ pragma solidity ^0.8.12; import "../core/BasePaymaster.sol"; -/* solhint-disable no-empty-blocks */ /** * test paymaster, that pays for everything, without any check. @@ -22,14 +21,6 @@ contract TestPaymasterAcceptAll is BasePaymaster { internal virtual override view returns (bytes memory context, uint256 validationData) { (userOp, userOpHash, maxCost); - // return a context, as it is used for EntryPoint gas checking. - return ("1", 0); - } - - function _postOp( - PostOpMode mode, - bytes calldata context, - uint256 actualGasCost - ) internal override { + return ("", 0); } } diff --git a/gascalc/4-token-paymaster.gas.ts b/gascalc/4-token-paymaster.gas.ts deleted file mode 100644 index 1d2dfd96..00000000 --- a/gascalc/4-token-paymaster.gas.ts +++ /dev/null @@ -1,132 +0,0 @@ -import { parseEther } from 'ethers/lib/utils' -import { - TestERC20__factory, TestOracle2__factory, - TestUniswap__factory, - TestWrappedNativeToken__factory, TokenPaymaster, - TokenPaymaster__factory -} from '../typechain' -import { ethers } from 'hardhat' -import { GasCheckCollector, GasChecker } from './GasChecker' -import { Create2Factory } from '../src/Create2Factory' -import { hexValue } from '@ethersproject/bytes' -import { - OracleHelper as OracleHelperNamespace, - UniswapHelper as UniswapHelperNamespace -} from '../typechain/contracts/samples/TokenPaymaster' -import { BigNumber } from 'ethers' -import { createAccountOwner } from '../test/testutils' -// const ethersSigner = ethers.provider.getSigner() - -context('Token Paymaster', function () { - this.timeout(60000) - const g = new GasChecker() - - let paymasterAddress: string - before(async () => { - await GasCheckCollector.init() - const globalSigner = ethers.provider.getSigner() - const create2Factory = new Create2Factory(ethers.provider, globalSigner) - - const ethersSigner = createAccountOwner() - await globalSigner.sendTransaction({ to: ethersSigner.getAddress(), value: parseEther('10') }) - - const minEntryPointBalance = 1e17.toString() - const initialPriceToken = 100000000 // USD per TOK - const initialPriceEther = 500000000 // USD per ETH - const priceDenominator = BigNumber.from(10).pow(26) - - const tokenInit = await new TestERC20__factory(ethersSigner).getDeployTransaction(6) - const tokenAddress = await create2Factory.deploy(tokenInit, 0) - const token = TestERC20__factory.connect(tokenAddress, ethersSigner) - - const wethInit = await new TestWrappedNativeToken__factory(ethersSigner).getDeployTransaction() - const wethAddress = await create2Factory.deploy(wethInit, 0) - const testUniswapInit = await new TestUniswap__factory(ethersSigner).getDeployTransaction(wethAddress) - const testUniswapAddress = await create2Factory.deploy(testUniswapInit, 0) - - const tokenPaymasterConfig: TokenPaymaster.TokenPaymasterConfigStruct = { - priceMaxAge: 86400, - refundPostopCost: 40000, - minEntryPointBalance, - priceMarkup: priceDenominator.mul(15).div(10) // +50% - } - - const nativeAssetOracleInit = await new TestOracle2__factory(ethersSigner).getDeployTransaction(initialPriceEther, 8) - const nativeAssetOracleAddress = await create2Factory.deploy(nativeAssetOracleInit, 0, 10_000_000) - const tokenOracleInit = await new TestOracle2__factory(ethersSigner).getDeployTransaction(initialPriceToken, 8) - const tokenOracleAddress = await create2Factory.deploy(tokenOracleInit, 0, 10_000_000) - - const oracleHelperConfig: OracleHelperNamespace.OracleHelperConfigStruct = { - cacheTimeToLive: 0, - nativeOracle: nativeAssetOracleAddress, - nativeOracleReverse: false, - priceUpdateThreshold: 200_000, // +20% - tokenOracle: tokenOracleAddress, - tokenOracleReverse: false, - tokenToNativeOracle: false - } - - const uniswapHelperConfig: UniswapHelperNamespace.UniswapHelperConfigStruct = { - minSwapAmount: 1, - slippage: 5, - uniswapPoolFee: 3 - } - - const owner = await ethersSigner.getAddress() - - const paymasterInit = hexValue(new TokenPaymaster__factory(ethersSigner).getDeployTransaction( - tokenAddress, - g.entryPoint().address, - wethAddress, - testUniswapAddress, - tokenPaymasterConfig, - oracleHelperConfig, - uniswapHelperConfig, - owner - ).data!) - paymasterAddress = await create2Factory.deploy(paymasterInit, 0) - const paymaster = TokenPaymaster__factory.connect(paymasterAddress, ethersSigner) - await paymaster.addStake(1, { value: 1 }) - await g.entryPoint().depositTo(paymaster.address, { value: parseEther('10') }) - await paymaster.updateCachedPrice(true) - await g.createAccounts1(11) - await token.sudoMint(await ethersSigner.getAddress(), parseEther('20')) - await token.transfer(paymaster.address, parseEther('0.1')) - for (const address of g.createdAccounts) { - await token.transfer(address, parseEther('1')) - await token.sudoApprove(address, paymaster.address, ethers.constants.MaxUint256) - } - - console.log('==addresses:', { - ethersSigner: await ethersSigner.getAddress(), - paymasterAddress, - nativeAssetOracleAddress, - tokenOracleAddress, - tokenAddress, - owner, - createdAccounts: g.createdAccounts - }) - }) - - it('token paymaster', async function () { - await g.addTestRow({ title: 'token paymaster', count: 1, paymaster: paymasterAddress, diffLastGas: false }) - await g.addTestRow({ - title: 'token paymaster with diff', - count: 2, - paymaster: paymasterAddress, - diffLastGas: true - }) - }) - - it('token paymaster 10', async function () { - if (g.skipLong()) this.skip() - - await g.addTestRow({ title: 'token paymaster', count: 10, paymaster: paymasterAddress, diffLastGas: false }) - await g.addTestRow({ - title: 'token paymaster with diff', - count: 11, - paymaster: paymasterAddress, - diffLastGas: true - }) - }) -}) diff --git a/reports/gas-checker.txt b/reports/gas-checker.txt index 3fac0df2..3fbf000b 100644 --- a/reports/gas-checker.txt +++ b/reports/gas-checker.txt @@ -2,9 +2,9 @@ the destination is "account.entryPoint()", which is known to be "hot" address used by this account it little higher than EOA call: its an exec from entrypoint (or account owner) into account contract, verifying msg.sender and exec to target) ╔══════════════════════════╤════════╗ -║ gas estimate "simple" │ 29014 ║ +║ gas estimate "simple" │ 28979 ║ ╟──────────────────────────┼────────╢ -║ gas estimate "big tx 5k" │ 125260 ║ +║ gas estimate "big tx 5k" │ 125224 ║ ╚══════════════════════════╧════════╝ ╔════════════════════════════════╤═══════╤═══════════════╤════════════════╤═════════════════════╗ @@ -12,36 +12,44 @@ ║ │ │ │ (delta for │ (compared to ║ ║ │ │ │ one UserOp) │ account.exec()) ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ simple │ 1 │ 81918 │ │ ║ +║ simple │ 1 │ 81570 │ │ ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ simple - diff from previous │ 2 │ │ 44187 │ 15173 ║ +║ simple - diff from previous │ 2 │ │ 43842 │ 14863 ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ simple │ 10 │ 479730 │ │ ║ +║ simple │ 10 │ 476227 │ │ ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ simple - diff from previous │ 11 │ │ 44247 │ 15233 ║ +║ simple - diff from previous │ 11 │ │ 43873 │ 14894 ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ simple paymaster │ 1 │ 89813 │ │ ║ +║ simple paymaster │ 1 │ 87750 │ │ ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ simple paymaster with diff │ 2 │ │ 44796 │ 15782 ║ +║ simple paymaster with diff │ 2 │ │ 42716 │ 13737 ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ simple paymaster │ 10 │ 493254 │ │ ║ +║ simple paymaster │ 10 │ 472423 │ │ ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ simple paymaster with diff │ 11 │ │ 44820 │ 15806 ║ +║ simple paymaster with diff │ 11 │ │ 42781 │ 13802 ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ big tx 5k │ 1 │ 182975 │ │ ║ +║ big tx 5k │ 1 │ 182627 │ │ ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ big tx - diff from previous │ 2 │ │ 144698 │ 19438 ║ +║ big tx - diff from previous │ 2 │ │ 144341 │ 19117 ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ big tx 5k │ 10 │ 1485374 │ │ ║ +║ big tx 5k │ 10 │ 1481819 │ │ ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ big tx - diff from previous │ 11 │ │ 144759 │ 19499 ║ +║ big tx - diff from previous │ 11 │ │ 144445 │ 19221 ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ token paymaster │ 1 │ 148244 │ │ ║ +║ paymaster+postOp │ 1 │ 89483 │ │ ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ token paymaster with diff │ 2 │ │ 72920 │ 43906 ║ +║ paymaster+postOp with diff │ 2 │ │ 44463 │ 15484 ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ token paymaster │ 10 │ 804795 │ │ ║ +║ paymaster+postOp │ 10 │ 489783 │ │ ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ token paymaster with diff │ 11 │ │ 73015 │ 44001 ║ +║ paymaster+postOp with diff │ 11 │ │ 44508 │ 15529 ║ +╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ +║ token paymaster │ 1 │ 147976 │ │ ║ +╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ +║ token paymaster with diff │ 2 │ │ 72659 │ 43680 ║ +╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ +║ token paymaster │ 10 │ 802242 │ │ ║ +╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ +║ token paymaster with diff │ 11 │ │ 72727 │ 43748 ║ ╚════════════════════════════════╧═══════╧═══════════════╧════════════════╧═════════════════════╝ From 42b15533f98065879119c60f1010a35341f8181e Mon Sep 17 00:00:00 2001 From: Dror Tirosh Date: Sun, 7 Jan 2024 18:55:33 +0200 Subject: [PATCH 09/20] more review fix --- erc/ERCS/erc-7562.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/erc/ERCS/erc-7562.md b/erc/ERCS/erc-7562.md index 701df8cf..d0882bb0 100644 --- a/erc/ERCS/erc-7562.md +++ b/erc/ERCS/erc-7562.md @@ -13,7 +13,7 @@ created: 2023-09-01 ## Abstract This document describes the rules we impose on the validation context of Account Abstraction transactions, -such as [ERC-4337](./eip-4337) `UserOperation` or RIP-7560 (Native Account Abstraction), which are enforced off-chain by a +such as [ERC-4337](./erc-4337.md) `UserOperation` or [RIP-7560](./rip-7560.md) (Native Account Abstraction), which are enforced off-chain by a block builder or a standalone bundler, and the rationale behind each one of them. ## Motivation @@ -30,7 +30,10 @@ However, there is one rule a transaction must follow to preserve the decentraliz The EOA model implicitly follows the rule: a valid transaction can't become invalid without payment by the account: e.g account balance can't be reduced (except with a higher paying transaction) -For Account-Abstraction system to be sustainable and DoS-protected, we define a set of rules to protect it. +This simple rule makes the network sustainable and DoS-protected: the network can't be cheaply attacked by a mass of transactions. An attack (sending a mass of transactions) is expensive, and gets more expensive as the network clogs. Legitimate users pay more, and can delay operations to avoid the cost, but the attacker pays a huge (and increasing) amount to keep the network clogged. + +For Account-Abstraction system we want to keep the same rule, so that attempting a DoS attack on the network should be as expensive. +In order to do so, we add the following validation rules. For the actual interfaces of those contract-based accounts see the definitions in ERC-4337 and RIP-7560. From 55ba9662214b520e155103368f511bf2f5d4ac1e Mon Sep 17 00:00:00 2001 From: Dror Tirosh Date: Tue, 9 Jan 2024 01:13:43 +0200 Subject: [PATCH 10/20] Apply suggestions from code review Co-authored-by: Yoav Weiss --- erc/ERCS/erc-7562.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erc/ERCS/erc-7562.md b/erc/ERCS/erc-7562.md index d0882bb0..0483d4ec 100644 --- a/erc/ERCS/erc-7562.md +++ b/erc/ERCS/erc-7562.md @@ -240,7 +240,7 @@ To help make sense of these params, note that a malicious paymaster can at most 3. A received `UserOperation` may fail any of the reasonable static checks, such as: \ invalid format, values below minimum, submitted with a blockhash that isn't recent, etc. \ In this case, the bundler should drop this particular `UserOperation` but keep the connection. -4. The bundler should validate the `UserOperation` against the nonces of last-included bundles. \ +4. The bundler should check the `UserOperation` against the nonces of last-included bundles. \ Silently drop `UserOperations` with `nonce` that was recently included. This invalidation is likely attributable to a network race condition and should not cause a reputation change. 5. If a received `UserOperation` fails against the current block: From 58608577eefda3f6959bf03d38030afa1a19734f Mon Sep 17 00:00:00 2001 From: Dror Tirosh Date: Thu, 11 Jan 2024 22:24:52 +0200 Subject: [PATCH 11/20] address yoav PR comments --- erc/ERCS/erc-7562.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/erc/ERCS/erc-7562.md b/erc/ERCS/erc-7562.md index 0483d4ec..17fd789a 100644 --- a/erc/ERCS/erc-7562.md +++ b/erc/ERCS/erc-7562.md @@ -316,7 +316,7 @@ The permanent storage access with `SLOAD` and `SSTORE` instructions within each These are storage rules that help to protect the bundler from denial of service: * **[STO-040]** `UserOperation` may not use an entity address (`factory`/`paymaster`/`aggregator`) that is used as an "account" in another `UserOperation` in the mempool. \ This means that `Paymaster` and `Factory` contracts cannot practically be an "account" contract as well. -* **[STO-041]** A contract whose storage is associated with any entity of a `UserOperation` may not be a "sender" of another UserOperation in the mempool. +* **[STO-041]** `UserOperation` may not use associated storage (of either its account or from staked entity) in a contract that is a "sender" of another UserOperation in the mempool. ### Staked Entities Reputation Rules @@ -362,9 +362,17 @@ The alt-mempool "topic" is a unique identifier. By convention, this is the IPFS * **[ALT-010]** The bundler listens to the alt-mempool "topic" over the P2P protocol * **[ALT-020]** The alt mempool rules MUST be checked only when a canonical rule is violated * That is, if validation follows the canonical rules above, it is not considered part of an alt-mempool. +* **[ALT-021]** Such a `UserOperation` (that violates the cannonical rules) is checked against all the "alternate mempools", and is considered part of all those alt-mempools * **[ALT-030]** Bundlers SHOULD forward `UserOperations` to other bundlers only once, regardless of how many (shared) alt-mempools they have. \ The receiving bundler validates the `UserOperations`, and based on the above rules (and subscribed alt-mempools) decides which alt-mempools to propagate it to. +### Alt-mempool reputation: + + +* **[AREP-010]** each alt-mempool manages "opsSeen" and "opsIncluded", much like entities. The opsSeen is incremented after `UserOperation` initial validation, where it is considered part of this mempool. + The "opsIncluded" is incremented after this UserOperation is included on-chain (either by this bundler, or another) +* **[AREP-020]** the alt-mempool becomes THROTTLED based on the [Reputation Calculation](#reputation-calculation) +* **[AREP-030]** if such a UserOperation that passed initial validation, later causes a bundle to revert, it is immediately marked as BANNED, by setting its opsIncluded to 0 and opsSeen to 10000 ## Security Considerations From 48e02581a26bece51413752d0220ec257708c377 Mon Sep 17 00:00:00 2001 From: Dror Tirosh Date: Fri, 12 Jan 2024 09:56:58 +0200 Subject: [PATCH 12/20] added EREP-015: paymaster opsSeen if failure caused by other entity --- erc/ERCS/erc-7562.md | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/erc/ERCS/erc-7562.md b/erc/ERCS/erc-7562.md index 17fd789a..75616f2e 100644 --- a/erc/ERCS/erc-7562.md +++ b/erc/ERCS/erc-7562.md @@ -196,7 +196,6 @@ There are two types of rules: 1. **opsSeen**: a per-entity counter of how many times a unique valid `UserOperation` referencing this entity was received by this bundler. This includes `UserOperation` received via incoming RPC calls or through a P2P mempool protocol. - * For a `paymaster`, this value is not incremented if factory or account validation fails. 2. **opsIncluded**: a per-entity counter of how many times a unique valid `UserOperation` referencing this entity appeared in an actual included `UserOperation`. \ @@ -336,7 +335,8 @@ These are storage rules that help to protect the bundler from denial of service: * **[EREP-010]** For each `paymaster`, the mempool must maintain the total gas `UserOperations` using this `paymaster` may consume. * Do not add a `UserOperation` to the mempool if the maximum total gas cost, including the new `UserOperation`, is above the deposit of the `paymaster` at the current gas price. - * **[EREP-011]** a staked paymaster is required to have only TODO: allow "fractional reserve" based on reputation? +* **[EREP-015]** A `paymaster` should not have its opsSeen incremented on failure of factory or account + * When running 2nd validation (before inclusion in a bundle), if a UserOperation fails because of factory or account error (either a FailOp revert or validation rule), then the paymaster's opsSeen valid is decremented by 1. * **[EREP-020]** A staked factory is "accountable" for account breaking the rules. \ That is, if the `validateUserOp()` is rejected for any reason in a `UserOperation` that has an `initCode`, it is treated as if the factory caused this failure, and thus this affects its reputation. * **[EREP-030]** A Staked Account is accountable for failures in other entities (`paymaster`, `aggregator`) even if they are staked. @@ -379,13 +379,6 @@ The alt-mempool "topic" is a unique identifier. By convention, this is the IPFS This document describes the security considerations bundlers must take to protect themselves (and the entire mempool network) from denial-of-service attacks. -### Possible Attacks - -Below are examples of possible attacks that were considered and a reference to the above rule that prevents them. - -This list does not attempt to be an exhaustive list of attacks. -These attacks are examples provided to describe and rationalize the reason for the above rules. - #### Sample Known (expensive) attack: * This attack can't be fully mitigated by the above validation rules. From 9c94417ecbd2c5b460fce4313cdc468f68f2f2e2 Mon Sep 17 00:00:00 2001 From: shahafn Date: Fri, 12 Jan 2024 12:22:13 +0200 Subject: [PATCH 13/20] Fixing reputation rules --- erc/ERCS/erc-7562.md | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/erc/ERCS/erc-7562.md b/erc/ERCS/erc-7562.md index 75616f2e..d2368684 100644 --- a/erc/ERCS/erc-7562.md +++ b/erc/ERCS/erc-7562.md @@ -317,19 +317,24 @@ These are storage rules that help to protect the bundler from denial of service: This means that `Paymaster` and `Factory` contracts cannot practically be an "account" contract as well. * **[STO-041]** `UserOperation` may not use associated storage (of either its account or from staked entity) in a contract that is a "sender" of another UserOperation in the mempool. -### Staked Entities Reputation Rules -* **[SREP-010]** The "canonical mempool" defines a staked entity if it has `MIN_STAKE_VALUE` and unstake delay of `MIN_UNSTAKE_DELAY` -* **[SREP-020]** A `BANNED` address is not allowed into the mempool.\ +### General Reputation rules +The following reputation rules apply for both staked and unstaked entities. + +* **[GREP-010]** A `BANNED` address is not allowed into the mempool.\ Also, all existing `UserOperations` referencing this address are removed from the mempool. -* **[SREP-030]** A `THROTTLED` address is limited to: +* **[GREP-020]** A `THROTTLED` address is limited to: * `THROTTLED_ENTITY_MEMPOOL_COUNT` entries in the mempool. * `THROTTLED_ENTITY_BUNDLE_COUNT` `UserOperations` in a bundle. * Can remain in the mempool only for `THROTTLED_ENTITY_LIVE_BLOCKS`. -* **[SREP-040]** An `OK` staked entity is unlimited by the reputation rule. +* **[GREP-040]** If an entity fails the second validation or fails bundle creation, its `opsSeen` set to `10000`, and `opsIncluded` to zero, causing it to be `BANNED`. + +### Staked Entities Reputation Rules + +* **[SREP-010]** The "canonical mempool" defines a staked entity if it has `MIN_STAKE_VALUE` and unstake delay of `MIN_UNSTAKE_DELAY` +* **[SREP-020]** An `OK` staked entity is unlimited by the reputation rule. * Allowed in unlimited numbers in the mempool. * Allowed in unlimited numbers in a bundle. -* **[SREP-050]** If a staked entity fails the second validation or fails bundle creation, its `opsSeen` set to `10000`, and `opsIncluded` to zero, causing it to be `BANNED`. ### Entity-specific rules: @@ -348,11 +353,11 @@ These are storage rules that help to protect the bundler from denial of service: * **`opsSeen`, `opsIncluded`, and reputation calculation** are defined above. * `UnstakedReputation` of an entity determines the maximum number of entries using this entity allowed in the mempool. * `opsAllowed` is a reputation-based calculation for an unstaked entity, representing how many `UserOperations` it is allowed to have in the mempool. -* **[UREP-010]** An unstaked sender is only allowed to have `SAME_SENDER_MEMPOOL_COUNT` `UserOperation`s in the mempool. A staked sender is only limited by the SREP rules. -* **[UREP-020]** For other entities: \ - `opsAllowed = SAME_UNSTAKED_ENTITY_MEMPOOL_COUNT + (inclusionRate * INCLUSION_RATE_FACTOR) + (min(opsIncluded, 10000)`. - * This is a default of `SAME_UNSTAKED_ENTITY_MEMPOOL_COUNT` for a new entity -* **[UREP-030]** If an unstaked entity causes an invalidation of a bundle, its `opsSeen` is set to `1000`, effectively blocking it from inclusion for 24 hours. + * Rules: + * **[UREP-010]** An unstaked sender is only allowed to have `SAME_SENDER_MEMPOOL_COUNT` `UserOperation`s in the mempool. A staked sender is only limited by the SREP rules. + * **[UREP-020]** For other unstaked `OK` entities: \ + `opsAllowed = SAME_UNSTAKED_ENTITY_MEMPOOL_COUNT + inclusionRate * (INCLUSION_RATE_FACTOR + min(opsIncluded, 10000))`. + * This is a default of `SAME_UNSTAKED_ENTITY_MEMPOOL_COUNT` for a new entity ### Alt-mempools rules: From 9d278c060320d8e9a70b2ae29fc03270d248f2f1 Mon Sep 17 00:00:00 2001 From: shahafn Date: Sat, 13 Jan 2024 22:17:48 +0200 Subject: [PATCH 14/20] Update erc/ERCS/erc-7562.md Co-authored-by: Yoav Weiss --- erc/ERCS/erc-7562.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erc/ERCS/erc-7562.md b/erc/ERCS/erc-7562.md index d2368684..8fd5782a 100644 --- a/erc/ERCS/erc-7562.md +++ b/erc/ERCS/erc-7562.md @@ -327,7 +327,7 @@ The following reputation rules apply for both staked and unstaked entities. * `THROTTLED_ENTITY_MEMPOOL_COUNT` entries in the mempool. * `THROTTLED_ENTITY_BUNDLE_COUNT` `UserOperations` in a bundle. * Can remain in the mempool only for `THROTTLED_ENTITY_LIVE_BLOCKS`. -* **[GREP-040]** If an entity fails the second validation or fails bundle creation, its `opsSeen` set to `10000`, and `opsIncluded` to zero, causing it to be `BANNED`. +* **[GREP-040]** If an entity fails bundle creation after passing 2nd validation, its `opsSeen` set to `10000`, and `opsIncluded` to zero, causing it to be `BANNED`. ### Staked Entities Reputation Rules From cec04019f65121f74ffaed8cc931991fd78402b4 Mon Sep 17 00:00:00 2001 From: shahafn Date: Sat, 13 Jan 2024 22:23:36 +0200 Subject: [PATCH 15/20] Fixing pr --- erc/ERCS/erc-7562.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/erc/ERCS/erc-7562.md b/erc/ERCS/erc-7562.md index d2368684..6399563e 100644 --- a/erc/ERCS/erc-7562.md +++ b/erc/ERCS/erc-7562.md @@ -171,6 +171,8 @@ There are two types of rules: | `INCLUSION_RATE_FACTOR` | 10 | | | `THROTTLING_SLACK` | 10 | | | `BAN_SLACK` | 50 | | +| `BAN_OPS_SEEN_PENALTY` | 10000 | | +| `MAX_OPS_ALLOWED_UNSTAKED_ENTITY` | 10000 | ### Validation Rules @@ -320,6 +322,7 @@ These are storage rules that help to protect the bundler from denial of service: ### General Reputation rules The following reputation rules apply for both staked and unstaked entities. +All rules apply to all entities unless specified otherwise. * **[GREP-010]** A `BANNED` address is not allowed into the mempool.\ Also, all existing `UserOperations` referencing this address are removed from the mempool. @@ -327,7 +330,7 @@ The following reputation rules apply for both staked and unstaked entities. * `THROTTLED_ENTITY_MEMPOOL_COUNT` entries in the mempool. * `THROTTLED_ENTITY_BUNDLE_COUNT` `UserOperations` in a bundle. * Can remain in the mempool only for `THROTTLED_ENTITY_LIVE_BLOCKS`. -* **[GREP-040]** If an entity fails the second validation or fails bundle creation, its `opsSeen` set to `10000`, and `opsIncluded` to zero, causing it to be `BANNED`. +* **[GREP-040]** If an entity fails the second validation or fails bundle creation, its `opsSeen` set to `BAN_OPS_SEEN_PENALTY`, and `opsIncluded` to zero, causing it to be `BANNED`. ### Staked Entities Reputation Rules @@ -355,9 +358,9 @@ The following reputation rules apply for both staked and unstaked entities. * `opsAllowed` is a reputation-based calculation for an unstaked entity, representing how many `UserOperations` it is allowed to have in the mempool. * Rules: * **[UREP-010]** An unstaked sender is only allowed to have `SAME_SENDER_MEMPOOL_COUNT` `UserOperation`s in the mempool. A staked sender is only limited by the SREP rules. - * **[UREP-020]** For other unstaked `OK` entities: \ - `opsAllowed = SAME_UNSTAKED_ENTITY_MEMPOOL_COUNT + inclusionRate * (INCLUSION_RATE_FACTOR + min(opsIncluded, 10000))`. - * This is a default of `SAME_UNSTAKED_ENTITY_MEMPOOL_COUNT` for a new entity + * **[UREP-020]** For an unstaked paymaster only that is not throttled/banned: \ + `opsAllowed = SAME_UNSTAKED_ENTITY_MEMPOOL_COUNT + inclusionRate * min(opsIncluded, MAX_OPS_ALLOWED_UNSTAKED_ENTITY)`. + * This is a default of `SAME_UNSTAKED_ENTITY_MEMPOOL_COUNT` for a new paymaster ### Alt-mempools rules: From 08211e1dc7434a57f824dcc0ca9b8b2ddcc9a189 Mon Sep 17 00:00:00 2001 From: shahafn Date: Sun, 14 Jan 2024 01:08:17 +0100 Subject: [PATCH 16/20] Update erc-7562.md --- erc/ERCS/erc-7562.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erc/ERCS/erc-7562.md b/erc/ERCS/erc-7562.md index 40dadb38..4f685c09 100644 --- a/erc/ERCS/erc-7562.md +++ b/erc/ERCS/erc-7562.md @@ -350,7 +350,7 @@ All rules apply to all entities unless specified otherwise. * **[EREP-030]** A Staked Account is accountable for failures in other entities (`paymaster`, `aggregator`) even if they are staked. * **[EREP-040]** An `aggregator` must be staked, regardless of storage usage. -### Unstaked Entities Reputation Rules +### Unstaked Paymasters Reputation Rules * Definitions: * **`opsSeen`, `opsIncluded`, and reputation calculation** are defined above. From d36f4887bdbb2d2bdbb3b3883f8aaf1ba2c91df0 Mon Sep 17 00:00:00 2001 From: shahafn Date: Sun, 14 Jan 2024 16:46:34 +0100 Subject: [PATCH 17/20] Update erc-7562.md --- erc/ERCS/erc-7562.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erc/ERCS/erc-7562.md b/erc/ERCS/erc-7562.md index 4f685c09..478a1028 100644 --- a/erc/ERCS/erc-7562.md +++ b/erc/ERCS/erc-7562.md @@ -321,8 +321,7 @@ These are storage rules that help to protect the bundler from denial of service: ### General Reputation rules -The following reputation rules apply for both staked and unstaked entities. -All rules apply to all entities unless specified otherwise. +The following reputation rules apply for all staked entities, and for unstaked paymasters. All rules apply to all of these entities unless specified otherwise. * **[GREP-010]** A `BANNED` address is not allowed into the mempool.\ Also, all existing `UserOperations` referencing this address are removed from the mempool. From 32da22f74974fae266b74a93b785171c8958d50e Mon Sep 17 00:00:00 2001 From: shahafn Date: Sun, 14 Jan 2024 19:24:43 +0200 Subject: [PATCH 18/20] Fixing merge --- erc/ERCS/erc-7562.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erc/ERCS/erc-7562.md b/erc/ERCS/erc-7562.md index 15a11c30..466f5c68 100644 --- a/erc/ERCS/erc-7562.md +++ b/erc/ERCS/erc-7562.md @@ -381,8 +381,7 @@ Alt-mempools are served by the same bundlers participating in the canonical memp * **[AREP-010]** each alt-mempool manages "opsSeen" and "opsIncluded", much like entities. The opsSeen is incremented after `UserOperation` initial validation, where it is considered part of this mempool. The "opsIncluded" is incremented after this UserOperation is included on-chain (either by this bundler, or another) -* **[AREP-020]** the alt-mempool becomes THROTTLED based on the [Reputation Calculation](#reputation-calculation) -* **[AREP-030]** if such a UserOperation that passed initial validation, later causes a bundle to revert, it is immediately marked as BANNED, by setting its opsIncluded to 0 and opsSeen to 10000 +* **[AREP-020]** the alt-mempool becomes THROTTLED based on the [Reputation Calculation](#reputation-calculation) ## Security Considerations From a2eaac3ef520782a09e1ac8da6f70d8ec5018879 Mon Sep 17 00:00:00 2001 From: shahafn Date: Sun, 14 Jan 2024 19:00:24 +0100 Subject: [PATCH 19/20] Update erc/ERCS/erc-7562.md Co-authored-by: Yoav Weiss --- erc/ERCS/erc-7562.md | 1 - 1 file changed, 1 deletion(-) diff --git a/erc/ERCS/erc-7562.md b/erc/ERCS/erc-7562.md index 466f5c68..8a17699a 100644 --- a/erc/ERCS/erc-7562.md +++ b/erc/ERCS/erc-7562.md @@ -337,7 +337,6 @@ The following reputation rules apply for all staked entities, and for unstaked p * **[SREP-020]** An `OK` staked entity is unlimited by the reputation rule. * Allowed in unlimited numbers in the mempool. * Allowed in unlimited numbers in a bundle. -* **[SREP-050]** If a staked entity fails the second validation or fails bundle creation, its `opsSeen` is incremented by `BAN_OPS_SEEN_PENALTY`, causing it to be `BANNED`. ### Entity-specific rules: From 086c3225f58b0af9f3cd559015856d01a36d15e2 Mon Sep 17 00:00:00 2001 From: Yoav Weiss Date: Sun, 14 Jan 2024 20:20:52 +0200 Subject: [PATCH 20/20] Update erc-7562.md: fixed typo --- erc/ERCS/erc-7562.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erc/ERCS/erc-7562.md b/erc/ERCS/erc-7562.md index 8a17699a..a4451516 100644 --- a/erc/ERCS/erc-7562.md +++ b/erc/ERCS/erc-7562.md @@ -369,7 +369,7 @@ The alt-mempool "topic" is a unique identifier. By convention, this is the IPFS * **[ALT-010]** The bundler listens to the alt-mempool "topic" over the P2P protocol * **[ALT-020]** The alt mempool rules MUST be checked only when a canonical rule is violated * That is, if validation follows the canonical rules above, it is not considered part of an alt-mempool. -* **[ALT-021]** Such a `UserOperation` (that violates the cannonical rules) is checked against all the "alternate mempools", and is considered part of all those alt-mempools +* **[ALT-021]** Such a `UserOperation` (that violates the canonical rules) is checked against all the "alternate mempools", and is considered part of all those alt-mempools * **[ALT-030]** Bundlers SHOULD forward `UserOperations` to other bundlers only once, regardless of how many (shared) alt-mempools they have. \ The receiving bundler validates the `UserOperations`, and based on the above rules (and subscribed alt-mempools) decides which alt-mempools to propagate it to. * **[ALT-040]** opsInclude and opsSeen of entities are kept per alt-mempool. That is, an entity can be considered throttled (or banned) in one mempool, while still active on another.