本文介绍了EIP-7702提案,该提案允许EOA账户将执行权委托给智能合约,从而实现批量操作、赞助交易和权限降级等功能。文章详细说明了如何通过签名授权和发送SET_CODE_TX_TYPE交易来实现委托,并探讨了与ERC-4337结合使用的方法,以便利用现有的基础设施来处理用户操作。同时,文章也提醒了安全风险,如重放攻击、存储冲突以及与赞助交易中继器的潜在问题。
EIP-7702 引入了一种新的交易类型 ( 0x4
),它授予 外部拥有账户 (EOA,Externally Owned Accounts) 将执行委托给智能合约的能力。 这对于使传统的 EVM 账户能够:
本节将引导你完成按照 ERC-7702 将 EOA 委托给合约的过程。 这允许你使用 EOA 的私钥通过自定义执行逻辑来签名和执行操作。 结合 ERC-4337 基础设施,用户可以通过 paymaster 实现 gas 赞助。
EIP-7702 使 EOA 能够将其执行能力委托给智能合约,从而有效地弥合了传统账户和 智能账户 之间的差距。 SignerERC7702
实用程序通过根据 EOA 的地址 ( address(this)
) 验证签名来促进此委托,从而更容易在智能合约账户中实现 EIP-7702。
// contracts/MyAccountERC7702.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {Account} from "@openzeppelin/community-contracts/account/Account.sol";
import {ERC721Holder} from "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol";
import {ERC1155Holder} from "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol";
import {ERC7821} from "@openzeppelin/community-contracts/account/extensions/ERC7821.sol";
import {SignerERC7702} from "@openzeppelin/community-contracts/utils/cryptography/signers/SignerERC7702.sol";
contract MyAccountERC7702 is Account, SignerERC7702, ERC7821, ERC721Holder, ERC1155Holder {
/// @dev 允许入口点作为授权的执行者。
function _erc7821AuthorizedExecutor(
address caller,
bytes32 mode,
bytes calldata executionData
) internal view virtual override returns (bool) {
return caller == address(entryPoint()) || super._erc7821AuthorizedExecutor(caller, mode, executionData);
}
}
用户可以委托给 ERC-7821 的实例,以获得一个不使用 ERC-4337 相关代码的最小批量执行器。 |
为了授权委托,EOA 所有者签署一条消息,其中包含链 ID、nonce、委托地址和签名组件(即 [chain_id, address, nonce, y_parity, r, s]
)。 此签名授权有两个目的:它将执行限制为仅委托合约,并防止重放攻击。
EOA 为每个链上的每个授权地址维护一个委托指示符,该指示符指向合约,该合约的代码将在 EOA 的上下文中执行以处理委托的操作。
以下是如何使用 viem 构建授权:
// 记住不要硬编码你的私钥!
const eoa = privateKeyToAccount('<YOUR_PRIVATE_KEY>');
const eoaClient = createWalletClient({
account: eoa,
chain: publicClient.chain,
transport: http(),
});
const walletClient = createWalletClient({
account, // 查看 Viem 的 `privateKeyToAccount`
chain, // import { ... } from 'viem/chains';
transport: http(),
})
const authorization = await eoaClient.signAuthorization({
account: walletClient.account.address,
contractAddress: '0x<YOUR_DELEGATE_CONTRACT_ADDRESS>',
// 如果你的 walletClient 的 account
// 是发送交易的 account,则使用此项代替 `account`
// executor: "self",
});
在实施委托合约时,请确保它们需要避免重放的签名(例如,域名分隔符、nonce)。<br>实施不完善的委托可能会允许恶意行为者几乎完全控制签名者的 EOA。 |
签署授权后,你需要发送一个 SET_CODE_TX_TYPE
(0x04) 交易,将委托指示符(即 0xef0100 || address
) 写入到你的 EOA 的代码中,这会告诉 EVM 在对你的 EOA 执行操作时,从指定的地址加载并执行代码。
// 随同 `data` 一起发送 `authorization`
const receipt = await walletClient
.sendTransaction({
authorizationList: [authorization],
data: '0x<CALLDATA_TO_EXECUTE_IN_THE_ACCOUNT>',
to: eoa.address,
})
.then((txHash) =>
publicClient.waitForTransactionReceipt({
hash: txHash,
})
);
// 打印 receipt
console.log(userOpReceipt);
要删除委托并将你的 EOA 恢复到其原始状态,你可以发送一个 SET_CODE_TX_TYPE
交易,其中包含一个指向零地址( 0x0000000000000000000000000000000000000000
) 的授权元组。 这将清除账户的代码并将其代码哈希重置为空哈希,但是,请注意,它不会自动清理 EOA 存储。
当更改账户的委托时,请确保新委托的代码是经过专门设计和测试的,作为旧代码的升级。 为了确保委托合约之间的安全迁移,遵循 ERC-7201 的命名空间存储可避免意外冲突。
由于潜在的存储冲突,更新委托指示符可能会导致你的 EOA 无法使用。 我们建议遵循类似于 编写可升级智能合约 的做法。 |
在 EOA 上设置代码以执行逻辑的能力允许用户利用 ERC-4337 基础设施来处理用户操作。 开发人员只需要将 Account
合约与 SignerERC7702
结合起来,即可实现开箱即用的 ERC-4337 合规性。
一旦你的 EOA 被委托给兼容 ERC-4337 的账户,你就可以通过入口点合约发送用户操作。 用户操作包括执行所需的所有字段,包括 gas 限制、费用以及要执行的实际调用数据。 入口点将验证操作并在你的委托账户的上下文中执行它。
类似于如何为工厂账户实现 发送 UserOp,以下是如何为一个已委托给 Account
合约的 EOA 构建 UserOp。
const userOp = {
sender: eoa.address,
nonce: await entrypoint.read.getNonce([eoa.address, 0n]),
initCode: "0x" as Hex,
callData: '0x<CALLDATA_TO_EXECUTE_IN_THE_ACCOUNT>',
accountGasLimits: encodePacked(
["uint128", "uint128"],
[
100_000n, // verificationGas
300_000n, // callGas
]
),
preVerificationGas: 50_000n,
gasFees: encodePacked(
["uint128", "uint128"],
[
0n, // maxPriorityFeePerGas
0n, // maxFeePerGas
]
),
paymasterAndData: "0x" as Hex,
signature: "0x" as Hex,
};
const userOpHash = await entrypoint.read.getUserOpHash([userOp]);
userOp.signature = await eoa.sign({ hash: userOpHash });
const userOpReceipt = await eoaClient
.writeContract({
abi: EntrypointV08Abi,
address: entrypoint.address,
authorizationList: [authorization],
functionName: "handleOps",
args: [[userOp], eoa.address],
})
.then((txHash) =>
publicClient.waitForTransactionReceipt({
hash: txHash,
})
);
console.log(userOpReceipt);
当使用赞助交易中继器时,请注意,授权账户可能会导致中继器花费 gas 而未得到报销,方法是使授权无效(增加账户的 nonce)或通过清除账户中的相关资产。 中继器可以实施诸如要求保证金或使用信誉系统之类的安全措施。 |
- 原文链接: docs.openzeppelin.com/co...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!