diff --git a/EIPS/eip-7702.md b/EIPS/eip-7702.md index 4334df8339569..5370981e59eb6 100644 --- a/EIPS/eip-7702.md +++ b/EIPS/eip-7702.md @@ -21,7 +21,7 @@ There is a lot of interest in adding short-term functionality improvements to EO * **Batching**: allowing multiple operations from the same user in one atomic transaction. One common example is an [ERC-20](./eip-20.md) approval followed by spending that approval, a common workflow in DEXes that requires two transactions today. Advanced use cases of batching occasionally involve dependencies: the output of the first operation is part of the input to the second operation. * **Sponsorship**: account X pays for a transaction on behalf of account Y. Account X could be paid in some other ERC-20 for this service, or it could be an application operator including the transactions of its users for free. -* **Privilege de-escalation**: users can sign sub-keys, and give them specific permissions that are much weaker than global access to the account. For example, you could imagine a permission to spend ERC-20 tokens but not ETH, or to spend up to 1% of total balance per day, or to interact only with a specific application. +* **Privilege de-escalation**: users can sign sub-keys and give them specific permissions that are much weaker than global access to the account. For example, you could imagine a permission to spend ERC-20 tokens but not ETH, or to spend up to 1% of the total balance per day, or to interact only with a specific application. ## Specification @@ -67,7 +67,7 @@ The [EIP-2718](./eip-2718.md) `ReceiptPayload` for this transaction is `rlp([sta At the start of executing the transaction, after incrementing the sender's nonce, for each `[chain_id, address, nonce, y_parity, r, s]` tuple do the following: 1. Verify the chain id is either 0 or the chain's current ID. -2. `authority = ecrecover(keccak(MAGIC || rlp([chain_id, address, nonce])), y_parity, r, s]` +2. `authority = ecrecover(keccak(MAGIC || rlp([chain_id, address, nonce])), y_parity, r, s])` 3. Add `authority` to `accessed_addresses` (as defined in [EIP-2929](./eip-2929.md).) 4. Verify the code of `authority` is either empty or already delegated. 5. Verify the nonce of `authority` is equal to `nonce`. @@ -81,7 +81,7 @@ Note that the signer of an authorization tuple may be different than `tx.origin` ##### Delegation Designation -The delegation designation uses the banned opcode `0xef` from [EIP-3541](./eip-3541.md) to designate the code has a special purpose. This designator requires all code retrieving operations follow the address pointer to fill the accounts observable code. The following instructions are impacted: `EXTCODESIZE`, `EXTCODECOPY`, `EXTCODEHASH`, `CALL`, `CALLCODE`, `STATICCALL`, `DELEGATECALL`. +The delegation designation uses the banned opcode `0xef` from [EIP-3541](./eip-3541.md) to designate the code has a special purpose. This designator requires all code retrieving operations to follow the address pointer to fill the account's observable code. The following instructions are impacted: `EXTCODESIZE`, `EXTCODECOPY`, `EXTCODEHASH`, `CALL`, `CALLCODE`, `STATICCALL`, `DELEGATECALL`. For example, `EXTCODESIZE` would return the size of the code pointed to by `address` instead of `23` which would represent the delegation designation. `CALL` would similarly load the code from `address` and execute it in the context of `authority`. @@ -97,21 +97,21 @@ If a code reading instruction accesses a cold account during the resolution of d #### Transaction Origination -Modify the restriction put in place by [EIP-3607](./eip-3607.md) to allow EOAs whose code is a valid delegation designation, i.e. `0xef0100 || address`, to continue to originate transactions. Accounts with any other code values may not originate transactions. +Modify the restriction put in place by [EIP-3607](./eip-3607.md) to allow EOAs whose code is a valid delegation designation, i.e., `0xef0100 || address`, to continue to originate transactions. Accounts with any other code values may not originate transactions. ## Rationale ### No initcode -Running initcode is not desirable for many reasons. The chief concern is it's unnatural. Initcode is intended to initialize and deploy contracts. With this EIP, it will take on a new role of determine whether it is appropriate to deploy code to the EOA. Suppose a user only wants code deployed to their account if they also have an operation bundled with the general transaction calldata. This gives EOA a unique power to control when and what code executes in their account. Although [EIP-7702](./eip-7702.md) as written still allows this to a degree, the lack of programmability in the decision will force wallets to not sign many authorization tuples and instead focus signing only a tuple pointing to a configurable proxy. This affords EOAs a similar experience to smart contract wallets +Running initcode is not desirable for many reasons. The chief concern is it's unnatural. Initcode is intended to initialize and deploy contracts. With this EIP, it will take on a new role of determining whether it is appropriate to deploy code to the EOA. Suppose a user only wants code deployed to their account if they also have an operation bundled with the general transaction calldata. This gives EOAs a unique power to control when and what code executes in their account. Although [EIP-7702](./eip-7702.md) as written still allows this to a degree, the lack of programmability in the decision will force wallets to not sign many authorization tuples and instead focus on signing only a tuple pointing to a configurable proxy. This affords EOAs a similar experience to smart contract wallets. -Additionally, initcode in transaction tends to be propagated inside the transaction. That means it would need to be included in the authorization tuple and signed over. The minimum initcode would be around 15 bytes and that would simply copy the contract code from an external address. The total cost would be `16 * 15 = 240` calldata cost, plus the [EIP-3860](./eip-3860.md) cost of `2 * 15 = 30`, plus the runtime costs of around `150`. So nearly `500` additional gas would be spent simply preparing the account; and even more likely, 1200+ gas if not copying from an external account. +Additionally, initcode in a transaction tends to be propagated inside the transaction. That means it would need to be included in the authorization tuple and signed over. The minimum initcode would be around 15 bytes, and that would simply copy the contract code from an external address. The total cost would be `16 * 15 = 240` calldata cost, plus the [EIP-3860](./eip-3860.md) cost of `2 * 15 = 30`, plus the runtime costs of around `150`. So nearly `500` additional gas would be spent simply preparing the account; and even more likely, 1200+ gas if not copying from an external account. ### Creation by template -Initcode or not, there is a question of how users should specify the code they intend to run in their account. The two main options are to specify the bytecode directly in the transaction or to specify a pointer to the code. The simplest pointer would just the address of some code deployed on-chain. +Initcode or not, there is a question of how users should specify the code they intend to run in their account. The two main options are to specify the bytecode directly in the transaction or to specify a pointer to the code. The simplest pointer would just be the address of some code deployed on-chain. -The cost analysis makes the answer clear. The smallest proxy would be around 50 bytes and an address is 20 bytes. The 30 byte difference provides no useful additional functionality and will be inefficiently replicated billions of times on the chain. +The cost analysis makes the answer clear. The smallest proxy would be around 50 bytes, and an address is 20 bytes. The 30 byte difference provides no useful additional functionality and will be inefficiently replicated billions of times on the chain. Furthermore, specifying code directly would again make it possible for EOAs to have a new, unique ability to execute arbitrary code specified in the transaction calldata. @@ -121,7 +121,7 @@ Consistency is a valuable property in the EVM, both from an implementation persp The main families of instructions where a ban was considered were storage related and contract creation related. The decision to not ban storage instructions hinged mostly on their importance to smart contract wallets. Although it's possible to have an external storage contract that the smart contract wallet calls into, it is unnecessarily inefficient. In the future, new state schemes may even allow substantially cheaper access to certain storage slots. This is something smart contract wallets will very much want to take advantage of that a storage contract wouldn't support. -Creation instructions were considered for a ban on other similar EIPs, however because this EIP allows EOAs to spend value intra-transaction, the concern with bumping the nonce intra-transaction and invalidating pending transactions is not significant. A neat byproduct of this is that by combining EIP-7702 and CREATE2 it will be possible to commit to deploy specific bytecode to an address without committing to any fee market parameters. This solves the long standing issue of universal cross-chain contract deployment. +Creation instructions were considered for a ban on other similar EIPs, however because this EIP allows EOAs to spend value intra-transaction, the concern with bumping the nonce intra-transaction and invalidating pending transactions is not significant. A neat byproduct of this is that by combining EIP-7702 and CREATE2 it will be possible to commit to deploying specific bytecode to an address without committing to any fee market parameters. This solves the long-standing issue of universal cross-chain contract deployment. ### Signature structure @@ -129,35 +129,35 @@ The signature scheme in this EIP supports flexible design patterns, allowing for #### Code pointer -One consideration when signing a code pointer is what code might that address point to on another chain. For some use cases, it may not be desirable to expend the effort verifying the deployment was deterministic. In such situations, the chain ID can be set to reduce the scope of the authorization. For other situations where universal deployment is preferred, e.g. delegating to a wallet proxy. In these cases, it's possible to set chain ID to 0 for validity on all EIP-7702 chains. Wallet maintainers will be able to hard code a single EIP-7702 authorization message into their wallet so that cross-chain code malleability never becomes a concern. +One consideration when signing a code pointer is what code might that address point to on another chain. For some use cases, it may not be desirable to expend the effort verifying the deployment was deterministic. In such situations, the chain ID can be set to reduce the scope of the authorization. For other situations where universal deployment is preferred, e.g., delegating to a wallet proxy, it's possible to set chain ID to 0 for validity on all EIP-7702 chains. Wallet maintainers will be able to hard code a single EIP-7702 authorization message into their wallet so that cross-chain code malleability never becomes a concern. -An alternative to adding chain ID could be to sign over the code the address points to. This seems to have the benefit of both minimizing the on-chain size of auth tuples while retaining specificity of the actual code running in the account. One unfortunate issue of this format though is that it imposes a database lookup to determine the signer of each auth tuple. This imposition itself seems to create enough complexity in transaction propagation that it is decided to avoid and simply sign over address directly. +An alternative to adding chain ID could be to sign over the code the address points to. This seems to have the benefit of both minimizing the on-chain size of auth tuples while retaining specificity of the actual code running in the account. One unfortunate issue of this format, though, is that it imposes a database lookup to determine the signer of each auth tuple. This imposition itself seems to create enough complexity in transaction propagation that it is decided to avoid and simply sign over the address directly. #### In-protocol revocation -Unlike previous versions of this EIP and EIPs similar, the delegation designation can be revoked at anytime signing and sending a EIP-7702 authorization to a new target with the account's current nonce. Without such action, a delegation will remain valid in perpetuity. +Unlike previous versions of this EIP and similar EIPs, the delegation designation can be revoked at any time by signing and sending an EIP-7702 authorization to a new target with the account's current nonce. Without such action, a delegation will remain valid in perpetuity. ### Self-sponsoring: allowing `tx.origin` to set code Allowing `tx.origin` to set code enables simple transaction batching, where the sender of the outer transaction would be the signing account. The ERC-20 approve-then-transfer pattern, which currently requires two separate transactions, could be completed in a single transaction with this proposal. -Once code exists in the EOA, it's possible for self-sponsored EIP-7702 transactions to have `msg.sender == tx.origin` anytime the code in the EOA dispatches a call. Without EIP-7702, this situation can only ever arise in the topmost execution layer of a transaction. Therefore this EIP breaks that invariant and so it affects smart contracts containing `require(msg.sender == tx.origin)` checks. This check is used for at least three purposes: +Once code exists in the EOA, it's possible for self-sponsored EIP-7702 transactions to have `msg.sender == tx.origin` anytime the code in the EOA dispatches a call. Without EIP-7702, this situation can only ever arise in the topmost execution layer of a transaction. Therefore, this EIP breaks that invariant and so it affects smart contracts containing `require(msg.sender == tx.origin)` checks. This check is used for at least three purposes: 1. Ensuring that `msg.sender` is an EOA (given that `tx.origin` always has to be an EOA). This invariant does not depend on the execution layer depth and, therefore, is not affected. - 2. Protecting against atomic sandwich attacks like flash loans, that rely on the ability to modify state before and after the execution of the target contract as part of the same atomic transaction. This protection would be broken by this EIP. However, relying on `tx.origin` in this way is considered bad practice, and can already be circumvented by miners conditionally including transactions in a block. + 2. Protecting against atomic sandwich attacks like flash loans, which rely on the ability to modify state before and after the execution of the target contract as part of the same atomic transaction. This protection would be broken by this EIP. However, relying on `tx.origin` in this way is considered bad practice, and can already be circumvented by miners conditionally including transactions in a block. 3. Preventing reentrancy. -Examples of (1) and (2) can be found in contracts deployed on Ethereum mainnet, with (1) being more common (and unaffected by this proposal.) On the other hand, use case (3) is more severely affected by this proposal, but the authors of this EIP did not find any examples of this form of reentrancy protection, though the search was non-exhaustive. +Examples of (1) and (2) can be found in contracts deployed on Ethereum mainnet, with (1) being more common (and unaffected by this proposal). On the other hand, use case (3) is more severely affected by this proposal, but the authors of this EIP did not find any examples of this form of reentrancy protection, though the search was non-exhaustive. -This distribution of occurrences—many (1), some (2), and no (3)—is exactly what the authors of this EIP expect, because: +This distribution of occurrences—many (1), some (2), and no (3)—is exactly what the authors of this EIP expect because: - * Determining if `msg.sender` is an EOA without `tx.origin` is difficult (if not impossible.) + * Determining if `msg.sender` is an EOA without `tx.origin` is difficult (if not impossible). * The only execution context which is safe from atomic sandwich attacks is the topmost context, and `tx.origin == msg.sender` is the only way to detect that context. - * In contrast, there are many direct and flexible ways of preventing reentrancy (ex. using a transient storage variable.) Since `msg.sender == tx.origin` is only true in the topmost context, it would make an obscure tool for preventing reentrancy, rather than other more common approaches. + * In contrast, there are many direct and flexible ways of preventing reentrancy (e.g., using a transient storage variable). Since `msg.sender == tx.origin` is only true in the topmost context, it would make an obscure tool for preventing reentrancy, rather than other more common approaches. There are other approaches to mitigate this restriction which do not break the invariant: - * Set `tx.origin` to a constant `ENTRY_POINT` address when using `CALL*` instruction in the context of an EOA. + * Set `tx.origin` to a constant `ENTRY_POINT` address when using the `CALL*` instruction in the context of an EOA. * Set `tx.origin` to a special address derived from the sender or signer addresses. * Disallow `tx.origin` from setting code. This would make the simple batching use cases impossible, but could be relaxed in the future. @@ -169,13 +169,13 @@ Specifically: * The `address` that users sign could literally point to existing ERC-4337 wallet code. * The "code pathways" that are used are code pathways that would, in many cases (though perhaps not all), continue to "make sense" in a pure-smart-contract-wallet world. -* Hence, it avoids the problem of "creating two separate code ecosystems", because to a large extent they would be the same ecosystem. There would be some workflows that require kludges under this solution that would be better done in some different "more native" under "endgame AA", but this is relatively a small subset. +* Hence, it avoids the problem of "creating two separate code ecosystems" because, to a large extent, they would be the same ecosystem. There would be some workflows that require kludges under this solution that would be better done in some different "more native" under "endgame AA", but this is relatively a small subset. * It does not require adding any opcodes, that would become dangling and useless in a post-EOA world. * It allows EOAs to masquerade as contracts to be included in ERC-4337 bundles, in a way that's compatible with the existing `EntryPoint`. ## Backwards Compatibility -This EIP breaks the invariant that an account balance can only decrease as a result of transactions originating from that account. It also breaks the invariant that an EOA nonce may not increase after transaction execution has begun. These breakages have consequences for mempool design, and for other EIPs such as inclusion lists. However, because the accounts are listed statically in the outer transaction it is possible to modify transaction propagation rules so that conflicting transactions are not forwarded. +This EIP breaks the invariant that an account balance can only decrease as a result of transactions originating from that account. It also breaks the invariant that an EOA nonce may not increase after transaction execution has begun. These breakages have consequences for mempool design, and for other EIPs such as inclusion lists. However, because the accounts are listed statically in the outer transaction, it is possible to modify transaction propagation rules so that conflicting transactions are not forwarded. ## Security Considerations @@ -183,7 +183,7 @@ This EIP breaks the invariant that an account balance can only decrease as a res The following is a non-exhaustive list of checks/pitfalls/conditions that delegate contracts *should* be wary of and require a signature over from the account's authority: - * Replay protection -- (ex. a nonce) should be implemented by the delegate and signed over. Without it, a malicious actor can reuse a signature, repeating its effects. + * Replay protection (e.g., a nonce) should be implemented by the delegate and signed over. Without it, a malicious actor can reuse a signature, repeating its effects. * `value` -- without it, a malicious sponsor could cause unexpected effects in the callee. * `gas` -- without it, a malicious sponsor could cause the callee to run out of gas and fail, griefing the sponsee. * `target` / `calldata` -- without them, a malicious actor may call arbitrary functions in arbitrary contracts. @@ -201,7 +201,7 @@ The authors of this EIP believe the risks of allowing this are acceptable for th ### Sponsored transaction relayers -It is possible for the `authorized` account to cause sponsored transaction relayers to spend gas without being reimbursed by either invalidating the authorization (i.e. increasing the account's nonce) or by sweeping the relevant assets out of the account. Relayers should be designed with these cases in mind, possibly by requiring a bond to be deposited or by implementing a reputation system. +It is possible for the `authorized` account to cause sponsored transaction relayers to spend gas without being reimbursed by either invalidating the authorization (i.e., increasing the account's nonce) or by sweeping the relevant assets out of the account. Relayers should be designed with these cases in mind, possibly by requiring a bond to be deposited or by implementing a reputation system. ### Front running initialization