DCIPs/EIPS/eip-2938.md

288 lines
28 KiB
Markdown

---
eip: 2938
title: Account Abstraction
author: Vitalik Buterin (@vbuterin), Ansgar Dietrichs (@adietrichs), Matt Garnett (@lightclient), Will Villanueva (@villanuevawill), Sam Wilson (@SamWilsn)
discussions-to: https://ethereum-magicians.org/t/eip-2938-account-abstraction/4630
status: Stagnant
type: Standards Track
category: Core
created: 2020-09-04
requires: 2718
---
## Simple Summary
Account abstraction (AA) allows a contract to be the top-level account that pays fees and starts transaction execution.
## Abstract
**See also: [https://ethereum-magicians.org/t/implementing-account-abstraction-as-part-of-eth1-x/4020](https://ethereum-magicians.org/t/implementing-account-abstraction-as-part-of-eth1-x/4020) and the links therein for historical work and motivation.**
Transaction validity, as of Muir Glacier, is defined rigidly by the protocol: ECDSA signature, a simple nonce, and account balance. Account abstraction extends the validity conditions of transactions with the execution of arbitrary EVM bytecode (with some limits on what state may be accessed.) To signal validity, we propose a new EVM opcode `PAYGAS`, which also sets the gas price and gas limit the contract is willing to pay.
We split account abstraction into two tiers: **single-tenant AA**, which is intended to support wallets or other use cases with few participants, and **multi-tenant AA**, which is intended to support applications with many participants (eg. tornado.cash, Uniswap).
## Motivation
The existing limitations preclude innovation in a number of important areas, particularly:
1. Smart contract wallets that use signature verification other than ECDSA (eg. Schnorr, BLS, post-quantum...)
2. Smart contract wallets that include features such as multisig verification or social recovery, reducing the highly prevalent risk of funds being lost or stolen
3. Privacy-preserving systems like [tornado.cash](http://tornado.cash)
4. Attempts to improve gas efficiency of DeFi protocols by preventing transactions that don't satisfy high-level conditions (eg. existence of a matching order) from being included on chain
5. Users being able to pay for transaction fees in a token other than ETH (eg. by converting that token into the ETH needed for fees inside the transaction in real-time)
Most of the above use cases are currently possible using intermediaries, most notably the [Gas Station Network](https://www.opengsn.org/) and application-specific alternatives. These implementations are (i) technically inefficient, due to the extra 21000 gas to pay for the relayer, (ii) economically inefficient, as relayers need to make a profit on top of the gas fees that they pay. Additionally, use of intermediary protocols means that these applications cannot simply rely on base Ethereum infrastructure and need to rely on extra protocols that have smaller userbases and higher risk of no longer being available at some future date.
Out of the five use cases above, single-tenant AA approximately supports (1) and (2), and multi-tenant AA approximately supports (3) and (4). We discuss the differences between the two tiers in the specification and rationale sections below.
## Specification
### Single Tenant
After `FORK_BLOCK`, the following changes will be recognized by the protocol.
#### Constants
| Constant | Value |
| - | - |
| **`AA_ENTRY_POINT`** | `0xffffffffffffffffffffffffffffffffffffffff` |
| **`AA_TX_TYPE`** | `2` |
| **`FORK_BLOCK`** | TBD |
| **`AA_BASE_GAS_COST`** | 15000 |
#### New Transaction Type
A new [EIP-2718](./eip-2718.md) transaction with type `AA_TX_TYPE` is introduced. Transactions of this type are referred to as "AA transactions". Their payload should be interpreted as `rlp([nonce, target, data])`.
The base gas cost of this transaction is set to `AA_BASE_GAS_COST` instead of 21000 to reflect the lack of "intrinsic" ECDSA and signature.
Nonces are processed analogously to existing transactions (check `tx.nonce == tx.target.nonce`, transaction is invalid if this fails, otherwise proceed and immediately set `tx.nonce += 1`).
Note that this transaction type has no intrinsic gas limit; when beginning execution, the gas limit is simply set to the remaining gas in the block (ie. `block.gas_limit` minus gas spent on previous transactions), and the `PAYGAS` opcode (see below) can adjust the gas limit downwards.
#### Transaction-wide global variables
Introduce some new transaction-wide global variables. These variables work similarly (in particular, have similar reversion logic) to the SSTORE refunds counter.
| Variable | Type | Initial value |
| - | - | - |
| `globals.transaction_fee_paid` | `bool` | `False if type(tx) == AA_TX_TYPE else True` |
| `globals.gas_price` | `int` | `0 if type(tx) == AA_TX_TYPE else tx.gas_price` |
| `globals.gas_limit` | `int` | `0 if type(tx) == AA_TX_TYPE else tx.gas_limit` |
#### `NONCE (0x48)` Opcode
A new opcode `NONCE (0x48)` is introduced, with gas cost `G_base`, which pushes the `nonce` of the callee onto the stack.
#### `PAYGAS (0x49)` Opcode
A new opcode `PAYGAS (0x49)` is introduced, with gas cost `G_base`. It takes two arguments off the stack: (top) `version_number`, (second from top) `memory_start`. In the initial implementation, it will `assert version_number == 0` and read:
* `gas_price = bytes_to_int(vm.memory[memory_start: memory_start + 32])`
* `gas_limit = bytes_to_int(vm.memory[memory_start + 32: memory_start + 64])`
Both reads use similar mechanics to MLOAD and CALL, so memory expands if needed.
Future hard forks may add support for different version numbers, in which case the opcode may take different-sized memory slices and interpret them differently. Two particular potential use cases are [EIP 1559](https://notes.ethereum.org/@vbuterin/BkSQmQTS8) and [the escalator mechanism](https://ethresear.ch/t/another-simple-gas-fee-model-the-escalator-algorithm-from-the-agoric-papers/6399).
The opcode works as follows. If all three of the following conditions (in addition to the version number check above) are satisfied:
1. The account's balance is `>= gas_price * gas_limit`
2. `globals.transaction_fee_paid == False`
3. We are in a top-level AA execution frame (ie. if the currently running EVM execution exits or reverts, the EVM execution of the entire transaction is finished)
Then do the following:
* Subtract `gas_price * gas_limit` from the contract's balance
* Set `globals.transaction_fee_paid` to `True`
* Set `globals.gas_price` to `gas_price`, and `globals.gas_limit` to `gas_limit`
* Set the remaining gas in the current execution context to equal `gas_limit` minus the gas that was already consumed
If any of the above three conditions are not satisfied, throw an exception.
At the end of execution of an AA transaction, it is mandatory that `globals.transaction_fee_paid == True`; if it is not, then the transaction is invalid. At the end of execution, the contract is refunded `globals.gas_price * remaining_gas` for any remaining gas, and `(globals.gas_limit - remaining_gas) * globals.gas_price` is transferred to the miner.
`PAYGAS` also serves as an EVM execution _checkpoint_: if the top-level execution frame reverts after `PAYGAS` has been called, then the execution only reverts up to the point right after `PAYGAS` was called, and exits there. In that case, the contract receives no refund, and `globals.gas_limit * globals.gas_price` is transferred to the miner.
#### Replay Protection
One of the two following approaches must be implemented to safeguard against replay attacks.
##### Require `SET_INDESTRUCTIBLE`
Require that contracts targeted by AA transactions begin with [EIP-2937]'s `SET_INDESTRUCTIBLE` opcode. AA transactions targeting contracts that do not begin with `SET_INDESTRUCTIBLE` are invalid, and cannot be included in blocks.
`AA_PREFIX` would need to be modified to include this opcode.
[EIP-2937]: ./eip-2937.md
##### Preserve Nonce on `SELFDESTRUCT`
The other option is to preserve contract nonces across `SELFDESTRUCT` invocations, instead of setting the nonce to zero.
#### Miscellaneous
* If `CALLER (0x33)` is invoked in the first frame of
execution of a call initiated by an AA transaction, then it must return `AA_ENTRY_POINT`.
* If `ORIGIN (0x32)` is invoked in any frame of execution of an AA
transaction it must return `AA_ENTRY_POINT`.
* The `GASPRICE (0x3A)` opcode now pushes the value `globals.gas_price`
Note that the new definition of `GASPRICE` does not lead to any changes in behavior in non-AA transactions, because `globals.gas_price` is initialized to `tx.gas_price` and cannot be changed as `PAYGAS` cannot be called.
#### Mining and Rebroadcasting Strategies
Much of the complexity in account abstraction originates from the strategies used by miners and validating nodes to determine whether or not to accept and rebroadcast transactions. Miners need to determine if a transaction will actually pay the fee if they include it after only a small amount of processing to avoid [DoS attacks](https://hackingdistributed.com/2016/06/28/ethereum-soft-fork-dos-vector/). Validating nodes need to perform an essentially identical verification to determine whether or not to rebroadcast the transaction.
By keeping the consensus changes minimal, this EIP allows for gradual introduction of AA mempool support by miners and validating nodes. Initial support would be focused on enabling simple, single-tenant use cases, while later steps would additionally allow for more complex, multi-tenant use cases. Earlier stages are deliberately more fully fleshed-out than later stages, as there is still more time before later stages need to be implemented.
##### Transactions with Fixed Nonces
| Constant | Value |
| - | - |
| `VERIFICATION_GAS_MULTIPLIER` | `6` |
| `VERIFICATION_GAS_CAP` | `= VERIFICATION_GAS_MULTIPLIER * AA_BASE_GAS_COST = 90000` |
| `AA_PREFIX` | `if(msg.sender != shr(-1, 12)) { LOG1(msg.sender, msg.value); return }`; compilation to EVM TBD |
When a node receives an AA transaction, they process it (i.e. attempt to execute it against the current chain head's post-state) to determine its validity, continuing to execute until one of several events happens:
* If the code of the `target` is NOT prefixed with `AA_PREFIX`, exit with failure
* If the execution hits any of the following, exit with failure:
* An environment opcode (`BLOCKHASH`, `COINBASE`, `TIMESTAMP`, `NUMBER`, `DIFFICULTY`, `GASLIMIT`)
* `BALANCE` (of any account, including the `target` itself)
* An external call/create that changes the `callee` to anything but the `target` or a precompile (`CALL`, `CALLCODE`, `STATICCALL`, `CREATE`, `CREATE2`).
* An external state access that reads code (`EXTCODESIZE`, `EXTCODEHASH`, `EXTCODECOPY`, but also `CALLCODE` and `DELEGATECALL`), unless the address of the code that is read is the `target`.
* If the execution consumes more gas than `VERIFICATION_GAS_CAP` (specified above), or more gas than is available in the block, exit with failure
* If the execution reaches `PAYGAS`, then exit with success or failure depending on whether or not the balance is sufficient (e.g. `balance >= gas_price * gas_limit`).
Nodes do not keep transactions with nonces higher than the current valid nonce in the mempool. If the mempool already contains a transaction with a currently valid nonce, another incoming transaction to the same contract and with the same nonce either replaces the existing one (if its gas price is sufficiently higher) or is dropped. Thus, the mempool keeps only at most one pending transaction per account.
While processing a new block, take note of which accounts were the `target` of an AA transaction (each block currently has `12500000` gas and an AA transaction costs `>= 15000` so there would be at most `12500000 // 15000 = 833` targeted accounts). Drop all pending transactions targeting those accounts. All other transactions remain in the mempool.
### Single Tenant+
If the [indestructible contracts EIP](http://github.com/ethereum/EIPs/pull/2937) is added, Single Tenant AA can be adapted to allow for `DELEGATECALL` during transaction verification: during execution of a new AA transaction, external state access that reads code (`EXTCODESIZE`, `EXTCODEHASH`, `EXTCODECOPY`, `CALLCODE`, `DELEGATECALL`) of any contract whose first byte is the `SET_INDESTRUCTIBLE` opcode is no longer banned. However, calls to anything but the `target` or a precompile that change the `callee` (i.e., calls other than `CALLCODE` and `DELEGATECALL`) are still not permitted.
If the [IS_STATIC EIP](http://github.com/ethereum/EIPs/pull/2975) is added, the list of allowed prefixes can be extended to allow a prefix that enables incoming static calls but not state-changing calls.
The list of allowed prefixes can also be extended to enable other benign use cases (eg. logging incoming payments).
External calls _into_ AA accounts can be allowed as follows. We can add an opcode `RESERVE_GAS`, which takes as argument a value `N` and has simple behavior: immediately burn `N` gas and add `N` gas to the refund. We then add an allowed `AA_PREFIX` that reserves `>= AA_BASE_GAS_COST * 2` gas. This ensures that at least `AA_BASE_GAS_COST` gas must be spent (as refunds can refund max 50% of total consumption) in order to call into an account and invalidate transactions targeting that account in the mempool, preserving that invariant.
Note that accounts may also opt to set a higher `RESERVE_GAS` value in order to safely have a higher `VERIFICATION_GAS_CAP`; the goal would be to preserve a `VERIFICATION_GAS_MULTIPLIER`-to-1 ratio between the minimum gas cost to edit an account (ie. half its `RESERVE_GAS`) and the `VERIFICATION_GAS_CAP` that is permitted that account. This would also preserve invariants around maximum reverification gas consumption that are implied by the previous section.
### Multi-Tenant & Beyond
In a later stage, we can add support for multiple pending transactions per account in the mempool. The main challenge here is that a single transaction can potentially cause state changes that invalidate all other transactions to that same account. Additionally, if we naively prioritize transactions by gasprice, there is an attack vector where the user willing to pay the highest gasprice publishes many (mutually exclusive) versions of their transaction with small alterations, thereby pushing everyone else's transactions out of the mempool.
Here is a sketch of a strategy for mitigating this problem. We would require incoming transactions to contain an [EIP-2930](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2930.md)-style access list detailing the storage slots that the transaction reads or modifies, and make it binding; that is, accesses outside the access list would be invalid. A transaction would only be included in the mempool if its access list is disjoint from the access lists of other transactions in the mempool (or if its gasprice is higher). An alternative way to think about this is to have per-storage-slot mempools instead of just per-account mempools, except a transaction could be part of multiple per-storage-slot mempools (if desired it could be capped to eg. 5 storage slots).
Note also that multi-tenant AA will almost certainly require allowing miners to edit the nonces of incoming transactions to put them into sequence, with the result that the final hash of a transaction is unpredictable at publication time. Clients will need to explicitly work around this.
More research is required to refine these ideas, and this is left for later work.
## Rationale
The core problem in an account abstraction setup is always that miners and network nodes need to be able to verify that a transaction that they attempt to include, or rebroadcast, will actually pay a fee. Currently, this is fairly simple, because a transaction is guaranteed to be includable and pay a fee as long as the signature and nonce are valid and the balance and gasprice are sufficient. These checks can be done quickly.
In an account abstraction setup, the goal is to allow accounts to specify EVM code that can establish more flexible conditions for a transaction's validity, but with the requirement that this EVM code can be quickly verified, with the same safety properties as the existing setup.
In a normal transaction, the top-level call goes from the `tx.sender` to `tx.to` and carries with it `tx.value`. In an AA transaction, the top-level call goes from the _entry point address_ (`0xFFFF...FF`) to the `tx.target`.
The top-level code execution is expected to be split into two phases: the shorter **verification phase** (before `PAYGAS`) and the longer **execution phase** (after `PAYGAS`). If execution throws an exception during the verification phase, the transaction is invalid, much like a transaction with an invalid signature in the current system. If execution throws an exception after the verification phase, the transaction pays fees, and so the miner can still include it.
The transition between different stages of AA is entirely done through changes in miner strategy. The first stage supports **single-tenant AA**, where the only use cases that can be easily implemented are where the `tx.target` is a contract representing a user account (that is, a smart contract wallet, eg. multisig). Later stages improve support for eg. logs and libraries, and also move toward supporting **multi-tenant AA**, where the goal is to try to support cases where the `tx.target` represents an _application_ that processes incoming activity from multiple users.
### Nonces still enshrined in single-tenant AA
Nonces are still enforced in single-tenant AA to ensure that single-target AA does not break the invariant that each transaction (and hence each transaction hash) can only be included in the chain once. While there is some limited value in allowing arbitrary-order transaction inclusion in single-tenant AA, there is not enough value to justify breaking that invariant.
Note that nonces in AA accounts do end up having a dual-purpose: they are both there for replay protection and for contract address generation when using the `CREATE` opcode. This does mean that a single transaction could increment the nonce by more than 1. This is deemed acceptable, as the other mechanics introduced by AA already break the ability to easily verify that a chain longer than one transaction can be processed. However, we strongly recommend that AA contracts use `CREATE2` instead of `CREATE`.
In multi-tenant AA, as mentioned above, nonces are expected to become malleable and applications that use multi-tenant AA systems would need to manage this.
### Nonces are exposed to the EVM
This is done to allow signature checking done in validation code to validate the nonce.
### Replay Protection
One of the above two approaches (requiring `SET_INDESTRUCTIBLE` or modifying `SELFDESTRUCT` behavior) must be implemented so that nonces cannot be reused. It must be a consensus change, and not simply part of `AA_PREFIX`, so that transaction hash uniqueness is maintained.
### Miners refuse transactions that access external data or the target's own balance, before PAYGAS
An important property of traditional transactions is that activity happening as part of transactions that originate outside of some given account X cannot make transactions whose sender is X invalid. The only state change that an outside transaction can impose on X is increasing its balance, which cannot invalidate a transaction.
Allowing AA contracts to access external data (both other accounts and environment variables such as GASPRICE, DIFFICULTY, etc.) before they call `PAYGAS` (ie. during the verification phase) breaks this invariant. For example, imagine someone sends many thousands of AA transactions that perform an external call `if FOO.get_number() != 5: throw()`. `FOO.number` might be set to `5` when those transactions are all sent, but a single transaction to `FOO` could set the `number` to something else, invalidating _all of the thousands of AA transactions_ that depend on it. This would be a serious DoS vector.
The one allowed exception is contracts that are indestructible (that is, whose first byte is the `SET_INDESTRUCTIBLE` opcode defined in [this EIP](https://hackmd.io/@HWeNw8hNRimMm2m2GH56Cw/SyNT3Cdmw)). This is a safe exception, because the data that is being read cannot be changed.
Disallowing reading `BALANCE` blocks a milder attack vector: an attacker could force a transaction to be reprocessed at a mere cost of 6700 gas (not 15000 or 21000), in the worst case more than doubling the number of transactions that would need to be reprocessed.
In the long term, AA could be expanded to allow reading external data, though protections such as mandatory access lists would be required.
### AA transactions must call contracts with prefix
The prelude is used to ensure that *only* AA transactions can call the contract. This is another measure taken to ensure the invariant described above. If this check did not occur, it would be possible for a transaction originating outside some AA account X to call into X and make a storage change, forcing transactions targeting that account to be reprocessed at the cost of a mere 5000 gas.
### Multi-tenant AA
Multi-tenant AA extends single-tenant AA by **better handling cases where distinct and uncoordinated users attempt to send transactions for/to the same account and those transactions may interfere with each other**.
We can understand the value of multi-tenant AA by examining two example use cases: (i) [tornado.cash](http://tornado.cash) and (ii) [Uniswap](http://uniswap.exchange). In both of these cases, there is a single central contract that represents the application, and not any specific user. Nevertheless, there is important value in using abstraction to do application-specific validation of transactions.
#### Tornado Cash
The tornado.cash workflow is as follows:
1. A user sends a transaction to the TC contract, depositing some standard quantity of coins (eg. 1 ETH). A record of their deposit, containing the hash of a secret known by the user, is added to a Merkle tree whose root is stored in the TC contract.
2. When that user later wants to withdraw, they generate and send a ZK-SNARK proving that they know a secret whose hash is in a leaf somewhere in the deposit tree (without revealing where). The TC contract verifies the ZK-SNARK, and also verifies that a nullifier value (also derivable from the secret) has not yet been spent. The contract sends 1 ETH to the user's desired address, and saves a record that the user's nullifier has been spent.
The privacy provided by TC arises because when a user makes a withdrawal, they can prove that it came from _some_ unique deposit, but no one other than the user knows which deposit it came from. However, implementing TC naively has a fatal flaw: the user usually does not yet have ETH in their withdrawal address, and if the user uses their deposit address to pay for gas, that creates an on-chain link between their deposit address and their withdrawal address.
Currently, this is solved via relayers; a third-party relayer verifies the ZK-SNARK and unspent status of the nullifier, publishes the transaction using their own ETH to pay for gas, and collects the fee back from the user from the TC contract.
AA allows this without relayers: the user could simply send an AA transaction targeting the TC contract, the ZK-SNARK verification and the nullifier checking can be done in the verification step, and PAYGAS can be called directly after that. This allows the withdrawer to pay for gas directly out of the coins going to their withdrawal address, avoiding the need for relayers or for an on-chain link to their deposit address.
Note that fully implementing this functionality requires AA to be structured in a way that supports multiple users sending withdrawals at the same time (requiring nonces would make this difficult), and that allows a single account to support both AA transactions (the withdrawals) and externally-initiated calls (the deposits).
#### Uniswap
A new version of Uniswap could be built that allows transactions to be sent that directly target the Uniswap contract. Users could deposit tokens into Uniswap ahead of time, and Uniswap would store their balances as well as a public key that transactions spending those balances could be verified against. An AA-initiated Uniswap trade would only be able to spend these internal balances.
This would be useless for normal traders, as normal traders have their coins outside the Uniswap contract, but it would be a powerful boon to arbitrageurs. Arbitrageurs would deposit their coins into Uniswap, and they would be able to send transactions that perform arbitrage every time external market conditions change, and logic such as price limits could be enforced during the verification step. Hence, transactions that do not get in (eg. because some other arbitrageur made the trade first) would not be included on-chain, allowing arbitrageurs to not pay gas, and reducing the number of "junk" transactions that get included on-chain. This could significantly increase both de-facto blockchain scalability as well as market efficiency, as arbitrageurs would be able to much more finely correct for cross-exchange discrepancies between prices.
Note that here also, Uniswap would need to support both AA transactions and externally-initiated calls.
## Backwards Compatibility
This AA implementation preserves the existing transaction type. The use of `assert origin == caller` to verify that an account is an EOA remains sound, but is not extensible to AA accounts; AA transactions will always have `origin == AA_ENTRY_POINT`.
Badly-designed single-tenant AA contracts will break the transaction non-malleability invariant. That is, it is possible to take an AA transaction in-flight, modify it, and have the modified version still be valid; AA account contracts can be designed in such a way as to make that not possible, but it is their responsibility. Multi-tenant AA will break the transaction non-malleability invariant much more thoroughly, making the transaction hash unpredictable even for legitimate applications that use the multi-tenant AA features (though the invariant will not further break for applications that existed before then).
AA contracts may not have replay protection unless they build it in explicitly; this can be done with the `CHAINID (0x46)` opcode introduced in [EIP 1344](./eip-1344.md).
## Test Cases
See: [https://github.com/quilt/tests/tree/account-abstraction](https://github.com/quilt/tests/tree/account-abstraction)
## Implementation
See: [https://github.com/quilt/go-ethereum/tree/account-abstraction](https://github.com/quilt/go-ethereum/tree/account-abstraction)
## Security Considerations
See [https://ethresear.ch/t/dos-vectors-in-account-abstraction-aa-or-validation-generalization-a-case-study-in-geth/7937](https://ethresear.ch/t/dos-vectors-in-account-abstraction-aa-or-validation-generalization-a-case-study-in-geth/7937) for an analysis of DoS issues.
### Re-validation
When a transaction enters the mempool, the client is able to quickly ascertain whether the transaction is valid. Once it determines this, it can be confident that the transaction will continue to be valid unless a transaction from the same account invalidates it.
There are, however, cases where an attacker can publish a transaction that invalidates existing transactions and requires the network to perform more recomputation than the computation in the transaction itself. The EIP maintains the invariant that recomputation is bounded to a theoretical maximum of six times the block gas limit in a single block; this is somewhat more expensive than before, but not that much more expensive.
#### Peer denial-of-service
Denial-of-Service attacks are difficult to defend against, due to the difficulty in identifying sybils within a peer list. At any moment, one may decide (or be bribed) to initiate an attack. This is not a problem that Account Abstraction introduces. It can be accomplished against existing clients today by inundating a target with transactions whose signatures are invalid. However, due to the increased allotment of validation work allowed by AA, it's important to bound the amount of computation an adversary can force a client to expend with invalid transactions. For this reason, it's best for the miner to follow the recommended mining strategies.
## Copyright
Copyright and related rights waived via [CC0](../LICENSE.md).