EIP-7702引入了一种新的交易类型0x4
,使外部账户(EOA)能够执行临时的智能合约功能,支持批量交易、赞助Gas支付等功能。文章详细介绍了EIP-7702的技术细节、使用场景,并通过Foundry工具展示了如何测试和部署该功能。
EIP-7702 在 以太坊的 Pectra 升级 中引入了一种新的交易类型 0x4
,使外部拥有账户(EOAs)能够执行临时的智能合约功能。这一账户抽象的进步弥合了 EOAs 和智能合约之间的差距,实现了批量交易、赞助 gas 支付和受控访问委托等关键功能。
注意
虽然 EIP-7702 尚未在主网或测试网上线,但可以通过本地 Foundry 环境和 Odyssey 测试网 在 Pectra 升级发布前进行测试。
本指南将带你了解 EIP-7702 的技术细节、用例以及如何使用 Foundry 和 Foundry 的 cheatcodes 进行测试。通过本指南,你将清楚地了解如何在项目中利用 EIP-7702,通过部署一个实现合约来查看 EIP-7702 的实际应用,并测试 EIP-7702 的功能。
0x4
交易0x4
交易,查看 EIP‑7702 的实际应用虽然典型的以太坊交易要么是转账,要么是与智能合约交互,但新的 0x4
交易类型允许 EOA 直接执行代码。这为外部拥有账户(EOAs)解锁了新的可能性,使它们能够更像智能合约一样运作。
通过这一新标准,EOAs 可以直接从自己的地址执行智能合约逻辑,从而实现以下功能:
以太坊账户类型
通过 本指南 了解更多关于以太坊账户类型的信息。
用户(EOA)签署一个授权消息,该消息包括链 ID、nonce、委托地址和签名部分(y_parity
、r
、s
)。这生成了一个签名授权,确保只有批准的实现合约可以执行交易,并防止重放攻击。
对于每个授权的委托地址,用户(EOA)存储一个委托指示器,指向 EOA 将委托给的实现合约。当用户(或赞助者)执行 EIP-7702 交易时,它会从该指示器指示的地址加载并运行代码。
在典型的以太坊交易中,如果你想调用智能合约上的函数,你将 to
字段设置为该合约的地址,并包含适当的 data
以调用其函数。在 EIP-7702 中,你将 to
字段设置为 EOA 的地址,并包含 data
以调用实现合约的函数,同时附带签名授权消息。
以下代码片段展示了如何使用 Viem 的钱包客户端为 EIP‑7702 智能账户构建批量交易。
to
字段设置为智能账户自己的地址。data
字段通过编码对 execute
函数的调用创建,该函数包含两个调用对象的数组。execute
函数应在实现合约中定义,并处理批量交易逻辑。authorizationList
中包含签名授权,允许智能账户将执行委托给实现合约。如果另一个钱包(赞助者)想要执行此交易(赞助交易),它可以使用相同的授权签名将执行委托给实现合约。
注意: 实现合约应设计为处理由 EIP-7702 启用的批量交易和其他功能。此外,它们应包括 nonce 和重放保护机制,以防止未经授权的交易。
import { createWalletClient, http, parseEther } from 'viem'
import { anvil } from 'viem/chains'
import { privateKeyToAccount } from 'viem/accounts'
import { eip7702Actions } from 'viem/experimental'
import { abi, contractAddress } from './contract' // 假设你已经部署了合约并在单独的文件中导出了 ABI 和合约地址
const account = privateKeyToAccount('0x...')
const walletClient = createWalletClient({
account,
chain: anvil,
transport: http(),
}).extend(eip7702Actions())
const authorization = await walletClient.signAuthorization({
contractAddress,
})
const hash = await walletClient.sendTransaction({
authorizationList: [authorization],
data: encodeFunctionData({
abi,
functionName: 'execute',
args: [\
[\
{\
data: '0x',\
to: '0xcb98643b8786950F0461f3B0edf99D88F274574D',\
value: parseEther('0.001'),\
},\
{\
data: '0x',\
to: '0xd2135CfB216b74109775236E36d4b433F1DF507B',\
value: parseEther('0.002'),\
},\
],\
]
}),
to: walletClient.account.address,
})
如果 EIP-7702 感觉抽象或复杂,不用担心!我们将通过一个动手演示来分解它——部署一个实现合约并测试其功能。
在本节中,你将学习如何使用 Foundry 实现 EIP-7702 功能,Foundry 是一个强大的智能合约开发工具。
Foundry Cheatcodes
在撰写本文时,EIP‑7702 尚未在以太坊主网或公共测试网上得到支持。但是,你可以使用 Foundry 在本地网络上实验其功能。Foundry 提供了一组 cheatcodes——特殊的命令,用于修改以太坊虚拟机(EVM)的行为以简化测试。在本指南中,我们将使用 signDelegation
、attachDelegation
和 signAndAttachDelegation
cheatcodes 来测试 EIP‑7702 功能。有关更多信息,请查看 Foundry Cheatcodes 文档。
在本项目中,我们部署了一个名为 BatchCallAndSponsor
的实现合约,该合约支持以下功能:
项目包括以下文件:
BatchCallAndSponsor.sol
– 包含批量交易和赞助交易的核心逻辑。BatchCallAndSponsor.t.sol
– 包括直接执行和赞助执行的单元测试。BatchCallAndSponsor.s.sol
– 用于部署合约并执行网络交易的脚本。MockERC20.sol
– 用于测试代币转账的模拟 ERC-20 代币合约。警告
本项目中的所有材料和代码仅用于教育目的。它们不适用于生产环境。
如果尚未安装 Foundry,请使用以下命令进行安装:
curl -L https://foundry.paradigm.xyz | bash
然后,重新启动终端并运行:
foundryup
这将确保你安装了最新版本。
如果你想从头开始设置项目,请初始化一个新的 Foundry 项目:
forge init eip-7702-project
cd eip-7702-project
或者,你可以克隆 QuickNode 示例项目:
git clone https://github.com/quiknode-labs/qn-guide-examples.git
cd qn-guide-examples/ethereum/eip-7702
安装所需的库:
forge install foundry-rs/forge-std
forge install OpenZeppelin/openzeppelin-contracts
forge-std
:提供测试的实用函数。openzeppelin-contracts
:包括 ERC-20 实现和加密实用工具。为了简化导入路径,请运行以下命令。它将在项目根目录中创建一个 remappings.txt
文件,包括所需的重映射。
forge remappings > remappings.txt
这将确保像以下这样的合约导入能够正确工作,而无需长相对路径:
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
修改 foundry.toml
以确保与 EIP‑7702 兼容,设置 Prague 硬分叉。在 [profile.default]
部分添加以下行:
evm_version = "prague"
这是必要的,因为 EIP‑7702 仅在 Prague 升级之后可用。
如果你使用示例项目,可以跳过此部分及以下步骤。
在 src
文件夹中,创建一个名为 BatchCallAndSponsor.sol
的新文件,并从 EIP-7702 示例项目 中添加合约逻辑。这是一个支持批量交易和赞助 gas 的基本实现合约。
不要忘记从 src
文件夹中删除任何未使用的文件(例如 Counter.sol
)。
实现合约的详细信息在 实现合约详解 部分提供。
在 test
文件夹中,创建一个名为 BatchCallAndSponsor.t.sol
的文件来测试合约。从 EIP-7702 示例项目 中添加测试用例。
不要忘记从 test
文件夹中删除任何未使用的文件(例如 Counter.t.sol
)。
测试用例的详细信息在 测试用例详解 部分提供。
在 script
文件夹中创建一个名为 BatchCallAndSponsor.s.sol
的文件,用于部署合约并执行交易。从 EIP-7702 示例项目 中添加部署脚本。
不要忘记从 script
文件夹中删除任何未使用的文件(例如 Counter.s.sol
)。
部署脚本的详细信息在 部署脚本详解 部分提供。
项目结构如下:
├── README.md
├── foundry.toml # Foundry 配置文件
├── lib # 已安装的包
│ ├── forge-std
│ └── openzeppelin-contracts
├── remappings.txt # 重映射文件
├── script
│ └── BatchCallAndSponsor.s.sol # 部署脚本
├── src
│ └── BatchCallAndSponsor.sol # 实现合约
└── test
└── BatchCallAndSponsor.t.sol # 测试用例
如果你想跳过详解并直接进入操作,请转到 运行和测试 EIP-7702 项目 部分。
BatchCallAndSponsor
合约是一个支持批量交易和赞助 gas 的简单实现合约。
请查看项目中的 BatchCallAndSponsor.sol
文件以获取完整实现。以下部分提供了合约功能的简要概述;但请注意,未包含完整的实现代码。
合约中的 execute
函数接受一个 Call
结构体数组,每个结构体代表一个不同的调用,并指定目标地址、值(以 Ether 为单位)和调用数据。
struct Call {
address to;
uint256 value;
bytes data;
}
function execute(Call[] calldata calls) external payable {
require(msg.sender == address(this), "Invalid authority");
_executeBatch(calls);
}
function _executeBatch(Call[] calldata calls) internal {
uint256 currentNonce = nonce;
nonce++;
for (uint256 i = 0; i < calls.length; i++) {
_executeCall(calls[i]);
}
emit BatchExecuted(currentNonce, calls);
}
function _executeCall(Call calldata callItem) internal {
(bool success,) = callItem.to.call{value: callItem.value}(callItem.data);
require(success, "Call reverted");
emit CallExecuted(msg.sender, callItem.to, callItem.value, callItem.data);
}
合约使用 OpenZeppelin 的 ECDSA
库和 MessageHashUtils
验证签名。签名消息包括调用者地址、目标合约、调用和 nonce。
bytes32 digest = keccak256(abi.encodePacked(nonce, encodedCalls));
require(ECDSA.recover(digest, signature) == msg.sender, "Invalid signature");
合约支持直接执行或赞助执行。
function execute(Call[] calldata calls) external payable {
// 调用者直接执行调用
}
function execute(Call[] calldata calls, bytes calldata signature) external payable {
// 赞助者代表调用者执行调用
}
合约使用 nonce 防止重放攻击。每次成功执行后,nonce 都会递增。如果不实现 nonce,攻击者可以多次重放相同的交易。
function _executeBatch(Call[] calldata calls) internal {
uint256 currentNonce = nonce;
nonce++; // 递增 nonce 以防止重放攻击
for (uint256 i = 0; i < calls.length; i++) {
_executeCall(calls[i]);
}
emit BatchExecuted(currentNonce, calls);
}
BatchCallAndSponsor.t.sol
文件包含 BatchCallAndSponsor
合约的测试用例。测试用例涵盖了直接执行和赞助执行场景、重放保护和错误处理。
以下部分提供了一些测试代码的见解,但并未包含所有代码。完整的测试文件请参考 BatchCallAndSponsor.t.sol
文件。
testDirectExecution
函数测试调用者(即 Alice)直接执行调用。它验证 Alice 在单个交易中向 Bob 发送 1 ETH 和 100 个代币。
在此测试中,Alice 授权实现合约代表她执行交易。我们使用 signAndAttachDelegation
cheatcode 签署授权消息并将其附加到交易中。
然后,Alice 自己在Alice 的 EOA 上调用 execute
函数,并传递调用数组,这在没有 EIP-7702 的情况下是不可能的。
testDirectExecution
函数的部分代码
function testDirectExecution() public {
console2.log("Sending 1 ETH from Alice to Bob and transferring 100 tokens to Bob in a single transaction");
BatchCallAndSponsor.Call[] memory calls = new BatchCallAndSponsor.Call[](2);
// ETH 转账
calls[0] = BatchCallAndSponsor.Call({to: BOB_ADDRESS, value: 1 ether, data: ""});
// 代币转账
>- 原文链接: [quicknode.com/guides/eth...](https://www.quicknode.com/guides/ethereum-development/smart-contracts/eip-7702-smart-accounts)
>- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!