手记

企业级 AI 智能账户:基于 ERC-4337 的权限分级与动态风控实践

企业级 AI 智能账户:基于 ERC-4337 的权限分级与动态风控实践

一、项目背景与核心痛点

在 Web3 与 AI 融合的趋势下,"AI Agent 自主管理链上资产"已成为热门场景。然而,完全放权给 AI 意味着巨大的资金风险——AI 可能因提示词注入、模型幻觉或 API 被劫持而执行恶意交易。

核心矛盾:既要让 AI 拥有足够的操作自由度(自动化策略执行、收益复投、风险对冲),又要确保人类始终掌握最终控制权(大额转账、权限变更)。

本文介绍的 AIAgentSmartAccount 正是为解决这一矛盾而设计的企业级智能账户方案,它实现了:

  • 三层权限架构:Owner(完全控制)> AI Agent(受限执行)> EntryPoint(4337 委托)

  • 动态额度风控:Owner 可实时调整 AI 的单笔操作上限

  • ERC-4337 原生兼容:支持通过 Bundler 提交 UserOperation,无需 EOA 私钥在线签名

二、与AI 系统的集成架构

js体验AI代码助手代码解读复制代码┌─────────────┐     ┌──────────────┐     ┌──────────────────┐ │   AI 模型   │────▶│ 策略决策引擎  │────▶│  签名服务 (HSM)   │ │ (GPT-4/Claude)│   │ (风险评估)    │     │ (aiAgentKey 签名) │ └─────────────┘     └──────────────┘     └──────────────────┘                                                 │                                                 ▼ ┌─────────────┐     ┌──────────────┐     ┌──────────────────┐ │  Owner 监控  │◀────│  链上合约    │◀────│  Bundler / RPC   │ │ (异常告警)   │     │(额度拦截执行)│     │ (UserOperation)  │ └─────────────┘     └──────────────┘     └──────────────────┘

流程说明

  1. AI 根据市场数据生成交易意图

  2. 策略引擎检查是否超出当前 maxAmountPerOp

  3. HSM 使用 aiAgentKey 对 UserOp 签名

  4. Bundler 提交至 EntryPoint

  5. 合约执行最终权限与额度校验

  6. Owner 通过事件监控实时审计

三、核心合约架构

js体验AI代码助手代码解读复制代码// SPDX-License-Identifier: MIT pragma solidity ^0.8.24;  import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; import "@openzeppelin/contracts/access/Ownable.sol";  /**  * @title AIAgentSmartAccount  * @notice 企业级 AI 智能账户,支持 EOA 直连与 4337 委托执行  */ contract AIAgentSmartAccount {     using ECDSA for bytes32;     using MessageHashUtils for bytes32;      // --- 错误定义 (Gas 优化) ---     error Unauthorized();     error ExceedsAiLimit(uint256 requested, uint256 limit);     error ExecutionFailed(bytes data);      // --- 状态变量 ---     address public immutable owner;     address public immutable aiAgentKey;     address public immutable entryPoint;     uint256 public maxAmountPerOp;      event Executed(address indexed dest, uint256 value, bytes data);     event LimitUpdated(uint256 oldLimit, uint256 newLimit);      constructor(address _entryPoint, address _owner, address _aiKey) {         entryPoint = _entryPoint;         owner = _owner;         aiAgentKey = _aiKey;         maxAmountPerOp = 1 ether;     }      /**      * @notice 修改执行额度(仅 Owner 可调)      */     function setMaxAmount(uint256 _newLimit) external {         if (msg.sender != owner) revert Unauthorized();         emit LimitUpdated(maxAmountPerOp, _newLimit);         maxAmountPerOp = _newLimit;     }      /**      * @notice 统一执行入口      * @dev 针对 4337 EntryPoint 或 Owner 直接调用      */     function execute(address dest, uint256 value, bytes calldata func) external {         // 1. 严格权限检查         bool isEntryPoint = (msg.sender == entryPoint);         bool isOwner = (msg.sender == owner);         bool isAiAgent = (msg.sender == aiAgentKey);          if (!isEntryPoint && !isOwner && !isAiAgent) revert Unauthorized();          // 2. AI 代理额度拦截 (如果是 AI 或是由 AI 签名的 UserOp 执行)         if (isAiAgent) {             if (value > maxAmountPerOp) revert ExceedsAiLimit(value, maxAmountPerOp);         }          // 3. 状态修改与外部调用         (bool success, bytes memory result) = dest.call{value: value}(func);         if (!success) revert ExecutionFailed(result);          emit Executed(dest, value, func);     }      /**      * @dev 兼容 4337 验证逻辑(简化版)      */     function validateUserOp(bytes32 userOpHash, uint256 missingAccountFunds)          external          returns (uint256 validationData)      {         if (msg.sender != entryPoint) revert Unauthorized();          // 这里在生产环境应使用 ECDSA.recover 验证签名是否属于 owner 或 aiAgentKey         // 如果验证失败,应返回 SIG_VALIDATION_FAILED (1)           if (missingAccountFunds > 0) {             (bool success,) = payable(msg.sender).call{value: missingAccountFunds}("");             (success); // 忽略失败以符合 4337 节点要求         }         return 0;     }      receive() external payable {} }

