Author: Kong
Editor: Sherry
Preface
Ethereum is about to usher in the Pectra upgrade, which is undoubtedly a significant update. Many important Ethereum improvement proposals will be introduced with this opportunity. Among them, EIP-7702 has made a revolutionary transformation to the Ethereum external account (EOA). This proposal blurs the boundary between EOA and contract account CA. It is a key step towards native account abstraction after EIP-4337, bringing a new interaction mode to the Ethereum ecosystem.
Currently, Pectra has been deployed on the test network and is expected to be launched on the main network soon. This article will deeply analyze the implementation mechanism of EIP-7702, explore the opportunities and challenges it may bring, and provide practical operation guidelines for different participants.
Protocol Analysis
Overview
EIP-7702 introduces a new transaction type that allows an EOA to specify a smart contract address and set code for it. This allows an EOA to execute code like a smart contract while retaining the ability to initiate transactions. This feature gives EOA programmability and composability, allowing users to implement features such as social recovery, permission control, multi-signature management, zk verification, subscription-based payments, transaction sponsorship, and transaction batching in EOA. It is worth mentioning that EIP-7702 is perfectly compatible with the smart contract wallet implemented by EIP-4337, and the seamless integration of the two greatly simplifies the development and application process of new features.
The specific implementation of EIP-7702 is to introduce a transaction type of SET_CODE_TX_TYPE (0x04), whose data structure is defined as follows:
rlp([chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, destination, value, data, access_list, authorization_list, signature_y_parity, signature_r, signature_s])
The authorization_list field is defined as:
authorization_list = [[chain_id, address, nonce, y_parity, r, s], ...]
In the new transaction structure, except for the authorization_list field, the rest follow the same semantics as EIP-4844. This field is a list type, which can contain multiple authorization entries. In each authorization entry:
- The chain_id field indicates the chain on which this authorization delegation takes effect.
- The address field indicates the target address of the delegation.
- The nonce field must match the nonce of the current authorized account
- The y_parity, r, s fields are the signature data of the authorized account signing the authorization
The authorization_list field in a transaction can contain authorization entries signed by multiple different authorized accounts (EOAs), that is, the transaction initiator can be different from the authorizer, so as to realize gas payment for the authorized operations of the authorizer.
accomplish
When signing the authorization data, the authorizer needs to first perform RLP encoding on the chain_id, address, and nonce. The encoded data is then hashed with the MAGIC number using keccak256 to obtain the data to be signed [1]. Finally, the hashed data is signed using the authorizer's private key to obtain the y_parity, r, and s data. Among them, MAGIC (0x05) is used as a domain separator to ensure that the results of different types of signatures do not conflict.
// Go-ethereum/core/types/tx_setcode.go#L109-L113func (a *SetCodeAuthorization) sigHash() common.Hash { return prefixedRlpHash(0x05, []any{ a.ChainID, a.Address, a.Nonce, })}
It should be noted that when the chain_id authorized by the authorizer is 0, it means that the authorizer allows [2] the authorization to be replayed on all EVM-compatible chains that support EIP-7702 (provided that the nonce also matches).
// Go-ethereum/core/state_transition.go#L562if !auth.ChainID.IsZero() && auth.ChainID.CmpBig(st.evm.ChainConfig().ChainID) != 0 { return authority, ErrAuthorizationWrongChainID}
After the authorizer signs the authorization data, the transaction initiator will gather it in the authorization_list field for signature and broadcast the transaction via RPC. Before the transaction is included in the block for execution, the Proposer will first pre-check the transaction[3], in which the to address is mandatory to ensure that this transaction is not a contract creation transaction. That is, when sending an EIP-7702 type transaction, the to address of the transaction cannot be empty[4].
// Go-ethereum/core/state_transition.go#L388-L390if msg.To == nil { return fmt.Errorf("%w (sender %v)", ErrSetCodeTxCreate, msg.From)}
At the same time, such transactions will force the authorization_list field in the transaction to contain at least one authorization entry. If multiple authorization entries are signed by the same authorizer, only the last authorization entry will take effect.
// Go-ethereum/core/state_transition.go#L391-L393if len(msg.SetCodeAuthorizations) == 0 { return fmt.Errorf("%w (sender %v)", ErrEmptyAuthList, msg.From)}
Later, during the transaction execution, the node will first increase the nonce value of the transaction initiator, and then applyAuthorization for each authorization entry in the authorization_list. In the applyAuthorization operation, the node will first check the nonce of the authorizer, and then increase the nonce of the authorizer. This means that if the transaction initiator and the authorizer are the same user (EOA), the nonce value should be increased by 1 when signing the authorization transaction.
// Go-ethereum/core/state_transition.go#L489-L497func (st *stateTransition) execute() (*ExecutionResult, error) { ... st.state.SetNonce(msg.From, st.state.GetNonce(msg.From)+1, tracing.NonceChangeEoACall)
// Apply EIP-7702 authorizations. if msg.SetCodeAuthorizations != nil { for _, auth := range msg.SetCodeAuthorizations { // Note errors are ignored, we simply skip invalid authorizations here. st.applyAuthorization(&auth) } } ...}// Go-ethereum/core/state_transition.go#L604func (st *stateTransition) applyAuthorization(auth *types.SetCodeAuthorization) error { authority, err := st.validateAuthorization(auth) ... st.state.SetNonce(authority, auth.Nonce+1, tracing.NonceChangeAuthorization) ...}// Go-ethereum/core/state_transition.go#L566func (st *stateTransition) validateAuthorization(auth *types.SetCodeAuthorization) (authority common.Address, err error) { ... if auth.Nonce+1 < auth.Nonce { return authority, ErrAuthorizationNonceOverflow } ...}
When a node applies an authorization entry, if any error is encountered, this authorization entry will be skipped and the transaction will not fail. Other authorization entries will continue to be applied, ensuring that there is no DoS risk in batch authorization scenarios.
// Go-ethereum/core/state_transition.go#L494for _, auth := range msg.SetCodeAuthorizations { // Note errors are ignored, we simply skip invalid authorizations here. st.applyAuthorization(&auth)}
After the authorization application is completed, the code field of the authorizer address will be set to 0xef0100 || address, where 0xef0100 is a fixed identifier and address is the target address of the delegation. Due to the limitations of EIP-3541, users cannot deploy contract codes starting with 0xef bytes in a conventional way, which ensures that such identifiers can only be deployed by transactions of type SET_CODE_TX_TYPE (0x04).
// Go-ethereum/core/state_transition.go#L612st.state.SetCode(authority, types.AddressToDelegation(auth.Address))
// Go-ethereum/core/types/tx_setcode.go#L45var DelegationPrefix = []byte{0xef, 0x01, 0x00}
func AddressToDelegation(addr common.Address) []byte { return append(DelegationPrefix, addr.Bytes()...)}
After the authorization is completed, if the authorizer wants to remove the authorization, he only needs to set the target address of the delegation to the 0 address.
The new transaction type introduced by EIP-7702 allows the authorizer (EOA) to execute code like a smart contract while retaining the ability to initiate transactions. Compared with EIP-4337, this brings users an experience closer to native account abstraction (Native AA), greatly reducing the user's usage threshold.
Best Practices
Although EIP-7702 has injected new vitality into the Ethereum ecosystem, new application scenarios will also bring new risks. The following are aspects that ecosystem participants need to be vigilant about during practice:
Private key storage
Even if EOA can solve the problem of financial loss caused by the loss of private keys by means of social recovery built into smart contracts after entrustment, it still cannot avoid the risk of EOA private keys being leaked. It should be clear that after the execution of the entrustment, the EOA private key still has the highest control over the account, and the holder of the private key can dispose of the assets in the account at will. After the user or wallet service provider completes the entrustment for the EOA, even if the private key stored locally is completely deleted, the risk of private key leakage cannot be completely eliminated, especially in scenarios where there is a risk of supply chain attacks.
For users, when using a delegated account, they should still put private key protection first and always remember: Not your keys, not your coins.
Multi-chain replay
When signing a delegation authorization, the user can select the chain where the delegation can take effect through chainId. Of course, the user can also choose to use chainId 0 for delegation, which allows the delegation to be replayed and effective on multiple chains, making it convenient for users to delegate on multiple chains with one signature. However, it should be noted that different implementation codes may exist in the same contract address delegated on multiple chains.
For wallet service providers, when users delegate, they should check whether the delegation effective chain is consistent with the currently connected network, and remind users of the risks that may arise from signing a delegation with chainId 0.
Users should also note that the contract codes of the same contract addresses on different chains are not always the same, and they should first understand the target of the delegation.
Unable to initialize
Most of the current mainstream smart contract wallets use a proxy model. When the wallet proxy is deployed, it will call the contract initialization function through DELEGATECALL to achieve atomic operations of wallet initialization and proxy wallet deployment, avoiding the problem of preemptive initialization. However, when users use EIP-7702 for delegation, only the code field of its address will be updated, and it cannot be initialized by calling the delegate address. This makes it impossible for EIP-7702 to call the initialization function in the contract deployment transaction to initialize the wallet like the common ERC-1967 proxy contract.
For developers, when combining EIP-7702 with existing EIP-4337 wallets, they should pay attention to performing permission checks during wallet initialization operations (such as performing permission checks by restoring the signature address through ecrecover) to avoid the risk of wallet initialization operations being preempted.
Storage Management
When using the EIP-7702 delegation function, users may need to re-delegate to a different contract address due to changes in functional requirements, wallet upgrades, etc. However, the storage structures of different contracts may be different (for example, the slot0 slots of different contracts may represent different types of data). In the case of re-delegate, the new contract may accidentally reuse the data of the old contract, which may lead to adverse consequences such as account lockout and fund loss.
For users, re-delegation should be handled with caution.
For developers, the Namespace Formula proposed by ERC-7201 should be followed during the development process to assign variables to designated independent storage locations to mitigate the risk of storage conflicts. In addition, ERC-7779 (draft) also provides a standard re-delegation process for EIP-7702: including using ERC-7201 to prevent storage conflicts, verifying storage compatibility before re-delegation, and calling the old delegate interface to clean up the old stored data.
Fake top-up
After the user makes a commission, EOA will also be able to be used as a smart contract, so centralized exchanges (CEX) may face the situation of universal smart contract recharge.
CEX should check the status of each recharge transaction through trace to prevent the risk of false recharge in smart contracts.
Account conversion
After the implementation of EIP-7702 delegation, the user's account type can be freely converted between EOA and SC, which allows the account to both initiate transactions and be called. This means that when the account calls itself and makes external calls, its msg.sender will also be tx.origin, which will break some security assumptions that only EOA participation projects are allowed.
It is no longer feasible for contract developers to assume that tx.origin is always EOA. Similarly, protection against reentrancy attacks via msg.sender == tx.origin checks will no longer work.
Developers should assume that future participants may all be smart contracts during the development process.
Contract compatibility
Existing ERC-721 and ERC-777 tokens have a Hook function when transferring to a contract, which means that the recipient must implement the corresponding callback function to successfully receive the token.
For developers, the target contract delegated by the user should implement the corresponding callback function to ensure compatibility with mainstream tokens.
Fishing Check
After the implementation of EIP-7702 delegation, the assets in the user's account may be controlled by the smart contract. Once the user delegates the account to a malicious contract, it will become easy for the attacker to steal the funds.
It is particularly important for wallet service providers to support EIP-7702 type transactions as soon as possible, and when users sign delegated contracts, the target contract of the delegation should be highlighted to users to mitigate the risk of phishing attacks.
In addition, more in-depth automatic analysis of the target contract of account delegation (open source check, permission check, etc.) can better help users avoid such risks.
Summarize
This article discusses the EIP-7702 proposal in Ethereum's upcoming Pectra upgrade. EIP-7702 introduces new transaction types to make EOA programmable and composable, blurring the boundaries between EOA and contract accounts. Since there is currently no battle-tested smart contract standard compatible with the EIP-7702 type, in actual applications, different ecosystem participants, such as users, wallet service providers, developers, CEX, etc., face many challenges and opportunities. The best practices described in this article cannot cover all potential risks, but it is still worth learning from and applying by all parties in actual operations.
Example
[Set EOA Account Code]
https://holesky.etherscan.io/tx/0x29252bf527155a29fc0df3a2eb7f5259564f5ee7a15792ba4e2ca59318080182
[Unset EOA Account Code]
https://holesky.etherscan.io/tx/0xd410d2d2a2ad19dc82a19435faa9c19279fa5b96985988daad5d40d1a8ee2269
Related links
[1] https://github.com/ethereum/go-ethereum/blob/7fed9584b5426be5db6d7b0198acdec6515d9c81/core/types/tx_setcode.go#L109-L113
[2] https://github.com/ethereum/go-ethereum/blob/7fed9584b5426be5db6d7b0198acdec6515d9c81/core/state_transition.go#L562
[3] https://github.com/ethereum/go-ethereum/blob/7fed9584b5426be5db6d7b0198acdec6515d9c81/core/state_transition.go#L304
[4] https://github.com/ethereum/go-ethereum/blob/7fed9584b5426be5db6d7b0198acdec6515d9c81/core/state_transition.go#L388-L390
References
[EIP-7702]https://eips.ethereum.org/EIPS/eip-7702
[EIP-4844]https://eips.ethereum.org/EIPS/eip-4844
[Go-ethereum]https://github.com/ethereum/go-ethereum/tree/7fed9584b5426be5db6d7b0198acdec6515d9c81
[EIP-3541]https://eips.ethereum.org/EIPS/eip-3541#backwards-compatibility
[Cobo: Practical Guide to EIP-7702]
https://mp.weixin.qq.com/s/ojh9uLw-sJNArQe-U73lHQ
[Viem]https://viem.sh/experimental/eip7702/signAuthorization
[ERC-7210]https://eips.ethereum.org/EIPS/eip-7201
[ERC-7779]https://eips.ethereum.org/EIPS/eip-7779