全部文章

理解并实现 EIP-7702

·
2025年9月27日
·
阅读约 6 分钟
·
••• 次浏览
·
••• 次点赞

本文译自英文原文。 阅读英文原文

理解并实现 EIP-7702

假设你要执行一笔复杂的一体化原子交易,需要兑换代币、添加流动性、质押奖励。可问题是,你的普通 Ethereum 钱包 EOA 做不了批量交易。所以,你得签三笔独立的交易,付三次 gas,这可万万不行,然后还得祈祷 MEV 机器人别在步骤之间把你夹成三明治。

这是最近 EVM 生态里讨论最激烈的话题之一,我们到底要怎样才能赋予普通钱包超能力,同时又不把所有东西一下子全搞砸?

这个挑战催生了两个 EIPs,先是 EIP-3074,随后是 EIP-7702。两者都想弥合简单的 EOA 与 smart contract accounts 之间的差距,但它们在委托权限上采取了不同的方式。一个提供持久的权力,事情可能会变得非常糟糕,你的钱包会被掏空。另一个既能给你持久委托,也能给你临时委托,后者可以在你的交易上链执行的那一刻就消失。

我们来聊聊为什么这个区别很重要,以及 Vitalik 的提案是如何可能让 Ethereum 躲过一场潜在的安全噩梦的。

EIP-2718 - 类型化交易信封

在进入委托这个话题之前,有一块重要的基础设施让这一切成为可能,那就是 EIP-2718。可以把它看作 Ethereum 为交易打造的集装箱系统。

// before eip-2718 all transactions looked the same
const legacyTx = rlp([nonce, gasPrice, gasLimit, to, value, data, v, r, s]);
 
// after eip-2718 transactions can now be typed
const typedTx = TransactionType || TransactionPayload;

EIP-2718 引入了 Typed Transaction Envelopes,其中 TransactionType || TransactionPayload 就是一笔有效的交易。TransactionType 标识交易的格式,而 Payload 包含交易的内容。这套包装系统意味着,我们可以在不破坏现有功能的情况下引入全新的交易行为。

引入信封交易类型之后,我们只需要保证与现有交易的向后兼容,从那以后,只需解决一个简单得多的问题:确保各个 TransactionTypes 之间没有编号冲突。所以当 EIP-7702 需要引入它的临时委托机制时,它只需定义一个新的交易类型,例如 0x04,而不需要对 EVM 本身做任何改动。

EIP-3074 - 持久委托方案

EIP-3074 在委托上采取了大胆的做法,通过两个新的 EVM opcodes,也就是 AUTH 和 AUTHCALL,让 EOAs 能够把权限永久委托给智能合约。

AUTH 和 AUTHCALL 是如何工作的

机制很直接

// user signs an auth message
bytes32 commit = keccak256(abi.encode(invokerAddress, batchActions));
bytes memory signature = signMessage(userPrivateKey, commit);
 
// invoker contract calls AUTH opcode
AUTH(commit, signature.v, signature.r, signature.s);
// This sets the 'authorized' context variable to user's address
 
// now invoker can make calls as the user
AUTHCALL(gasLimit, targetContract, 0, 0, calldata, calldataLength, 0, 0);

AUTH 接收四个参数,commit 以及签名的 yParity、r 和 s,如果签名有效,并且签名者地址等于 authority,上下文变量 authorized 就会被设置为该 authority。随后,AUTHCALL 允许被授权的 invoker 合约发送交易,此时 CALLER 被设置为被授权的地址,而不是 invoker。

现在,想象一个能处理批量交易的 invoker 合约

contract BatchInvoker {
    struct BatchCall {
        address target;
        bytes data;
        uint256 value;
    }
    
    function executeBatch(
        BatchCall[] calldata calls,
        bytes32 commit,
        uint8 v, bytes32 r, bytes32 s
    ) external {
        // authorize the user
        AUTH(commit, v, r, s);
        
        // Execute all calls as the user
        for (uint i = 0; i < calls.length; i++) {
            AUTHCALL(
                gasleft(),
                calls[i].target,
                calls[i].value,
                0, // valueExt (must be 0 for now)
                // ... calldata parameters
            );
        }
    }
}

尽管这很令人兴奋,但这两个 opcodes 在 EVM 社区引发了一系列安全担忧。上下文变量 authorized 在合约的单个执行帧内一直存在,但不会传递给任何调用,包括 DELEGATECALL。这基本上意味着授权在整个交易范围内都是有效的。

真正的问题不在于它在单笔交易内的临时性,而在于当 invoker 合约被广泛使用时会发生什么。如果最终每个人都会用上智能账户,那么 AUTH 和 AUTHCALL 这两个 opcodes 可能会成为巨大的技术负担,并增加 EVM 本身的复杂度。

如果像上面那样一个热门的 invoker 合约出了 bug,会怎么样?突然之间,每一个曾经授权过这个合约的用户都可能面临风险。一次恶意更新,一个被遗漏的边界情况,一次整数溢出,就这样,大规模的资产掏空开始了。

EVM 生态意识到,这造成了一种奇怪的局面。EIP-3074 的安全模型严重依赖 invoker 合约。由于多个用户可能依赖同一套 invoker 逻辑,一个 invoker 中的漏洞或攻击可能一下子危及许多用户的资产或权限,这就是 EIP-3074 被撤回的原因。

EIP-7702 - 灵活的委托方案

现在来看 EIP-7702,它由 Vitalik 在 2024 年 5 月提出,于 2025 年 5 月通过 pectra upgrade 上了 mainnet,它引入了一种新的精简方案,让 EOAs 能在单笔交易期间临时像智能合约一样运作,这可是件大事!

