EIP-7702 技术概述
EIP-7702(以太坊改进提案7702)引入了一种新的机制,允许外部拥有账户(EOA)临时获得智能合约的功能,而无需将其转换为合约账户。这种机制被称为"账户委托"(Account Delegation)。
EIP-7702 的核心思想是:允许用户签署一个特殊的授权消息,指定一个合约地址,然后在交易执行时,用户的 EOA 临时获得该合约的代码,使其能够执行智能合约功能。
授权消息格式
EIP-7702 定义了一种特殊的授权消息格式,用户需要使用其私钥签署这个消息:
struct Authorization {
address contractAddress;
uint256 validUntil;
uint256 validAfter;
bytes32 nonce;
}
在 viem 库中,签署授权的代码如下:
const authorization = await wallet.signAuthorization({
contractAddress: BATCH_CALL_DELEGATION,
});
技术实现原理
EIP-7702 的实现涉及以太坊协议层的修改,主要包括以下几个方面:
- 授权验证:以太坊节点验证用户提交的授权签名是否有效
- 代码注入:临时将授权合约的代码注入到用户的 EOA 地址
- 上下文保持:在执行过程中保持
msg.sender 为用户的 EOA 地址
- 状态恢复:交易执行完成后恢复 EOA 的原始状态
关键技术点:当 EOA 被委托给合约后,它会临时获得该合约的代码,但在执行过程中,msg.sender 仍然是 EOA 的地址,而不是合约地址。这使得 EOA 可以直接调用其他合约,就好像是 EOA 自己在调用一样。
批处理合约示例
以下是一个简单的批处理合约示例,它允许用户在一次交易中执行多个操作:
pragma solidity ^0.8.20;
contract BatchCallDelegation {
struct Call {
bytes data;
address to;
uint256 value;
}
function execute(Call[] calldata calls) external payable {
for (uint256 i = 0; i < calls.length; i++) {
Call memory call = calls[i];
(bool success,) = call.to.call{ value: call.value }(call.data);
require(success, "call reverted");
}
}
}
这个合约非常简单,它只有一个 execute 函数,接受一个 Call 结构体数组,然后依次执行每个调用。
注意: 这个合约没有任何访问控制,任何人都可以调用它的 execute 函数。在实际应用中,您可能需要添加适当的访问控制机制。
为什么不需要传统的代币授权
在传统的 ERC20 代币交互中,如果一个合约要代表用户转移代币,需要以下步骤:
- 用户调用代币合约的
approve() 函数,允许第三方合约使用一定数量的代币
- 第三方合约调用
transferFrom() 函数来实际转移这些代币
这个过程需要两个交易,用户体验不佳。而使用 EIP-7702,情况完全不同:
技术原理解析
当使用 EIP-7702 授权执行交易时:
- 用户的 EOA 临时获得批处理合约的代码
- 批处理合约调用代币合约的
transfer() 函数
- 代币合约看到的
msg.sender 是用户的 EOA 地址,而不是批处理合约的地址
- 因此,代币合约直接从用户的余额中扣除代币,而不需要事先批准
这就是为什么使用 EIP-7702 时,批处理合约可以直接调用代币的 transfer() 函数而不需要事先获得授权的原因。
await token.approve(batchContract.address, amount);
await batchContract.execute([{ to: token.address, data: transferFromData }]);
await wallet.writeContract({
abi: batchAbi,
address: account.address,
functionName: "execute",
args: [[{ to: token.address, data: transferData }]],
authorizationList: [authorization],
});
EIP-7702 与 ERC-4337 的比较
EIP-7702 和 ERC-4337(账户抽象)都旨在改善以太坊的用户体验,但它们的方法和目标有所不同:
| 特性 |
EIP-7702 |
ERC-4337 |
| 账户类型 |
保持 EOA 不变,临时赋予合约功能 |
创建新的智能合约账户 |
| 实现方式 |
协议层修改 |
应用层实现 |
| 兼容性 |
需要网络升级 |
在现有网络上可用 |
| 用户迁移 |
无需迁移 |
需要创建新账户 |
| Gas 支付 |
用户支付 |
可由第三方支付 |
EIP-7702 可以被视为向完全账户抽象过渡的一个中间步骤,它为现有的 EOA 用户提供了智能合约功能,而无需迁移到新的账户系统。
安全考虑
使用 EIP-7702 时,需要考虑以下安全问题:
- 授权合约的安全性:用户应该仔细审查被授权的合约代码,确保它不包含恶意功能
- 授权有效期:建议设置适当的授权有效期,避免长期授权带来的风险
- 重放攻击防护:确保使用正确的 nonce 值,防止授权被重放
- 授权撤销:了解如何撤销已经授予的授权
警告: 授权一个合约意味着该合约可以代表您执行操作,包括转移您的资产。只授权您信任的合约,并且仔细检查合约的功能和安全性。
代码示例:批量转账 ETH 和代币
以下是一个完整的代码示例,展示如何使用 EIP-7702 在一次交易中同时转账 ETH 和代币:
import { createWalletClient, http, parseEther, parseAbi, createPublicClient, encodeFunctionData } from "viem";
import { anvil } from "viem/chains";
import { privateKeyToAccount } from "viem/accounts";
import { eip7702Actions } from "viem/experimental";
const ALICE_PK = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80";
const BOB = "0x70997970C51812dc3A010C7d01b50e0d17dc79C8";
const BATCH_CALL_DELEGATION = "0x5FbDB2315678afecb367f032d93F642f64180aa3";
const SIMPLE_TOKEN = "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512";
const main = async () => {
const account = privateKeyToAccount(ALICE_PK);
const clients = {
wallet: createWalletClient({ chain: anvil, transport: http(), account }).extend(eip7702Actions()),
public: createPublicClient({ chain: anvil, transport: http() }),
};
const authorization = await clients.wallet.signAuthorization({
contractAddress: BATCH_CALL_DELEGATION,
});
const tokenAbi = parseAbi([
"function transfer(address,uint256) returns (bool)",
]);
const tokenTransferData = encodeFunctionData({
abi: tokenAbi,
functionName: "transfer",
args: [BOB, 100n * 10n ** 18n],
});
const batchAbi = parseAbi(["function execute((bytes data,address to,uint256 value)[])"]);
await clients.wallet.writeContract({
abi: batchAbi,
address: account.address,
functionName: "execute",
args: [
[
{
data: "0x",
to: BOB,
value: parseEther("1"),
},
{
data: tokenTransferData,
to: SIMPLE_TOKEN,
value: 0n,
},
],
],
authorizationList: [authorization],
});
console.log(">>> 批量转账成功完成");
};
main();