Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add EIP: Set EOA account code for one transaction #8527

Merged
merged 7 commits into from
May 8, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
98 changes: 98 additions & 0 deletions EIPS/eip-7702.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
---
eip: 7702
title: Set EOA account code for one transaction
description: Add a new tx type that sets the code for an EOA during one transaction execution
author: Vitalik Buterin (@vbuterin), Sam Wilson (@SamWilsn), Ansgar Dietrichs (@adietrichs), Matt Garnett (@lightclient)
discussions-to: https://ethereum-magicians.org/t/eip-set-eoa-account-code-for-one-transaction/19923
status: Draft
type: Standards Track
category: Core
created: 2024-05-07
requires: 2718, 2930
---
vbuterin marked this conversation as resolved.
Show resolved Hide resolved

## Abstract

Add a new transaction type that adds a `contract_code` field and a signature, and converts the signing account (not necessarily the same as the `tx.origin`) into a smart contract wallet for the duration of that transaction. Intended to offer similar functionality to [EIP-3074](./eip-3074.md).

## Motivation

There is a lot of interest in adding short-term functionality improvements to EOAs, increasing the usability of applications and in some cases allowing improved security. Three particular applications include:

* **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.

[EIP-3074](./eip-3074.md) solves all of these use cases. However, it has forward-compatibility concerns:

* It introduces two opcodes, `AUTH` and `AUTHCALL`, that would have no use in an "endgame account abstraction" world where eventually all users are using smart contract wallets (which seems like it must happen eventually, at the least because eventually quantum computers will break the ECDSA that EOAs use)
* It leads to the development of an "invoker contract" ecosystem that would be separate from the "smart contract wallet" ecosystem, leading to possible fragmentation of effort.

The purpose of this EIP is to enable all of the use cases of EIP-3074, without these two weaknesses.

## Specification

The keywords “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119.

### Parameters

* `FORK_BLKNUM` = `TBD`
* `TX_TYPE` = `TBD`
* `MAGIC` = `TBD`
* `PER_CONTRACT_CODE_BASE_COST` = `5000`

As of `FORK_BLOCK_NUMBER`, a new [EIP-2718](./eip-2718.md) transaction is introduced with `TransactionType` = `TX_TYPE(TBD)`.

The [EIP-2718](./eip-2718.md) `TransactionPayload` for this transaction is

```
rlp([chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, destination, data, access_list, [[contract_code, y_parity, r, s], ...], signature_y_parity, signature_r, signature_s])
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A small suggestion: when y_parity is 0 or 1, it's a temporary setting code. When it's 2 or 3, it's secp256r1 and a permanent setting code. When it's 4 or 5, it's secp256k1 and a permanent setting code (currently not supported).

Your idea is very similar to mine, but you chose the tx type, so there's no need to add an opcode.
https://github.com/ethereum/EIPs/pull/8493/files

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a new field, don't overload yParity. We don't need more obscure parsing rules.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. If EIP7702 has already been implemented, the cost of adding a new algorithm isn't high.

  2. The risk of permanently upgrading an EOA to a CA is significant (as EOAs carry historical baggage), but addresses generated by algorithms like secp256r1 are not EOAs, so permanently setting code carries no additional risk.

  3. People need a more stable way to ensure deterministic deployments.

  4. Better backward compatibility: When temporary code setting is prohibited and only permanent code setting is allowed, simply reject that type and enable a new type.

  5. Prevent user errors: In the past, Ethereum used block heights to determine certain code behaviors, which wasn't user-friendly. We should directly reject user requests via a flag since upgrading an EOA to a CA is risky.

Copy link

@txgyy txgyy May 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a new field, don't overload yParity. We don't need more obscure parsing rules.

Yeah, a new flag like this:
0x00: Temporary upgrade And secp256k1
0x10: Permanent upgrade And secp256k1
0x01: Temporary upgrade And secp256r1
0x11: Permanent upgrade And secp256r1
...

```

The intrinsic cost of the new transaction is inherited from [EIP-2930](./eip-2930.md), specifically `21000 + 16 * non-zero calldata bytes + 4 * zero calldata bytes + 1900 * access list storage key count + 2400 * access list address count`. Additionally, we add a cost of `16 * non-zero calldata bytes + 4 * zero calldata bytes` over each `contract_code`, plus `PER_CONTRACT_CODE_BASE_COST` times the length of the `contract_code` array.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems expensive, can users not point to an already deployed contract and reduce data size of txn payload?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My thinking is that that's what delegatecall forwarders are for. But happy to consider the direct-to-address option too.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minimal proxy accounts have very little bytecode. Using ERC-1167 should be sufficient and keep this EIP lean


At the start of executing the transaction, for each `[contract_code, y_parity, r, s]` tuple:

1. Let `signer = ecrecover(keccak(MAGIC + contract_code), y_parity, r, s]`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This means that a signature is reusable and non revocable. That can then be reused and encapsulated into other transactions.
If the code is vulnerable (for any reason) the signer becomes vulnerable indefinitely

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Assuming 'MAGIC' is a constant value)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the code tuple contained the signer's nonce then the same revocation strategy as 3074 could be used. While that solves revocation, it's unclear to me if the tuples being reusable is a feature or a bug though.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3074 includes the invokerAddress in the AUTH payload, so auth is only reusable by a single invoker, so perhaps the tuple also an address field to compare tx.origin against?

