Alert Source Discuss
⚠️ Draft Standards Track: Core

EIP-7701: Native Account Abstraction

Native Account Abstraction protocol, relying on a new transaction type and a family of opcodes

Authors Vitalik Buterin (@vbuterin), Yoav Weiss (@yoavw), Alex Forshtat (@forshtat), Dror Tirosh (@drortirosh), Shahaf Nacson (@shahafn)
Created 2024-05-01
Discussion Link https://ethereum-magicians.org/t/eip-7701-native-account-abstraction/19893

Abstract

We propose splitting the Ethereum transaction scope into multiple steps: validations, execution, and post-operation logic. Transaction validity is determined by the result of the validation steps of a transaction.

We further separate transaction validation for the purposes of authorization and the gas fee payment, allowing one contract to pay gas for a transaction that will be executed from another contract.

Motivation

Native Account Abstraction allows custom validation logic of a transaction and custom gas payment logic, opening new use-cases and features for wallets and dApps.

A more detailed motivation for this proposal can be found in the README document.

Specification

Constants

Name Value
AA_TX_TYPE TBD
AA_ENTRY_POINT address(0x7701)
AA_BASE_GAS_COST 15000
ROLE_SENDER_DEPLOYMENT 0xA0
ROLE_SENDER_VALIDATION 0xA1
ROLE_PAYMASTER_VALIDATION 0xA2
ROLE_SENDER_EXECUTION 0xA3
ROLE_PAYMASTER_POST_OP 0xA4

New Transaction Type

A new EIP-2718 transaction with type AA_TX_TYPE is introduced. Transactions of this type are referred to as “AA transactions”.

Their payload should be interpreted as:

AA_TX_TYPE || rlp([
  chain_id,
  nonce,
  sender, sender_validation_data,
  deployer, deployer_data,
  paymaster, paymaster_data,
  sender_execution_data,
  max_priority_fee_per_gas, max_fee_per_gas,
  sender_validation_gas, paymaster_validation_gas,
  sender_execution_gas, paymaster_post_op_gas,
  access_list,
  authorization_list
])

CURRENT_ROLE and ACCEPT_ROLE opcodes

current_context_role is a context variable set by the AA transaction to the current role. During AA transactions, it is set to the current role in the transaction’s lifecycle for each top level call. During non-AA transactions it is always set to ROLE_SENDER_EXECUTION. It remains unchanged on DELEGATECALL but is reset to ROLE_SENDER_EXECUTION on CALL / STATICCALL / CALLCODE. This behavior resembles msg.sender.

The CURRENT_ROLE opcode returns the current_context_role value.

The ACCEPT_ROLE opcode is equivalent to RETURN in the sense that it copies a memory slice, ends execution, and pastes the memory slice onto parent returndata, with a single modification:

  • It accepts the frame_role as the additional input parameter, and reverts if it differs from the current_context_role

For each role in the transaction’s lifecycle, a successful ACCEPT_ROLE is expected with the frame_role == role. If any validation frame failed to perform an ACCEPT_ROLE matching its role, the transaction fails the validity checks and cannot be included.

TXPARAM* opcodes

The TXPARAMDLOAD, TXPARAMSIZE, TXPARAMCOPY opcodes follow the pattern of CALLDATA* / RETURNDATA* opcode families.

Each TXPARAM* opcode takes an extra stack input value as a first input compared to its CALLDATA* equivalent. The values of this input are as follows:

n Return value Data size Default Comment
0x00 current transaction type 32    
0x01 nonce 32    
0x02 sender 32    
0x03 sender_validation_data dynamic    
0x04 deployer 0 or 32 address(0)  
0x05 deployer_data dynamic empty array  
0x06 paymaster 0 or 32 address(0)  
0x07 paymaster_data dynamic empty array  
0x08 sender_execution_data dynamic    
0x0B max_priority_fee_per_gas 32    
0x0C max_fee_per_gas 32    
0x0D sender_validation_gas 32    
0x0E paymaster_validation_gas 32 0  
0x0F sender_execution_gas 32    
0x10 paymaster_post_op_gas 32 0  
0x11 access_list hash 32    
0x12 authorization_list hash 32    
0xf1 execution_status 32   See transaction scoped vars in processing flow
0xf2 execution_gas_used 32   See transaction scoped vars in processing flow
0xff tx_hash_for_signature 32   Hash of the transaction without the signature

