EIP-7702最佳实践
EIP-7702对以太坊的外部账户(EOA)带来了变革性变化。该提案模糊了EOA与合约账户(CA)之间的界限,并作为迈向原生账户抽象化的重要一步。

一键发币: SUI | SOL | BNB | ETH | BASE | ARB | OP | POLYGON | AVAX | FTM | OK
以太坊即将进行Pectra升级,这无疑是一次重大更新。通过这次机会,将引入众多重要的以太坊改进提案(EIP)。其中,EIP-7702对以太坊的外部账户(EOA)带来了变革性变化。该提案模糊了EOA与合约账户(CA)之间的界限,并作为迈向原生账户抽象化的重要一步,紧随EIP-4337之后。它为以太坊生态系统引入了一种新的交互模型。
目前,Pectra已经在测试网络中部署,并预计很快将在主网上线。本文将深入分析EIP-7702的实现机制,探讨其可能带来的机遇和挑战,并为不同参与者提供实际的操作指南。
1、协议分析
1.1 概述
EIP-7702引入了一种新的交易类型,允许EOA指定一个智能合约地址并设置其代码。这使得EOA能够像智能合约一样执行代码,同时仍保留发起交易的能力。这一功能赋予EOA编程性和可组合性,使用户能够在EOA内实现诸如社交恢复、权限控制、多重签名管理、零知识验证、订阅支付、交易赞助和批量处理等功能。
值得注意的是,EIP-7702完全兼容通过EIP-4337实现的智能合约钱包。这两个提案的无缝集成大大简化了新功能的开发和应用。
EIP-7702的具体实现涉及引入一种新的交易类型,即SET_CODE_TX_TYPE (0x04),其数据结构如下:
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])
authorization_list字段定义为:
authorization_list = [[chain_id, address, nonce, y_parity, r, s], ...]
在新的交易结构中,除了authorization_list字段外,其余字段的语义与EIP-4844相同。authorization_list字段是列表类型,可以包含多个授权条目。每个授权条目包括以下内容:
- chain_id:指定授权有效的链。
- address:指定委托的目标地址。
- nonce:必须匹配被授权账户的当前nonce值。
- y_parity, r, s:被授权账户签署授权数据的签名数据。
交易中的authorization_list字段可以包含由多个不同EOA签名的授权条目,这意味着交易发起者可以与授权者不同。这允许为授权操作提供gas赞助。
1.2 实现
当授权者签署授权数据时,他们首先需要使用RLP编码chain_id, address, nonce字段。然后,使用keccak256对编码后的数据以及MAGIC数字进行哈希运算,生成待签名的数据[1]。最后,授权者的私钥对哈希数据进行签名,生成y_parity, r, s值。
// go-ethereum/core/types/tx_setcode.go#L109-L113
func (a *SetCodeAuthorization) sigHash() common.Hash {
return prefixedRlpHash(0x05, []any{
a.ChainID,
a.Address,
a.Nonce,
})
}
**MAGIC (0x05)**数字用作域分隔符,以确保不同类型签名不会冲突。
如果授权条目的chain_id为0,则表示授权者允许[2]授权在所有支持EIP-7702的EVM兼容链上重放,前提是nonce也匹配。
// go-ethereum/core/state_transition.go#L562
if !auth.ChainID.IsZero() && auth.ChainID.CmpBig(st.evm.ChainConfig().ChainID) != 0 {
return authority, ErrAuthorizationWrongChainID
}
一旦授权数据被签署,交易发起者将其聚合到authorization_list字段中,签署交易并通过RPC广播。在交易被包含在区块之前,提议者会进行预检查[3],确保to地址有效。此检查防止了合约创建交易,这意味着在EIP-7702交易中,to地址不能为空[4]。
// go-ethereum/core/state_transition.go#L388-L390
if msg.To == nil {
return fmt.Errorf("%w (sender %v)", ErrSetCodeTxCreate, msg.From)
}
随后,在交易执行期间,节点首先增加交易发起者的nonce值,然后对authorization_list中的每个授权条目执行applyAuthorization
操作。在applyAuthorization
操作中,节点首先检查授权的nonce值,然后增加授权的nonce值。这意味着如果交易发起者和授权者是同一用户(EOA),在签署授权交易时,nonce值应增加1。
// go-ethereum/core/state_transition.go#L489-L497
func (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#L604
func (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#L566
func (st *stateTransition) validateAuthorization(auth *types.SetCodeAuthorization) (authority common.Address, err error) {
...
if auth.Nonce+1 < auth.Nonce {
return authority, ErrAuthorizationNonceOverflow
}
...
}
当节点应用授权条目时,如果遇到任何错误,该条目将被跳过,交易不会失败。其他授权条目将继续应用,确保在批量授权场景下不会出现DoS风险。
// go-ethereum/core/state_transition.go#L494
for _, auth := range msg.SetCodeAuthorizations {
// Note errors are ignored, we simply skip invalid authorizations here.
st.applyAuthorization(&auth)
}
一旦授权条目应用完毕,授权者地址的代码字段将被设置为0xef0100 || address
,其中0xef0100
是一个固定标识符,address
是委托的目标地址。由于EIP-3541的限制,用户无法通过常规方式部署以0xef
开头的合约,从而确保此类标识符只能通过SET_CODE_TX_TYPE (0x04)
类型的交易部署。
// go-ethereum/core/state_transition.go#L612
st.state.SetCode(authority, types.AddressToDelegation(auth.Address))
// go-ethereum/core/types/tx_setcode.go#L45
var DelegationPrefix = []byte{0xef, 0x01, 0x00}
func AddressToDelegation(addr common.Address) []byte {
return append(DelegationPrefix, addr.Bytes()...)
}
授权后,如果授权者想要撤销授权,只需将委托的目标地址设置为0
地址即可。
通过EIP-7702引入的新交易类型,授权者(EOA)可以在保留发起交易能力的同时像智能合约一样执行代码。与EIP-4337相比,这为用户提供了一个更接近原生账户抽象(Native AA)的体验,大大降低了用户的入门门槛。
2、最佳实践
尽管EIP-7702为以太坊生态系统注入了新的活力,但新应用场景的引入也带来了新的风险。以下是生态系统参与者在实践中应谨慎考虑的一些方面:
2.1 私钥存储
即使EOA(外部拥有的账户)可以利用智能合约内置的社会恢复机制来解决因私钥丢失导致的资产损失问题,但仍无法避免私钥泄露的风险。重要的是要注意,授权后,EOA的私钥仍然对账户拥有最高控制权;持有私钥使用户能够自由处置账户内的资产。用户或钱包服务提供商在完成EOA的授权后,无法完全消除私钥泄露的风险,特别是在可能存在供应链攻击的情况下。
对于用户来说,保护私钥始终是首要任务。关键是要记住:不是你的密钥,就不是你的币。
2.2 多链重放
当用户签署授权时,可以选择授权生效的链ID。用户还可以选择使用链ID为0,这允许授权在多个链上重放。然而,不同链上的相同合约地址可能有不同的实现。
钱包服务提供商应检查授权链是否与当前网络匹配,并警告用户关于使用链ID为0的授权可能在不同链上重放的风险。
用户还应意识到,不同链上的相同合约地址可能并不总是具有相同的合约代码。在进行授权之前,了解被委托目标的详细信息非常重要。
2.3 初始化问题
大多数主流智能合约钱包使用代理模式,其中钱包代理通过DELEGATECALL
调用初始化函数来实现钱包的原子初始化和部署。然而,通过EIP-7702,地址的code
字段只会被更新,无法通过委托地址调用初始化函数。这限制了EIP-7702与ERC-1967代理合约提供的相同钱包初始化能力,后者可以在部署时调用初始化函数。
开发者应在钱包初始化过程中确保权限检查(例如,通过ecrecover
验证签名地址)以避免初始化漏洞。
2.4 存储管理
在使用EIP-7702委托时,用户可能需要重新委托到不同的合约地址,因为功能变更或钱包升级。不同的合约可能具有不同的存储结构(例如,slot0
在不同合约中可能代表不同类型的数据),重新委托可能导致旧合约数据被重复使用,从而引发账户锁定、资产损失等问题。
用户应谨慎处理重新委托的情况。
开发者应遵循ERC-7201提出的命名空间公式,将变量分配到指定的独立存储位置,以缓解存储冲突。此外,ERC-7779(草案)提供了针对EIP-7702特定的重新委托标准流程,包括在重新委托前防止存储冲突和验证兼容性。
2.5 虚假充值
授权后,EOA也可以充当智能合约,这可能导致集中式交易所(CEX)面临广泛的智能合约充值风险。
CEX应使用跟踪检查监控每笔存款交易,以减轻来自智能合约的虚假充值风险。
2.6 账户转换
通过EIP-7702授权,用户的账户可以自由在EOA和智能合约之间转换,使其既能发起交易也能被调用。这意味着当账户调用自身或进行外部调用时,其msg.sender
也将是tx.origin
,这打破了仅EOA参与的安全假设。
合约开发者不应假设tx.origin
始终是EOA。同样,使用msg.sender == tx.origin
作为防止重入攻击的防御措施将不再有效。
开发者在开发过程中应假设未来参与者可能是智能合约。
2.7 合约兼容性
现有的ERC-721和ERC-777代币在转移代币时具有钩子函数,这意味着接收方必须实现相应的回调函数才能成功接收代币。
开发者应确保用户委托的目标合约实现了必要的回调函数,以确保与主流代币的兼容性。
2.8 网络钓鱼检查
实施EIP-7702授权后,用户账户中的资产可能由智能合约控制。如果用户将账户委托给恶意合约,攻击者可以轻松窃取资产。
钱包服务提供商应快速支持EIP-7702交易,并在用户签署授权时突出显示目标合约,以减少网络钓鱼攻击的风险。
此外,对委托目标合约进行深入的自动化分析(开源检查、权限检查等)可以帮助用户避免此类风险。
3、结束语
本文探讨了即将到来的以太坊Pectra升级中的EIP-7702。EIP-7702引入了新的交易类型,赋予EOA编程性和可组合性,模糊了EOA与合约账户之间的界限。然而,由于目前没有广泛测试的标准与EIP-7702兼容的合约,各种生态系统参与者,如用户、钱包提供商、开发者和CEX,面临着许多挑战和机遇。这里讨论的最佳实践不能涵盖所有潜在风险,但对实际应用仍然具有价值。
原文链接:In-Depth Discussion on EIP-7702 and Best Practices
DefiPlot翻译整理,转载请标明出处
免责声明:本站资源仅用于学习目的,也不应被视为投资建议,读者在采取任何行动之前应自行研究并对自己的决定承担全部责任。