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 RIP: Native Account Abstraction #3

Merged
merged 4 commits into from
Jan 31, 2024

Conversation

forshtat
Copy link
Contributor

@forshtat forshtat commented Nov 16, 2023

EIP draft to support a native account abstraction transaction type. This is one of three EIPs: AA transaction, aggregation, validation rules. See https://hackmd.io/@alexforshtat/native-account-abstraction-roadmap
and https://vitalik.eth.limo/general/2023/09/30/enshrinement.html

@CarlBeek
Copy link
Contributor

@forshtat, please update your RIP number to 7560, it is the next in the EIP/RIP sequence


This value is denominated in wei and is passed from the `sender` to the `coinbase` as part of the gas pre-charge.

### Unused gas penalty charge
Copy link
Member

Choose a reason for hiding this comment

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

this seems like a frustrating + confusing ux ding

Copy link
Contributor Author

Choose a reason for hiding this comment

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

this seems like a frustrating + confusing ux ding

The "unused gas penalty" was one of two mitigations to a potential problem of a transaction consuming unpredictable amounts of gas and significantly complicating the block builder's computation.
Please see the description of the issue here:
https://ethereum-magicians.org/t/rip-7560-native-account-abstraction/16664/12?u=alex-forshtat-tbk

If there is a better approach to preventing this problem the "unused gas penalty" should be removed from this EIP.


If `deployerData` is specified, its first 20 bytes contain the address of a `deployer` contract.

### Optional "transaction counter header"
Copy link
Member

Choose a reason for hiding this comment

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

I think including this in the block is not right. I would instead include an additional header field which stores a list (or root of a list) of index ranges. So if txs 1 through 3 and 8 through 15 are AA, the header list would be [1, 3, 8, 15])

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Making this "counter" a separate transaction type was a complicated decision.
For context, in ERC-4337 there is a feature of "signature aggregation" - when there is a separate signature verification phase that is shared by multiple UserOperations in the block. This phase can access and modify state which makes it somewhat similar to a separate transaction.
Signature aggregation was not included in this document due to its complexity and it is still in the works. To solve it we intended to introduce an "aggregated signature verification transaction header" in the future, which would include the actual "transaction" that validates all signatures.
And that is the reason the "counter header" is also defined as a transaction subtype here.

Copy link
Member

Choose a reason for hiding this comment

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

IIUC, you're saying you also have another tx header thing which validates an aggregated signature for ops following it? If that is the case I also think that is the wrong solution. These are really things that should live in the header rather than carving up the block object with markers.

Copy link
Contributor

Choose a reason for hiding this comment

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

Aggregation is not a "marker" but an actual transaction that executes EVM code and changes the state.

In 4337 (and similarly its native RIP counterpart), aggregation is abstracted. An aggregator is a contract that looks at a set of transactions from accounts that delegate their signature validation to that aggregator by returning its address in their validation function. The ERC and the future aggregation RIP support multiple aggregations with multiple aggregators in the same bundle/block. Each aggregation consists of an aggregator address and a set of transactions. There could be any number of such aggregations in the same block.

The aggregator runs in the context of validating the transactions in its set, and may be affected by EVM state and modify it. Therefore it must be executed just before the first transaction in the set.

Considering the above, would it make sense to put these aggregations in the header? It would require the header to have a list of aggregations, each containing an aggregator address and a pointer to the first transaction in its set. The validator would have to keep referring to this list as it walks through the transactions in the block. Wouldn't it be less efficient then placing an aggregation transaction just before the first transaction?

If you see a better way to support aggregation abstraction, we'd be happy to collaborate on it. We're still working on it and haven't published an RIP for it yet.

Copy link
Member

Choose a reason for hiding this comment

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

Can you point to something describing this? I was only commenting on the "header transaction" concept. I don't see where it says it executes EVM code?

Copy link
Contributor

@yoavw yoavw Dec 11, 2023

Choose a reason for hiding this comment

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