Affected opcodes

In all top-level frames, the global variables have the following meaning:

Opcode Name Solidity Equivalent Value
CALLER msg.sender The AA_ENTRY_POINT address
ORIGIN tx.origin The transaction sender address
CALLDATA* msg.data Empty for all call frames except for the sender execution frame, for which it is set to sender_execution_data

Costs of accessing cold addresses for Sender, Paymaster, and Deployer

The Sender address is pre-warmed as part of the AA_BASE_GAS_COST.

When a non-zero address that is not equal to the Sender address, is provided for a Paymaster or a Deployer contract, an additional EIP-2930 ACCESS_LIST_ADDRESS_COST cost of 2400 gas is charged and the address is added to accessed_addresses.

AA transaction processing flow

We define processing flow for an AA transaction as follows:

def state_transition_function(tx, block, state):
    # Empty refunds, warm list, execution status and gas used (new), etc
    state.transaction_scoped_vars = {}

    max_gas = tx.sender_validation_gas + tx.paymaster_validation_gas + tx.sender_execution_gas + tx.paymaster_post_op_gas
    gas_price = min(tx.max_fee_per_gas, block.base_fee_per_gas + tx.max_priority_fee_per_gas)
    payer = tx.sender if tx.paymaster is None else tx.paymaster
    total_max_cost = max_gas * gas_price
    balances[payer] -= total_max_cost
    gas_used = 0

    if get_code(tx.sender) is None:
        deployer_result = call(tx.deployer, [], tx.sender_validation_gas_limit, ROLE_SENDER_DEPLOYMENT)
        assert deployer_result.accepted_role == ROLE_SENDER_DEPLOYMENT
        gas_used += deployer_result.gas_used

    sender_result = call(tx.sender, [], tx.sender_validation_gas_limit - gas_used, ROLE_SENDER_VALIDATION)
    assert sender_result.accepted_role == ROLE_SENDER_VALIDATION
    gas_used += sender_result.gas_used

    if tx.paymaster:
        paymaster_result = call(tx.paymaster, [], tx.paymaster_validation_gas, ROLE_PAYMASTER_VALIDATION)
        assert paymaster_result.accepted_role == ROLE_PAYMASTER_VALIDATION
        gas_used += paymaster_result.gas_used

    checkpoint = state.take_snapshot()
    sender_execution_result = call(tx.sender, [], tx.sender_execution_gas, ROLE_SENDER_EXECUTION)
    gas_used += sender_execution_result.gas_used
    state.transaction_scoped_vars[execution_status] = sender_execution_result.output_code
    state.transaction_scoped_vars[execution_gas_used] = gas_used

    if tx.paymaster:
        postop_result = call(tx.paymaster, [], tx.paymaster_post_op_gas, ROLE_PAYMASTER_POST_OP)
        gas_used += postop_result.gas_used
        if postop_result.accepted_role != ROLE_PAYMASTER_POST_OP:
            state.revert_snapshot(checkpoint)

    balances[payer] += gas_price * (max_gas - gas_used)

Rationale

A full list of rationales for the decisions made in this proposal can be found in the README document.

Backwards Compatibility

Security Considerations

As the ACCEPT_ROLE opcode represent a generic way to authorize any action on behalf of the contract, correct and secure implementation of this code is critical. We expect that compilers targeting EVM will play a major role in enabling and ensuring Smart Contract Accounts’ security.

For smart contract security auditors and security-oriented developer tools it is crucial to ensure that contracts not meant to have roles in AA transactions do not have unexpected ACCEPT_ROLE opcode. Otherwise, these contracts may present an immediate security threat.

As an example, block explorers should tag contracts as “user accounts” or “paymasters” if they have the ACCEPT_ROLE opcode used in their source code.

Copyright and related rights waived via CC0.

Citation

Please cite this document as:

Vitalik Buterin (@vbuterin), Yoav Weiss (@yoavw), Alex Forshtat (@forshtat), Dror Tirosh (@drortirosh), Shahaf Nacson (@shahafn), "EIP-7701: Native Account Abstraction [DRAFT]," Ethereum Improvement Proposals, no. 7701, May 2024. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7701.