2. Verify that the contract code of `signer` is empty.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is important to mention that the outcome of this "verification" is only whether the signer's code is set.
That is: if the ecrecover succeeds (doesn't return zero), AND the returned signer address doesn't have code, only then its code is set to contract_code.
In any other circumstance, it is left unmodified.

3. Set the contract code of `signer` to `contract_code`.

At the end of the transaction, set the `contract_code` of each `signer` back to empty.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I presume during transaction execution the EXTCODEHASH will return the contract_code, right?

Note that the signer of any of the `contract_code` signatures, and the `tx.origin` of the transaction, are allowed to be different.

Copy link
Contributor

@Amxx Amxx May 7, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
### Opcode restriction
For security reasons, some opcodes MUST not be executed in the context of an EOA:
- `SSTORE` (0x55): Setting storage under an EOA breaks common assumptions. Conflits could arise from a single EOA using multiple code vertsion (in multiple transactions) that interpret the storage layout under the account differently. Therefore, EOAs should be forbiden from performing `SSTORE` if the code is to be cleared at the end of the transaction. Any CALL performed by the code placed at the EOA's address is free to manipulate storage normally.
- `CREATE` (0xF0), `CREATE2` (0xF5) and `SELFDESTRUCT` (0xFF): There may be an expectation that transactions from a given sender should have consecutive nonces. This assumption would be broken if code placed at an EOA's address was to execute one or multiple operations that alter the sender account's nonce. Consequently, EOA performing a delegate transaction should not be able to use the `CREATE`, `CREATE2` or `SELFDESTRUCT` opcodes. Any CALL performed by the code placed at the EOA's address is free to create contracts normally.
Any attempts to execute one of these restricted operations in the context of one of the signers MUST throw an exception.
This restrictions only apply if the code placed at the signer's address is removed at the end of the transaction. If a flag is set to not set the code back to empty at the end of the transactions, then these restrictions do not apply.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with restricting CREATE, CREATE2 and SELFDESTRUCT but not sure what harm SSTORE poses. While the code stored at the address is deleted, all written storage slots can be cleared alongside it, that way it isn't persisted across different transactions.

I'd argue that the gas cost of SSTORE and SLOAD be the same as that of TLOAD and TSTORE since they'd behave exactly the same way in this scenario.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want to be able to SSTORE.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd also want to use storage, but that would be breaking invariants specified in other EIP

There is a restriction that account with no code should have no storage. If someone can find a référénce that would be great.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True, but I think if all written slots are cleared at the end of the transaction it doesn't break any invariants.

During the transaction it doesn't break any invariants too since if the code length, code or codehash is queried, it returns a non-default value.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a restriction that account with no code should have no storage. If someone can find a référénce that would be great.

Not so sure about this because a contract that sets storage at initialization but also returns 0 bytes at initialization has no bytecode but has > 0 storage slots set.

See here: https://twitter.com/AmadiMichaels/status/1641918500020137984?t=sy3aLWqR4we0tuA_kXV20w&s=19

## Rationale

### Conversion of EIP-3074 use cases

In this design, it requires fairly little work to convert an existing EIP-3074 workflow. Specifically, AUTH and AUTHCALL would get replaced by calls into the EOA. One way to do this is that the `contract_code` would be a user wallet (which could be a `DELEGATECALL` forwarder to save gas), and would expose two functions, `verify` and `execute`.

* AUTH would be replaced by a code to `verify`, which would use TSTORE to locally set `authorized[msg.sender, ...] = True`.
* AUTHCALL would be replaced by a call to `execute`, which would use TLOAD to verify `authorized[msg.sender, ...]`, and then execute from there.

Hence, there is a very simple transformation from "existing EIP-3074 workflows" into workflows under this new scheme.

### Forward-compatibility with future account abstraction

This EIP is designed to be very forward-compatible with endgame account abstraction, without over-enshrining any fine-grained details of [ERC-4337](./eip-4337.md) or RIP-7560.

Specifically:

* The contract code that users would need to sign could literally be 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.
* It does not require adding any opcodes, that would become dangling and useless in a post-EOA world.
* It allows EOAs to temporarily convert themselves into contracts to be included in ERC-4337 bundles, in a way that's compatible with the existing `EntryPoint`.
* Once this is implemented, [EIP-5003](./eip-5003.md) is "only one line of code": just add a flag to not set the code back to empty at the end.

## Backwards Compatibility

This EIP breaks the invariant that an account balance can only decrease as a result of transactions originating from that account. This has consequences for mempool design, and for other EIPs such as inclusion lists. However, these issues are common to any proposal that provides similar functionality, including EIP-3074.

## Security Considerations
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume it is, but just to confirm - this EIP doesn't allow storage modification of the EOA right?
e.g., SSTORE, etc

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


Many security considerations with EIP-3074 are shared. Particularly, user wallets need to be very careful about which `contract_code` they sign.

## Copyright

Copyright and related rights waived via [CC0](../LICENSE.md).