The aggregation transaction is not in this RIP - it'll be a separate one. But it'll be similar to how aggregation works in ERC-4337:

  • For every UserOp that returns an aggregator in its validateUserOp, the bundler may remove UserOp.signature and use the aggregator instead. E.g. BLSAccount has a BLS key and returns the address of BLSSignatureAggregator.
  • The bundler then aggregates the signatures of all the UserOps that returned the same aggregator, and calls EntryPoint.handleAggregatedOps instead of the usual handleOps. The bundle's calldata doesn't include individual signatures - only a single BLS signature.
  • On-chain, each aggregator is called exactly once, and verifies the aggregated signature for the UserOps listed under this aggregator's address.
  • EntryPoint then calls the validation functions of every account (as it always does) and requires that they return the aggregator that they were listed under (or return true if it's validated without an aggregator).

It's described in detail in ERC-4337, but that's the general idea.

Some use cases:

  • The BLS aggregator (which exists in the main repo)
  • A more generic SNARK aggregator for verifying other signature schemes - something PSE has been researching. Theoretically a SNARK aggregator could verify the signatures of an entire block with a single proof, supporting multiple signature schemes.
  • Hacks unrelated to signature aggregation. For example Dan Finlay once asked to add atomicity to the protocol, allowing UserOps to be valid only if included together. Instead of adding this complexity to 4337, we proposed an AtomicAggregator which achieves the same result without protocol changes. Such hacks will likely be used by "intent solvers" - a topic that became quite popular lately.

Our next RIP is going to be the equivalent of this 4337 feature. It'll be another subtype of TransactionType4, which calls an aggregator for the N transactions that come after it. The block will be valid only if the aggregation transaction hasn't reverted, and the N transactions after it all return this aggregator in their validateTransaction.

There are still open questions and we deferred the aggregation RIP until we finalize this one. When we're back on it soon, let's collaborate on how it should be implemented.

Alex only mentioned aggregation as the reason for using this "header transaction" to specify the next N transactions rather than using a different method - because we're going to have such a transaction subtype anyway for aggregation so we may as well use it for another use case. It's basically an aggregation function without calling an aggregator.

RIPS/rip-7560.md Outdated Show resolved Hide resolved

### New Transaction Type

A new [EIP-2718](./eip-2718) transaction with type AA_TX_TYPE is introduced. Transactions of this type are referred to as

Choose a reason for hiding this comment

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

Suggested change
A new [EIP-2718](./eip-2718) transaction with type AA_TX_TYPE is introduced. Transactions of this type are referred to as
A new [EIP-2718](https://eips.ethereum.org/EIPS/eip-2718) transaction with type AA_TX_TYPE is introduced. Transactions of this type are referred to as

RIPS/rip-7560.md Outdated Show resolved Hide resolved
callData, paymasterData, deployerData,
maxPriorityFeePerGas, maxFeePerGas,
validationGasLimit, paymasterGasLimit, callGasLimit,
accessList, signature

Choose a reason for hiding this comment

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

It would be good to define who is creating the signature and what data it is over, and how the canonical form of the data is constructed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Well, there is no single "correct" or "canonical" way to implement transaction validation with Account Abstraction.
The wallet applications may implement it in any way they want, although it probably makes a lot of sense for them to cover all the fields in the TransactionType4 structure and sign it with some kind of a cryptographic key.


If `paymaster` is not specified, the `maxPossibleGasCost` is charged up-front, before any computation is done in any
execution frame, from the balance of the `sender` address.
If `paymaster` is specified, the gas cost is charged from its balance.

Choose a reason for hiding this comment

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

How do you determine that they paymaster contract is prepared to pay for the gas up front / how do you determine that the paymaster has authorised payment for the gas?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

For a transaction to be "valid" and included in a block, the specified Paymaster must return a correct "magic value" in its validatePaymasterTransaction function. This is the way for the Paymaster contract to indicate accepting the transaction on-chain. And there is probably no single "correct" way to implement the off-chain communication.

RIPS/rip-7560.md Outdated Show resolved Hide resolved

## Motivation

ERC-4337 can do a lot as a purely voluntary ERC. However, any of the out-of-protocol ways of achieving

Choose a reason for hiding this comment

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

I think it would be very helpful to write a paragraph or two, with a diagram as well, that explains that this EIP does, without the assumed knowledge that readers are fully conversant with ERC 4337.


The `nonce` is [validated and incremented](#nonce-validation-frame) on-chain before the rest of the validation code.

The old `nonce` account parameter remains in use for transactions initiated by EOAs and for the `CREATE` opcode.

Choose a reason for hiding this comment

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

In the event of multiple AA_TX_TYPE transactions employing the CREATE opcode, if the old nonce parameter is utilized for the CREATE opcode, it could result in the computation of identical deployment addresses for each transaction. This could potentially open a window for the injection of malicious bytecodes.

One solution could be to use the new nonce with CREATE as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In the event of multiple AA_TX_TYPE transactions employing the CREATE opcode, if the old nonce parameter is utilized for the CREATE opcode, it could result in the computation of identical deployment addresses for each transaction. This could potentially open a window for the injection of malicious bytecodes.

One solution could be to use the new nonce with CREATE as well.

I am not sure I understand the situation. The old nonce of the account will be incremented once it is used for the CREATE opcode, so there should be no way different transactions end up deploying a contract to an identical address. Could you please explain how identical deployment addresses may be achieved?

RIPS/rip-7560.md Outdated Show resolved Hide resolved
RIPS/rip-7560.md Outdated
and all execution frames apply immediately after that.

In theory, the validation frames can also invalidate each other, but we define ways to prevent that by applying
certain rules for the mempool transactions in EIP-9999.

Choose a reason for hiding this comment

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

A bit confused how this one is going to be enforced for generic validation state changes. Do we restrict what areas of state validation logic can affect (e.g. a declaration list of contract's)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, there are restrictions on the validation phase that are enforced by the mempool. This is described in a separate document because these rules define a recommended block builder strategy and are not parts of the "consensus layer".
What these rules say are, basically:

  • Access only your inner storage
  • Do not use "environmental" opcodes (TIMESTAMP, block's NUMBER, DIFFICULTY etc.)
  • Keep track of Paymaster, Factory "reputation" to avoid getting DoS'ed

Please see ERC-7562 ( ethereum/ERCs#105 ) for more details on this.

RIPS/rip-7560.md Outdated
validation and is not linked to the gas price, the `sender` may decide
to pay an extra `builderFee` as a "tip" to the block builder.

This value is denominated in wei and is passed from the `sender` to the `coinbase` as part of the gas pre-charge.

Choose a reason for hiding this comment

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

coinbase? paymaster?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added "or Paymaster". I think "coinbase" is a correct term to use here to define a value transfer to the block builder that included this transaction.

include `AA_TX_TYPE` transactions in their blocks, as well as a potential L1 gas cost for builders
operating on L2 rollups, and given that this work does not correspond to the amount of gas spent on
validation and is not linked to the gas price, the `sender` may decide
to pay an extra `builderFee` as a "tip" to the block builder.

Choose a reason for hiding this comment

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

perhaps naive but how are AA clients expected to calculate or determine a fair builderFee?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

perhaps naive but how are AA clients expected to calculate or determine a fair builderFee?

Calculation of the builderFee is probably something the RPC endpoint is expected to do for the clients. We defined the response of the eth_estimateGasAccountAbstraction method to include the builderFee, however the exact way to calculate this value may be very much rollup-specific.

Copy link
Contributor

Choose a reason for hiding this comment

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

In any cases builderFee will be zero. It is meant to pay for things that cannot be measured on-chain, such as the proving cost of a ZK rollup for a certain transaction. In cases where everything can be measured on-chain, a limit makes more sense than a fixed fee. For example, L1 calldata costs (or 4844 blob-space after Cancun) are known to the network at the time of batching, so it would make sense for the user to specify a limit rather than a fee.

We are considering adding a wei-denominated rollup fee limit in addition to the fixed builderFee that covers costs not measurable by the network. If we do, I think builderFee will be zero on most networks except ZK rollups or some special cases.

@CarlBeek CarlBeek mentioned this pull request Nov 28, 2023
@poojaranjan
Copy link

category: Core

This proposal is in the RIP repo and not the EIP repo. May I suggest to update?

category: RIP

contracts to be used in both systems with minimal to no modifications, while providing significant UX improvements.

Existing contracts are not significantly affected by the change.
The assumption that `tx.origin` is guaranteed to be an EOA is no longer valid.

Choose a reason for hiding this comment

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

Note on tx.origin. Lots of rollups (e.g. Optimism, Arbitrum, Era) depend on tx.origin != msg.sender check to apply an alias to msg.sender when doing L1->L2 communication. This prevents an evil L1 "clone" of an L2 contract from draining all the funds by utilising L1->L2 messaging. This evil clone could have the same address as the original L2 contract, while different storage (or even code if the contract has been deployed by CREATE). The standard extcodesize > 0 check while helpful, is not enough, because the hack might happen in constructor, where extcodesize = 0.

This scenario above is applicable for smart wallets too if the owners between L2 and L1 become unsynchronized (the old owners could redeploy a clone of the account on L1, where they were passed as the initial owners in the constructor).

While it may not be an obstacle for L2 adoption (though it might be one in case an L3 is deployed on top of an L2), but it may be an issue for future adoption on L1.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, the way aliasing is done by rollups on L1 is not fully compatible with AA, and makes an EOA related assumption. It won't be an issue for the RIP since aliasing happens in the L1 contracts, but you are right that it's a future consideration if used as an EIP in the future.

However, I think this aliasing method is also incompatible with any proposal that adds code to accounts, e.g. EIP-5003, EIP-6913, EIP-7377, and probably a couple of others. If any of these EIPs is adopted by an L2, it becomes possible to deploy an L2 contract at an address which is still an EOA on L1, and then use the L1 EOA to inject calls that will originate from that contract on L2. Therefore I think the EOA exception in the L1 aliasing contracts will have to change before supporting account migration, in any L2.

IIRC the purpose of this exception to the aliasing rule, is to support the special case where an EOA on L1 sends assets to itself on L2, since there's no logic in the L2 recipient (since it's an EOA) and therefore no way to unalias. This use case could be supported by a more narrow check without relying on tx.origin and without conflicting with any of the migration EIPs above. For example, always alias on L1, and then the L2 side of the bridge could only unalias if the recipient has no code. It requires L2 recipients (that do have code) to unalias any address they receive, but that's something they have to do anyway in order to support calls from L1 contracts.

In any case, that's something we should discuss in RollCall regardless of this RIP because it conflicts with other EIPS under consideration. Thanks for bringing it up.

Copy link

@StanislavBreadless StanislavBreadless Dec 13, 2023

Choose a reason for hiding this comment

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

BTW, the following construction could be used on L1:

if (tx.origin == msg.sender && extcodesize(msg.sender) == 0) {
  // not alias
} else {
  // alias
}

but that would work only if EOAs won't ever have a constructor.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure any L1 check could solve the problem, since the attack happens on L2. Suppose you have an EOA that gets code on L2 and becomes a high value DeFi contract holding a lot of USDC, but remains an EOA on L1. The key owner will be able to rugpull the L2 DeFi contract without ever deploying code on L1, by just sending a USDC.transfer() to the L1 bridge contract. It would cause the L2 contract to transfer all of its USDC.

Therefore I think the check must be on the L2 side, regardless of whether the address has code on L1.


In terms of block validity, all validation and execution frames may read and write any state when included in the block.
However, the AA transactions in the mempool SHOULD be bound by storage access rules to avoid DoS on block builders.
These rules are defined in [ERC-7562](./eips/eip-7562).

Choose a reason for hiding this comment

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

Seems the link to ERC-7562 is broken

Comment on lines +16 to +17
Combining the [EIP-2938](./eip-2938)
and [ERC-4337](./eip-4337)

Choose a reason for hiding this comment

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

Suggested change
Combining the [EIP-2938](./eip-2938)
and [ERC-4337](./eip-4337)
Combining the [EIP-2938](https://eips.ethereum.org/EIPS/eip-2938)
and [ERC-4337](https://eips.ethereum.org/EIPS/eip-4337)

Comment on lines +565 to +566
* [EIP-1283](./eip-1283) Gas metering for SSTORE
* [EIP-1153](./eip-1153) Transient storage opcodes

Choose a reason for hiding this comment

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

Suggested change
* [EIP-1283](./eip-1283) Gas metering for SSTORE
* [EIP-1153](./eip-1153) Transient storage opcodes
* [EIP-1283](https://eips.ethereum.org/EIPS/eip-1283) Gas metering for SSTORE
* [EIP-1153](https://eips.ethereum.org/EIPS/eip-1153) Transient storage opcodes

Copy link
Contributor

@CarlBeek CarlBeek left a comment

Choose a reason for hiding this comment

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

Given how much review we've already seen on this topic, I'd like to merge this RIP and we can come back and make changes at a later date if we need to.

@CarlBeek CarlBeek merged commit 5f6add3 into ethereum:master Jan 31, 2024
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.