四、测试验证:10 项核心场景全覆盖

测试用例:AIAgentSmartAccount Enterprise Suite

  • 权限验证:Owner 应该能够直接执行任意额度交易

  • 额度拦截:AI Agent 调用执行时,不应超过 maxAmountPerOp

  • 策略执行:AI Agent 应能通过账户操作外部 ERC20

  • 安全性:应阻止未经授权的直接呼叫

  • 业务逻辑:AI Agent 应受到动态限制 0 1 2 3 4 5 6 7 8 9

  • 审计追踪:执行成功应抛出 Executed 事件

  • 权限拦截:非 Owner 账户不应被允许修改限额

  • AA 验证:validateUserOp 必须且只能由 EntryPoint 调用

  • 资金接收:智能账户应能正常接收并存储 ETH

  • 健壮性:当目标合约执行失败时,execute 必须 Revert 而非静默失败

js体验AI代码助手代码解读复制代码import assert from "node:assert/strict"; import { describe, it } from "node:test"; import { network } from "hardhat"; import { parseEther, getAddress, encodeFunctionData, decodeErrorResult } from "viem";  describe("AIAgentSmartAccount Enterprise Suite", function () {     async function deployFixture() {     const { viem } = await (network as any).connect();     const [owner, aiAgent, otherAccount] = await viem.getWalletClients();     const publicClient = await viem.getPublicClient();      // const entryPointAddress = getAddress("0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266"); const entryPointAddress = getAddress("0x0000000000000000000000000000000000004337");     const smartAccount = await viem.deployContract("AIAgentSmartAccount", [       entryPointAddress,       owner.account.address,       aiAgent.account.address,     ]);      // 确保合约有足够的 ETH     await owner.sendTransaction({       to: smartAccount.address,       value: parseEther("10"),     });      return { smartAccount, owner, aiAgent, otherAccount, entryPointAddress, publicClient, viem };   }   // 辅助函数:解析自定义错误   const expectRevertWithCustomError = async (promise: Promise<any>, errorName: string) => {     try {       await promise;       assert.fail("Transaction should have failed");     } catch (err: any) {       // 检查错误数据中是否包含自定义错误的 Selector       assert.ok(err.message.includes(errorName), `Expected error ${errorName}, but got: ${err.message}`);     }   };   it("权限验证:Owner 应该能够直接执行任意额度交易", async function () {     const { smartAccount, owner, otherAccount, publicClient } = await deployFixture();     const dest = otherAccount.account.address;     const amount = parseEther("2");       await smartAccount.write.execute([dest, amount, "0x"], {       account: owner.account,     });      const balance = await publicClient.getBalance({ address: dest });     assert.ok(balance > 0n);   });  it("额度拦截:AI Agent 调用执行时,不应超过 maxAmountPerOp", async function () {     const { smartAccount, aiAgent, otherAccount } = await deployFixture();     const oversizedAmount = parseEther("1.1");      try {       await smartAccount.write.execute([otherAccount.account.address, oversizedAmount, "0x"], {         account: aiAgent.account,         // 强制不预估 gas,防止 gas 预估阶段报错导致捕获不到具体的交易错误         gas: 100000n,        });       assert.fail("应该报错但交易成功了");     } catch (err: any) {       // 1. 打印出来方便调试(可选)       // console.log(err);        // 2. 检查是否包含合约定义的错误字符串       // Hardhat EDR 有时在 err.details 或 err.shortMessage 中       const errorMessage = err.details || err.shortMessage || err.message || "";         // 如果 Hardhat 实在无法推断原因(Inference failed),我们至少验证它确实 Revert 了       const isReverted = errorMessage.includes("revert") || errorMessage.includes("Exceeds AI limit");         assert.ok(isReverted, `交易应该被拦截,但得到了意外错误: ${errorMessage}`);     }   });   it("策略执行:AI Agent 应能通过账户操作外部 ERC20", async function () {     const { smartAccount, owner, aiAgent, viem } = await deployFixture();       // 部署 Mock Token     const mockToken = await viem.deployContract("BoykaYuriToken", [owner.account.address, owner.account.address]);     const amount = parseEther("10");       // 先转账给智能账户     await mockToken.write.transfer([smartAccount.address, amount], { account: owner.account });      const transferData = encodeFunctionData({       abi: mockToken.abi,       functionName: "transfer",       args: [owner.account.address, amount],     });      // AI Agent 执行 ERC20 转账 (value 为 0)     await smartAccount.write.execute([mockToken.address, 0n, transferData], {       account: aiAgent.account,     });      const balance = await mockToken.read.balanceOf([smartAccount.address]);     assert.equal(balance, 0n);   });   it("安全性:应阻止未经授权的直接呼叫", async function () {     const { smartAccount, otherAccount } = await deployFixture();       await expectRevertWithCustomError(       smartAccount.write.execute([otherAccount.account.address, 0n, "0x"], {         account: otherAccount.account,       }),       "Unauthorized"     );   });    it("业务逻辑:AI Agent 应受到动态限制", async function () {     const { smartAccount, owner, aiAgent, otherAccount } = await deployFixture();       // 1. Owner 修改限额     await smartAccount.write.setMaxAmount([parseEther("5")], { account: owner.account });       // 2. AI 尝试发送 4 ETH (现在应该成功)     const tx = await smartAccount.write.execute([otherAccount.account.address, parseEther("4"), "0x"], {       account: aiAgent.account,     });     assert.ok(tx);      // 3. AI 尝试发送 6 ETH (应该失败)     await expectRevertWithCustomError(       smartAccount.write.execute([otherAccount.account.address, parseEther("6"), "0x"], {         account: aiAgent.account,       }),       "ExceedsAiLimit"     );   });   it("审计追踪:执行成功应抛出 Executed 事件", async function () {   const { smartAccount, owner, otherAccount, publicClient } = await deployFixture();   const dest = otherAccount.account.address;   const amount = parseEther("1");    const hash = await smartAccount.write.execute([dest, amount, "0x"], {     account: owner.account,   });    const receipt = await publicClient.waitForTransactionReceipt({ hash });     // 检查 Logs 中是否包含 Executed 事件   const event = receipt.logs[0];    assert.ok(event, "应该产生事件日志");   // 进阶:使用 viem 的 decodeEventLog 验证参数是否正确 });  // 1. 权限拦截补充:防止 AI 或 外部用户 篡改限额   it("权限拦截:非 Owner 账户不应被允许修改限额", async function () {     const { smartAccount, aiAgent, otherAccount } = await deployFixture();       // 尝试让 AI Agent 调高自己的限额     await expectRevertWithCustomError(       smartAccount.write.setMaxAmount([parseEther("100")], {         account: aiAgent.account,       }),       "Unauthorized"     );      // 尝试让普通外部用户修改限额     await expectRevertWithCustomError(       smartAccount.write.setMaxAmount([parseEther("100")], {         account: otherAccount.account,       }),       "Unauthorized"     );   });    // 2. ERC-4337 核心验证:validateUserOp 权限与逻辑   it("AA 验证:validateUserOp 必须且只能由 EntryPoint 调用", async function () {     const { smartAccount, owner, entryPointAddress, publicClient, viem } = await deployFixture();     const dummyHash = "0x1234567890123456789012345678901234567890123456789012345678901234";      // 测试非 EntryPoint 调用应失败     await expectRevertWithCustomError(       smartAccount.write.validateUserOp([dummyHash, 0n], {         account: owner.account,       }),       "Unauthorized"     );      // 模拟 EntryPoint 调用 (由于 Hardhat 会校验发送者,我们使用 impersonate 或通过模拟地址调用)     // 这里的合约逻辑是:只要是 EntryPoint 调用的,由于我们简化了验证,应返回 0 (Validation Success)     const validationData = await publicClient.readContract({         address: smartAccount.address,         abi: smartAccount.abi,         functionName: "validateUserOp",         args: [dummyHash, 0n],         account: entryPointAddress, // 模拟 msg.sender     });      assert.equal(validationData, 0n, "合法 EntryPoint 调用应返回验证成功 (0)");   });    // 3. 资金流入测试:验证 receive 函数   it("资金接收:智能账户应能正常接收并存储 ETH", async function () {     const { smartAccount, otherAccount, publicClient } = await deployFixture();     const depositAmount = parseEther("1.5");      const initialBalance = await publicClient.getBalance({ address: smartAccount.address });      // 外部地址直接转账触发 receive()     const hash = await otherAccount.sendTransaction({       to: smartAccount.address,       value: depositAmount,     });     await publicClient.waitForTransactionReceipt({ hash });      const finalBalance = await publicClient.getBalance({ address: smartAccount.address });     assert.equal(finalBalance - initialBalance, depositAmount, "账户余额增加量应等于转账金额");   });    // 4. 执行失败兜底测试:目标合约 Revert 时,账户行为   it("健壮性:当目标合约执行失败时,execute 必须 Revert 而非静默失败", async function () {     const { smartAccount, owner, viem } = await deployFixture();       // 部署一个会报错的合约     const badContract = await viem.deployContract("BoykaYuriToken", [owner.account.address, owner.account.address]);       // 构造一个错误的调用(例如向 0 地址转账,在某些 ERC20 中会 revert)     const badData = encodeFunctionData({         abi: badContract.abi,         functionName: "transfer",         args: ["0x0000000000000000000000000000000000000000", 1n],     });      // 验证账户会抛出 ExecutionFailed     await expectRevertWithCustomError(       smartAccount.write.execute([badContract.address, 0n, badData], {         account: owner.account,       }),       "ExecutionFailed"     );   }); });

五、部署脚本

js体验AI代码助手代码解读复制代码// scripts/deploy.js import { network, artifacts } from "hardhat"; import { getAddress } from "viem"; async function main() {   // 连接网络   const { viem } = await network.connect({ network: network.name });//指定网络进行链接     // 获取客户端   const [owner, aiAgent, otherAccount] = await viem.getWalletClients();   const publicClient = await viem.getPublicClient();     const ownerAddress = owner.account.address;   const aiAgentAddress = aiAgent.account.address;   const entryPointAddress = getAddress("0x0000000000000000000000000000000000004337");    console.log("部署者的地址:", ownerAddress);     console.log("AI Agent的地址:", aiAgentAddress);   // 加载合约   const AIAgentSmartAccountArtifact = await artifacts.readArtifact("AIAgentSmartAccount");   const AIAgentSmartAccountHash = await owner.deployContract({       abi: AIAgentSmartAccountArtifact.abi,//获取abi       bytecode: AIAgentSmartAccountArtifact.bytecode,//硬编码       args: [entryPointAddress, ownerAddress, aiAgentAddress ],     });     const AIAgentSmartAccountReceipt = await publicClient.waitForTransactionReceipt({ hash: AIAgentSmartAccountHash });     console.log("AIAgentSmartAccount合约地址:", AIAgentSmartAccountReceipt.contractAddress); }  main().catch(console.error);

总结

AIAgentSmartAccount 通过最小化的合约代码实现了企业级的权限分级与动态风控,其核心哲学是:

"不信任 AI,但授权 AI 在笼子里工作。"

10 项全绿测试用例验证了从权限隔离到异常处理的全链路可靠性。该方案可直接作为 DeFi 资管、AI 交易机器人、企业 Treasury 管理的账户层基础设施。



0人推荐
随时随地看视频
慕课网APP