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

WIP: Add support for uninterrupted delegatecall chains #10

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 43 additions & 12 deletions EIPS/eip-7701.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,11 @@ Relying on the concept of "code sections" introduced with EIP-3540 appears to be

### Constants

| Name | Value |
|------------|-------|
| AA_TX_TYPE | TBD |
| Name | Value |
|------------------|--------------------------------------------|
| AA_TX_TYPE | TBD |
| AA_BASE_GAS_COST | 15000 |
| AA_ORIGIN | 0x7701000000000000000000000000000000007701 |

### New Transaction Type

Expand All @@ -61,6 +63,9 @@ Transactions of this type are referred to as

The contents of such transactions are fully described in RIP-7560.

The base gas cost of this transaction is set to `AA_BASE_GAS_COST` instead of 21000 to reflect the lack of "intrinsic"
ECDSA signature verification.

### System-level code entry points

Modify the EOF container format to consist of the following sections:
Expand All @@ -81,6 +86,7 @@ entry_points_section := (entry_point_role, target_section_index, target_section_
```

For regular calls to the contract, the execution always starts at the first byte of code section 0, and `pc` is set to

0.

Here the `entry_points_section` defines alternative indexes of code sections to start the execution for system calls.
Expand All @@ -94,11 +100,12 @@ The contract that has a role in an Account Abstraction transaction, either as a
has to contain all necessary sections marked with one of the following `entry_point_role` markers:

```
role_sender_execution = 0x00000000
role_sender_deployment = 0x00000001
role_sender_validation = 0x00000002
role_paymaster_validation = 0x00000003
role_paymaster_post_tx = 0x00000004
role_none = 0x0000
role_sender_execution = 0x0001
role_sender_deployment = 0x0002
role_sender_validation = 0x00003
role_paymaster_validation = 0x0004
role_paymaster_post_tx = 0x0005
```

This section is equivalent to a code section.
Expand All @@ -109,6 +116,18 @@ If it is the first code section of a contract, it can act as an entry point duri
Only a single section per role is allowed in a contract.
This rule is validated during contract creation.

### Context variable for the `entry_point_role` value

During the execution of the `Sender`, `Paymaster` or a `Deployer` code as defined by the `AA_TX_TYPE` transaction,
the global `entry_point_role` variable is set to the corresponding role.
Copy link

Choose a reason for hiding this comment

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

How is this variable accessed from EVM? A new opcode?

Copy link
Author

Choose a reason for hiding this comment

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

This variable is not actually visible inside the EVM. It is used by the EVM implementation to decide which code section to enter when using DELEGATECALL.

Copy link

Choose a reason for hiding this comment

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

We could encode that variable into ORIGIN so instead of 0x7701000000000000000000000000000000007701 it would return 0x7701000000000000000000000000000000000000 | entry_point_role but I'm not sure if we should. May be useful if a function called by the role's code section needs to know the context. OTOH the code itself could pass it as an arg if it's needed.

The `entry_point_role` remains set through an uninterrupted chain of `DELEGATECALL`/`EXTDELEGATECALL` calls.

The default value for `entry_point_role` is `role_none`. Call frames initiated with any opcodes other than
`DELEGATECALL`/`EXTDELEGATECALL` run with the default role.

Frames that originate as part of the EIP-7701 transaction have their `ORIGIN` and `SENDER` opcodes
return the `AA_ORIGIN` address.

### Execution entry point for Account Abstraction transaction type participant entity (Sender, Paymaster and Deployer)

During a regular contract code execution, its behaviour is defined as follows by EIP-3540:
Expand All @@ -118,15 +137,27 @@ Execution starts at the first byte of code section 0, and pc is set to 0
```

However, if a contract is referenced in an `AA_TX_TYPE` transaction as a `Sender`, `Paymaster` or a `Deployer`,
execution starts at the first byte of code section with the `entry_point_role` marker corresponding to the current step,
execution starts at the first byte of code section with the current `entry_point_role` variable value,
and `pc` is set to `0`.

If the specified contract does not contain such a section, or is not an EOF contract, the transaction is not valid.
If the specified contract does not contain such a section, or is not an EOF contract,
execution starts at code section 0, and pc is set to 0.

The transaction is considered invalid if it never reached a code section corresponding to the current `entry_point_role`
Copy link

Choose a reason for hiding this comment

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

I know the goal is to support legacy proxies but it seems risky:

  1. Suppose a contract that implements user-controlled callbacks (such as an ERC-1155 token that calls onERC1155BatchReceived) is called this way. An attacker could deploy a contract that implements the methodsig of onERC1155BatchReceived to call its own role_sender_validation and role_sender_execution sections which accept the transaction. Will this result in executing the attacker's call from the token contract?
  2. DoS resistance. Many proxies could point to the same implementation, which could then invalidate many mempool transactions. We have no way to associate the proxy with the implementation and ensure 1:1 relation. How do we mitigate this?

Copy link
Author

@forshtat forshtat Jan 10, 2025

Choose a reason for hiding this comment

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

Re 1: The the entry_point_role is only preserved (and can activate) through an uninterrupted chain of DELEGATECALLs. Calling the malicious contract with a regular CALL does not carry the entry_point_role, and the callbacks cannot authorize a transaction.

Re 2: As the implementation in this scenario is an EOF contract, which means it has no SELFDESTRUCT or any other way to modify its own code, and must be called with DELEGATECALL meaning its storage cannot be accessed, I don't see a way for the "owner" of the implementation contract to invalidate a transaction. Am I missing something?

Copy link

Choose a reason for hiding this comment

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

Re 2: Its storage can't be accessed, but environment opcodes can still be used, no?

Copy link

Choose a reason for hiding this comment

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

Got it. That makes sense. I previously misunderstood the uninterrupted chain of DELEGATECALLs part. Looks like it does solve the proxy case, neat.

Re 2: As the implementation in this scenario is an EOF contract, which means it has no SELFDESTRUCT or any other way to modify its own code,

Since it's a chain, a non-EOF link in the chain could break the chain and never reach an EOF contract. But your assumption still holds since there's no SELFDESTRUCT for an existing non-EOF contract either and it can't access its own storage. Therefore invalidation can only be done in the account itself, making it an O(n) attack. Seems fine.

Re 2: Its storage can't be accessed, but environment opcodes can still be used, no?

Since it can't use storage to divert the flow to a different implementation, the opcodes during offchain simulation and block inclusion are the same. Therefore the ERC-7562 rules can ensure that no banned opcodes are used.

in any contract called through an uninterrupted chain of `DELEGATECALL`/`EXTDELEGATECALL` calls.

The transaction is considered invalid if it reached such a code section but did not
perform [explicit acceptance](#accepting-the-transaction-by-sender-or-paymaster).

The `target_section_flags` parameter is added to provide signaling into the EVM so that EOF can perform some additional
validations as part of EOF code validation.
The description of specific flags and their impact on EOF validation shall be added as a separate EIP.

### Accepting the transaction by `Sender` or `Paymaster`

In order for the `Sender` or `Paymaster` contracts to accept the transaction,
they must perform a `CALL` to the `AA_ORIGIN` address with the appropriate acceptance data as call data.
Copy link

Choose a reason for hiding this comment

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

Does the call have to be performed by the actual Sender / Paymaster, or just in its context? If it's the former then the above proxy-support method, then proxies won't work. If the latter, then the security concerns I wrote above apply.

Copy link
Author

Choose a reason for hiding this comment

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

I think supporting existing proxy contracts is essential, so the call must be made in the context of the Sender / Paymaster. As I replied, I don't think there is a security concern unless there is a DELEGATECALL to an untrusted contract, in which case the attack does not require messing with roles.


### Encoding inputs for different execution frames

#### Sender Deployment
Expand Down Expand Up @@ -159,7 +190,7 @@ abi.encode(
This step is performed with the `role_sender_deployment` code section.

In order for the transaction to be considered valid the
sender validation frame MUST return two 64-bit values:
sender validation frame MUST provide the following acceptance data values:

```
abi.encode(bool success, uint64 validUntil, uint64 validAfter)
Expand All @@ -172,7 +203,7 @@ Inputs to the `Paymaster` validation section are same as the ones in the [Sender
This step is performed with the `role_paymaster_validation` code section.

In order for the transaction to be considered valid the
paymaster validation frame MUST return the following values:
paymaster validation frame MUST provide the following acceptance data values:

```
abi.encode(bool success, uint64 validUntil, uint64 validAfter, bytes context)
Expand Down
Loading