Alert Source Discuss
⚠️ Draft Standards Track: Core

EIP-7701: 原生账户抽象

原生账户抽象协议,依赖于一种新的交易类型和一系列操作码

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

摘要

我们建议将以太坊交易范围拆分为多个步骤:验证、执行和后操作逻辑。交易的有效性由交易验证步骤的结果决定。

我们进一步分离交易验证,用于授权和 gas 费用支付,允许一个合约为另一个合约执行的交易支付 gas 费用。

动机

原生账户抽象允许自定义交易的验证逻辑和自定义 gas 支付逻辑,为钱包和 dApp 开辟了新的用例和功能。

关于此提案的更详细的动机可以在 README document 中找到。

规范

常量

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

新的交易类型

引入一种新的 EIP-2718 交易类型,类型为 AA_TX_TYPE。 这种类型的交易被称为“AA 交易”。

它们的 payload 应该被解释为:

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_ROLEACCEPT_ROLE 操作码

current_context_role 是一个由 AA 交易设置的上下文变量,表示当前角色。 在 AA 交易期间,对于每个顶级调用,它被设置为交易生命周期中的当前角色。 在非 AA 交易期间,它始终设置为 ROLE_SENDER_EXECUTION。 它在 DELEGATECALL 上保持不变,但在 CALL / STATICCALL / CALLCODE 上重置为 ROLE_SENDER_EXECUTION。此行为类似于 msg.sender

CURRENT_ROLE 操作码返回 current_context_role 值。

ACCEPT_ROLE 操作码等同于 RETURN,因为它复制一个内存切片,结束执行,并将内存切片粘贴到父 returndata,只有一个修改:

  • 它接受 frame_role 作为附加输入参数,如果它与 current_context_role 不同,则恢复。

对于交易生命周期中的每个 role,都需要成功执行 ACCEPT_ROLE,其中 frame_role == role。 如果任何验证 frame 未能执行与其角色匹配的 ACCEPT_ROLE,则交易将无法通过有效性检查,并且无法被包含。

TXPARAM* 操作码

TXPARAMDLOADTXPARAMSIZETXPARAMCOPY 操作码遵循 CALLDATA* / RETURNDATA* 操作码族的模式。

CALLDATA* 等效项相比,每个 TXPARAM* 操作码都需要一个额外的堆栈输入值作为第一个输入。 此输入的值如下:

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

受影响的操作码

在所有顶层 frame 中,全局变量具有以下含义:

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

访问 Sender、Paymaster 和 Deployer 的冷地址的成本

Sender 地址已预先预热,作为 AA_BASE_GAS_COST 的一部分。

当为 PaymasterDeployer 合约提供一个不等于 Sender 地址的非零地址时, 将收取额外的 EIP-2930 ACCESS_LIST_ADDRESS_COST 成本,即 2400 gas,并将该地址添加到 accessed_addresses

AA 交易处理流程

我们将 AA 交易的处理流程定义如下:

def state_transition_function(tx, block, state):
    # Empty refunds, warm list, execution status and gas used (new), etc
    # 清空退款、预热列表、执行状态和 gas 使用量(新),等等
    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)

理由

关于此提案中做出的决定的完整理由列表可以在 README document 中找到。

向后兼容性

安全考虑

由于 ACCEPT_ROLE 操作码表示一种通用方式来授权代表合约执行任何操作, 因此正确和安全地实现此代码至关重要。 我们期望以 EVM 为目标的编译器在启用和确保智能合约账户的安全性方面发挥重要作用。

对于智能合约安全审计员和面向安全性的开发人员工具来说,至关重要的是要确保不打算在 AA 交易中扮演角色的合约没有意外的 ACCEPT_ROLE 操作码。 否则,这些合约可能会构成直接的安全威胁。

例如,如果 block explorer 在其源代码中使用了 ACCEPT_ROLE 操作码,则应将合约标记为“用户帐户”或“paymaster”。

版权

版权及相关权利通过 CC0 放弃。

Citation

Please cite this document as:

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