EIP-7702 没有引入新的 opcodes,而是利用 EIP-2718's 交易信封系统引入了一种新的交易类型

interface EIP7702Transaction {
    chainId: number;
    nonce: number;
    maxPriorityFeePerGas: bigint;
    maxFeePerGas: bigint;
    gasLimit: bigint;
    to: string;
    value: bigint;
    data: Uint8Array;
    accessList: AccessList;
    authorizationList: Authorization[];
}
 
interface Authorization {
    chainId: number;
    address: string;  // the address of the contract code to delegate to
    nonce: number;
    yParity: 0 | 1;
    r: Uint8Array;
    s: Uint8Array;
}

对于每个授权对象,都会有一个委托标识 (0xef0100 || address) 被写入授权账户的代码中。所有执行代码的操作都必须加载并执行该委托所指向的代码。

委托的持久性究竟是如何运作的

这正是很多人搞不清楚的地方。EIP-7702 自最初的草案以来一直在演进。起初,这个提案的设计是让 EOAs 只在一笔交易内临时变得“智能”。但最终版本让你可以控制委托持续多久。

function processEIP7702Transaction(tx) {
    // verify all auth signatures
    for (const auth of tx.authorizationList) {
        const signer = recoverSigner(auth);
        if (signer !== getAddressFromAuth(auth)) {
            throw new Error("Invalid authorization");
        }
    }
    
    // set delegation pointers (these persist after transaction)
    for (const auth of tx.authorizationList) {
        const eoaAddress = getAddressFromAuth(auth);
        
        // set delegation pointer - 0xef0100 || contract_address
        const delegationPointer = concat(['0xef0100', auth.address]);
        setCode(eoaAddress, delegationPointer);
        
        // nowm this delegation PERSISTS unless explicitly cleared
    }
    
    // execute the transaction normally
    executeTransaction(tx);
    
    // no auto cleanup, delegation stays active
}

注意:如果一笔 7702 交易执行失败,已经处理的委托标识不会回滚,而是会一直保留,除非在后续的一笔 7702 交易中把委托地址设置为 0x0,显式地清除它。

这意味着你有三种委托模式:

临时和持久,两种选择都有

EIP-7702 的好处在于它让你自己选。想为一次性的复杂交易做临时委托?可以。想把你的 EOA 持久升级成像智能合约钱包一样工作?也可以。

临时委托的例子如下

// one off delegation
const tempDelegationTx = {
    type: 0x04,
    authorizationList: [{
        address: batchExecutorContract,
        // ... auth details
    }],
    to: userAddress,
    data: encodeBatchCall([
        // execute your complex operation
        swapTokens(),
        addLiquidity(), 
        stakeRewards(),
    ])
};

而持久委托的例子如下

// upgrade EOA to smart wallet
const persistentDelegationTx = {
    type: 0x04,
    authorizationList: [{
        address: smartWalletImplementation,
        // ... auth details  
    }],
    to: userAddress,
    data: encodeCall("initializeSmartFeatures", [])
    // this delegation persists for future txns
};
 
// later txns can now use smart wallet features
const futureTransaction = {
    to: userAddress, // your EOA will now execute smart wallet code
    data: encodeCall("batchExecute", [multipleOperations])
    // no new auth needed
};

为什么 7702 更安全

EIP-3074 和 EIP-7702 的关键区别不只是临时与持久,而在于风险的分布与控制。EIP-3074 制造了一个共享的故障点,一个热门合约的问题可能波及成千上万的用户。EIP-7702 隔离了每一笔交易,所以 bug 只会影响那一笔具体的交易。EIP-7702 与现有的 ERC-4337 完全兼容,不需要新的 opcodes。这基本上意味着,钱包可以逐步采用它,而不会破坏任何现有系统。

关键的洞见在于,管理委托的是 wallets,而不是 dApps。所以普遍的共识是只允许钱包管理委托,这意味着:

开发者需要知道的事

如果你在构建一个将要支持 EIP-7702 的应用,有几个技术点需要注意

  1. 交易构造的变化 你的交易构建逻辑需要处理授权列表
// old: simple EOA transaction
const tx = {
    to: contractAddress,
    data: encodedCall,
    // ... standard fields
};
 
// new way with eip-7702 delegation
const tx = {
    type: 0x04,
    to: userAddress, // calling user's own address
    data: encodeBatchCall([
        contract.interface.encodeFunctionData("swap", [tokenA, tokenB, amount]),
        contract.interface.encodeFunctionData("addLiquidity", [lpTokens]),
        contract.interface.encodeFunctionData("stake", [lpAmount])
    ]),
    authorizationList: [{
        address: batchExecutorImplementation,
        // ... authorization signature
    }],
    // ... standard fields
};
  1. Gas 估算的复杂性 如果 EOA 决定撤回智能合约的控制权,就必须再发送一笔 7702 交易,把委托重新设置为 0x0。这意味着你的 gas 估算需要考虑到
  1. 智能合约签名支持 EIP-7702 为 EOAs 启用了 EIP-1271 签名验证。这意味着你升级后的 EOA 可以使用 passkeys 这类替代方式来签名消息,同时依然保持向后兼容。
// your EOA can now implement smart signature verification
function isValidSignature(bytes32 hash, bytes signature) external view returns (bytes4) {
    // can verify passkey signatures, multisig, etc.
    if (verifyPasskeySignature(hash, signature)) {
        return 0x1626ba7e; // EIP-1271 magic value
    }
    return 0xffffffff;
}

必读链接

•••
分享本文:

获取最新动态

将最新文章直接发送到你的收件箱。