从零拆解 Aave V4:Hub-Spoke 架构、EIP-1153 瞬态存储与落地场景解析

5 阅读12分钟

合规提示:本文代码与内容仅用于区块链底层技术研究、智能合约安全审计与去中心化金融(DeFi)架构学习,不构成任何投资建议或金融服务承诺。

前言

在当前的 DeFi 架构演进中,Aave V4 推出的 Hub-Spoke(中心-辐射) 架构备受瞩目。它通过核心流动性解耦与清算风险控制,实现了资本效率与安全性的跃升。

今天,我们将从架构设计、核心亮点到商业落地场景,带大家一次性理清 Aave V4 的技术精髓。


一、 核心架构设计

整个系统由三大核心组件构成:

  1. AaveV4Hub(资金中心) :

    • 集中管理所有资产的全局闲置流动性、借出总量。
    • 提供统一的闪电贷通道,并引入 tload / tstore (EIP-1153) 实现零 Gas 开销的瞬态防重入锁。
    • 为各个 Spoke(分支市场)设定信用额度控制。
  2. AaveV4Spoke(分支清算与借贷市场) :

    • 负责用户的抵押品存入、借款计算、健康因子校验。
    • 内置分段利率模型(Interest Rate Model) ,当资金利用率跨越拐点(Optimal Utilization)时动态调整借款利率。
    • 实现 Target Health Factor(目标健康因子)局部智能疗伤清算,避免传统清算一次性全额归零的粗暴体验。
  3. 测试与接收端合约:

    • 包含模拟闪电贷执行的 MockFlashLoanReceiver 以及全套集成测试用例。

二、 四大核心技术亮点

  1. EIP-1153 瞬态存储防重入

    • 利用以太坊最新的瞬态内存(tload / tstore),闪电贷重入锁在交易结束后自动清空,省去了昂贵的 SSTORE 状态修改 Gas 费,同时杜绝了跨合约重入隐患。
  2. 全局流动性聚合(Liquidity Aggregation)

    • 总行(Hub)集中管理总资本池,分行(Spoke)按需借调。大幅提升了资金利用率(Utilization Rate),降低了用户的平均借贷成本。
  3. 动态分段利率模型

    • 以 Optimal Utilization(最优利用率拐点) 为界。利用率健康时利率平稳;一旦跨越拐点(如 80%),利率呈指数级飙升,通过经济杠杆自动防范挤兑风险。
  4. 温和的“局部疗伤”清算(Target Health Factor)

    • 摒弃了传统的“一刀切”全额清算,通过精确计算仅清算恢复安全线所需的债务部分,保护了用户资产,减少了市场瞬间冲击。

三、 Aave V4 的四大商业落地场景

  • 1. 模块化隔离风险市场(Isolated Risk Markets)

    • 开发者或机构可自主部署专属 Spoke(如长尾资产、RWA、LST),将风险严格隔离在分支内部,不波及全局核心流动性。
  • 2. 机构级与合规 RWA 资产上链

    • 机构可构建内置白名单和身份验证的合规 Spoke,无缝共享 DeFi 底层庞大的闲置流动性深度。
  • 3. AI Agent 与自动化策略集成

    • 结合 EIP-1153 瞬态闪电贷与无状态调用,AI 智能体能够以极低 Gas 开销高效执行链上套利与杠杆策略。
  • 4. 多 Rollup 跨链流动性聚合

    • Hub 部署在核心结算层,各大 L2 作为 Spoke 通过跨链协议调用中心流动性,有效解决多链环境下的资本碎片化问题。

四、 核心合约代码实现

1. AaveV4Hub.sol —— 资金与闪电贷中心

// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";

interface IFlashLoanReceiver {
    function executeOperation(address asset, uint256 amount, uint256 fee, address initiator, bytes calldata params) external returns (bool);
}

contract AaveV4Hub is Ownable, ReentrancyGuard {
    using SafeERC20 for IERC20;

    // 瞬态存储插槽定义 (EIP-1153),用于零 Gas 锁标记
    bytes32 private constant FLASH_LOAN_KEY = keccak256("AAVE_V4_FLASH_LOAN_LOCKED");

    mapping(address => uint256) public totalPoolLiquidity;       // 闲置流动性
    mapping(address => uint256) public totalBorrowedLiquidity;   // 被借出的总流动性
    mapping(address => mapping(address => uint256)) public spokeCreditLimits;
    mapping(address => mapping(address => uint256)) public spokeBorrows;

    // 闪电贷费率:万分之九 (0.09%)
    uint256 public constant FLASH_LOAN_FEE_BPS = 9;
    uint256 public constant BPS_DIVISOR = 10000;

    event Deposited(address indexed user, address indexed asset, uint256 amount);
    event Withdrawn(address indexed user, address indexed asset, uint256 amount);
    event SpokeBorrowed(address indexed spoke, address indexed asset, address addressBorrower, uint256 amount);
    event SpokeRepaid(address indexed spoke, address indexed asset, uint256 amount);
    event FlashLoan(address indexed receiver, address indexed asset, uint256 amount, uint256 fee);

    constructor() Ownable(msg.sender) {}

    function setSpokeLimit(address spoke, address asset, uint256 limit) external onlyOwner {
        spokeCreditLimits[spoke][asset] = limit;
    }

    function deposit(address asset, uint256 amount) external nonReentrant {
        require(amount > 0, "Amount zero");
        totalPoolLiquidity[asset] += amount;
        IERC20(asset).safeTransferFrom(msg.sender, address(this), amount);
        emit Deposited(msg.sender, asset, amount);
    }

    function withdraw(address asset, uint256 amount) external nonReentrant {
        require(totalPoolLiquidity[asset] >= amount, "Insufficient liquidity");
        totalPoolLiquidity[asset] -= amount;
        IERC20(asset).safeTransfer(msg.sender, amount);
        emit Withdrawn(msg.sender, asset, amount);
    }

    function executeBorrow(address asset, address borrower, uint256 amount) external {
        uint256 limit = spokeCreditLimits[msg.sender][asset];
        uint256 currentBorrow = spokeBorrows[msg.sender][asset];
        
        require(currentBorrow + amount <= limit, "Spoke credit limit exceeded");
        require(totalPoolLiquidity[asset] >= amount, "Hub liquidity shortage");

        spokeBorrows[msg.sender][asset] += amount;
        totalPoolLiquidity[asset] -= amount;
        totalBorrowedLiquidity[asset] += amount;

        IERC20(asset).safeTransfer(borrower, amount);
        emit SpokeBorrowed(msg.sender, asset, borrower, amount);
    }

    function executeRepay(address asset, uint256 amount) external {
        require(spokeBorrows[msg.sender][asset] >= amount, "Repay exceeds borrow");
        
        spokeBorrows[msg.sender][asset] -= amount;
        totalPoolLiquidity[asset] += amount;
        totalBorrowedLiquidity[asset] -= amount;

        IERC20(asset).safeTransferFrom(msg.sender, address(this), amount);
        emit SpokeRepaid(msg.sender, asset, amount);
    }

    // 修复警告:确保闪电贷结束时完全清空瞬态存储锁,解除可组合性隐患
    function flashLoan(address receiver, address asset, uint256 amount, bytes calldata params) external nonReentrant {
        require(totalPoolLiquidity[asset] >= amount, "Low liquidity");
        
        bytes32 slot = FLASH_LOAN_KEY;
        uint256 locked;
        assembly { locked := tload(slot) }
        require(locked == 0, "Flash loan reentrancy");
        
        assembly { tstore(slot, 1) }

        uint256 fee = (amount * FLASH_LOAN_FEE_BPS) / BPS_DIVISOR;
        uint256 balanceBefore = IERC20(asset).balanceOf(address(this));

        IERC20(asset).safeTransfer(receiver, amount);

        // 使用 bool 接收确保后续的锁清理无论如何都能执行
        bool success = IFlashLoanReceiver(receiver).executeOperation(asset, amount, fee, msg.sender, params);
        require(success, "Execution failed");

        uint256 balanceAfter = IERC20(asset).balanceOf(address(this));
        require(balanceAfter >= balanceBefore + fee, "Flash loan not repaid");

        totalPoolLiquidity[asset] += fee; 
        
        // 显式彻底释放并归还瞬态状态
        assembly { tstore(slot, 0) }
        emit FlashLoan(receiver, asset, amount, fee);
    }

    function getUtilizationRate(address asset) public view returns (uint256) {
        uint256 totalLiquidity = totalPoolLiquidity[asset] + totalBorrowedLiquidity[asset];
        if (totalLiquidity == 0) return 0;
        return (totalBorrowedLiquidity[asset] * 1e18) / totalLiquidity;
    }
}

2. AaveV4Spoke.sol —— 利率模型与智能清算

// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

interface IAaveV4Hub {
    function executeBorrow(address asset, address borrower, uint256 amount) external;
    function executeRepay(address asset, uint256 amount) external;
    function getUtilizationRate(address asset) external view returns (uint256);
}

contract AaveV4Spoke {
    using SafeERC20 for IERC20; // 引入 SafeERC20 库

    IAaveV4Hub public immutable hub;
    
    mapping(address => mapping(address => uint256)) public userCollateral;
    mapping(address => mapping(address => uint256)) public userDebts;

    uint256 public constant LIQUIDATION_THRESHOLD = 130;
    uint256 public constant TARGET_HEALTH_FACTOR = 140;
    uint256 public constant LIQUIDATION_BONUS = 5;

    uint256 public constant OPTIMAL_UTILIZATION = 0.8 * 1e18; 
    uint256 public constant BASE_BORROW_RATE = 0.02 * 1e18;   
    uint256 public constant SLOPE1 = 0.04 * 1e18;              
    uint256 public constant SLOPE2 = 0.75 * 1e18;              

    constructor(address _hub) {
        hub = IAaveV4Hub(_hub);
    }

    function getBorrowRate(address asset) public view returns (uint256) {
        uint256 util = hub.getUtilizationRate(asset);
        if (util < OPTIMAL_UTILIZATION) {
            return BASE_BORROW_RATE + (util * SLOPE1) / OPTIMAL_UTILIZATION;
        } else {
            uint256 excessUtil = util - OPTIMAL_UTILIZATION;
            uint256 excessUtilRange = 1e18 - OPTIMAL_UTILIZATION;
            return BASE_BORROW_RATE + SLOPE1 + (excessUtil * SLOPE2) / excessUtilRange;
        }
    }

    function addCollateral(address asset, uint256 amount) external {
        userCollateral[msg.sender][asset] += amount;
        IERC20(asset).safeTransferFrom(msg.sender, address(this), amount);
    }

    function borrow(
        address collateralAsset, 
        uint256 collateralPrice, 
        address borrowAsset, 
        uint256 borrowPrice, 
        uint256 borrowAmount
    ) external {
        userDebts[msg.sender][borrowAsset] += borrowAmount;
        
        uint256 collateralValue = userCollateral[msg.sender][collateralAsset] * collateralPrice;
        uint256 debtValue = userDebts[msg.sender][borrowAsset] * borrowPrice;
        require(collateralValue * 100 >= debtValue * LIQUIDATION_THRESHOLD, "Below liquidation threshold");

        hub.executeBorrow(borrowAsset, msg.sender, borrowAmount);
    }

    function liquidate(
        address borrower,
        address collateralAsset,
        uint256 collateralPrice,
        address borrowAsset,
        uint256 borrowPrice
    ) external {
        uint256 collateralValue = userCollateral[borrower][collateralAsset] * collateralPrice;
        uint256 debtValue = userDebts[borrower][borrowAsset] * borrowPrice;

        require(collateralValue * 100 < debtValue * LIQUIDATION_THRESHOLD, "Borrower is healthy");

        uint256 numerator = (debtValue * TARGET_HEALTH_FACTOR) - (collateralValue * 100);
        uint256 denominator = (TARGET_HEALTH_FACTOR) - (100 + LIQUIDATION_BONUS);
        uint256 maxRepayValue = numerator / denominator;

        if (maxRepayValue > debtValue) {
            maxRepayValue = debtValue;
        }

        uint256 debtToRepay = maxRepayValue / borrowPrice;
        require(debtToRepay > 0, "Repay amount too small");

        uint256 collateralValueToSeize = (maxRepayValue * (100 + LIQUIDATION_BONUS)) / 100;
        uint256 collateralToSeize = collateralValueToSeize / collateralPrice;

        userDebts[borrower][borrowAsset] -= debtToRepay;
        userCollateral[borrower][collateralAsset] -= collateralToSeize;

        IERC20(borrowAsset).safeTransferFrom(msg.sender, address(this), debtToRepay);
        
        // 🔧 修复核心编译错误:使用 OpenZeppelin 5.x 推荐的 forceApprove
        IERC20(borrowAsset).forceApprove(address(hub), debtToRepay);
        
        hub.executeRepay(borrowAsset, debtToRepay);

        IERC20(collateralAsset).safeTransfer(msg.sender, collateralToSeize);
    }
}

3.MockFlashLoanReceiver.sol —— mock闪电贷接收者

// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

interface IFlashLoanReceiver {
    function executeOperation(
        address asset,
        uint256 amount,
        uint256 fee,
        address initiator,
        bytes calldata params
    ) external returns (bool);
}

interface IHubFlashLoan {
    function flashLoan(address receiver, address asset, uint256 amount, bytes calldata params) external;
}

contract MockFlashLoanReceiver is IFlashLoanReceiver {
    using SafeERC20 for IERC20;

    bool public attemptReentrancy = false;
    address public targetHub;

    function setReentrancyTest(bool _enable, address _hub) external {
        attemptReentrancy = _enable;
        targetHub = _hub;
    }

    function executeOperation(
        address asset,
        uint256 amount,
        uint256 fee,
        address /* initiator */,
        bytes calldata /* params */
    ) external override returns (bool) {
        uint256 totalRepayment = amount + fee;

        if (attemptReentrancy && targetHub != address(0)) {
            IHubFlashLoan(targetHub).flashLoan(address(this), asset, amount, "");
        }

        IERC20(asset).safeTransfer(msg.sender, totalRepayment);
        return true;
    }
}

五、 自动化集成测试与验证

以下是针对动态利率拐点与智能局部清算的关键测试代码:

  • 测试用例: Aave V4 Hub & Spoke Full Integration Test Suite
    • 1. 存款与提现:Hub 应正确管理闲置流动性
    • 2. 信用额度与借贷:Spoke 验证额度并安全调用 Hub 借款和还款
    • 3. 深度利率计算:当资金利用率跨越拐点(Optimal Utilization)时,应触发高斜率动态利率
    • 4. 深度清算机制:清算时应精确执行 Target Health Factor 局部智能疗伤清算(部分清算,而非全部归零)
    • 5. 闪电贷安全:EIP-1153 瞬态存储加锁与防重入验证
    • 6. 闪电贷安全:正常归还通过,重入攻击应被拦截
import assert from "node:assert/strict";
import { describe, it } from "node:test";
import { parseEther } from "viem";
import { network } from "hardhat";

describe("Aave V4 Hub & Spoke Full Integration Test Suite", function () {
  async function deployFixture() {
    const conn = await network.create();
    const viem = conn.viem;
    
    const [owner, user1, liquidator] = await viem.getWalletClients();
    const publicClient = await viem.getPublicClient();

    const mockToken = await viem.deployContract("BoykaYuriToken", [
      owner.account.address,
      owner.account.address,
    ]);

    const hub = await viem.deployContract("AaveV4Hub");
    const spoke = await viem.deployContract("AaveV4Spoke", [hub.address]);
    const flashReceiver = await viem.deployContract("MockFlashLoanReceiver");

    return {
      viem,
      hub,
      spoke,
      mockToken,
      flashReceiver,
      owner,
      user1,
      liquidator,
      publicClient,
    };
  }

  it("1. 存款与提现:Hub 应正确管理闲置流动性", async function () {
    const { viem, hub, mockToken, user1 } = await deployFixture();
    const depositAmount = parseEther("1000");

    await mockToken.write.transfer([user1.account.address, depositAmount]);
    
    const mockTokenAsUser1 = await viem.getContractAt("BoykaYuriToken", mockToken.address, { client: { wallet: user1 } });
    await mockTokenAsUser1.write.approve([hub.address, depositAmount]);

    const hubAsUser1 = await viem.getContractAt("AaveV4Hub", hub.address, { client: { wallet: user1 } });
    await hubAsUser1.write.deposit([mockToken.address, depositAmount]);

    const poolLiquidity = await hub.read.totalPoolLiquidity([mockToken.address]);
    assert.equal(poolLiquidity, depositAmount, "Hub 闲置流动性应等于存款金额");

    const withdrawAmount = parseEther("400");
    await hubAsUser1.write.withdraw([mockToken.address, withdrawAmount]);

    const remainingLiquidity = await hub.read.totalPoolLiquidity([mockToken.address]);
    assert.equal(remainingLiquidity, parseEther("600"), "提现后剩余流动性应正确");
  });

  it("2. 信用额度与借贷:Spoke 验证额度并安全调用 Hub 借款和还款", async function () {
    const { viem, hub, spoke, mockToken, user1 } = await deployFixture();
    const depositAmount = parseEther("5000");
    const borrowAmount = parseEther("1000");

    await mockToken.write.approve([hub.address, depositAmount]);
    await hub.write.deposit([mockToken.address, depositAmount]);
    await hub.write.setSpokeLimit([spoke.address, mockToken.address, parseEther("10000")]);

    await mockToken.write.transfer([user1.account.address, depositAmount]);
    const tokenAsUser1 = await viem.getContractAt("BoykaYuriToken", mockToken.address, { client: { wallet: user1 } });
    await tokenAsUser1.write.approve([spoke.address, depositAmount]);

    const spokeAsUser1 = await viem.getContractAt("AaveV4Spoke", spoke.address, { client: { wallet: user1 } });
    await spokeAsUser1.write.addCollateral([mockToken.address, depositAmount]);

    await spokeAsUser1.write.borrow([
      mockToken.address,
      1n, 
      mockToken.address,
      1n, 
      borrowAmount,
    ]);

    const userDebt = await spokeAsUser1.read.userDebts([user1.account.address, mockToken.address]);
    assert.equal(userDebt, borrowAmount, "用户借款债务应正确记录");
  });

  // ==========================================================================
  // 深度测试 3:利率模型分段动态测试(利用率超过拐点后斜率飙升)
  // ==========================================================================
  it("3. 深度利率计算:当资金利用率跨越拐点(Optimal Utilization)时,应触发高斜率动态利率", async function () {
    const { viem, hub, spoke, mockToken, user1 } = await deployFixture();
    
    // 1. 存入 1000 代币作为流动性池
    const poolDeposit = parseEther("1000");
    await mockToken.write.approve([hub.address, poolDeposit]);
    await hub.write.deposit([mockToken.address, poolDeposit]);
    await hub.write.setSpokeLimit([spoke.address, mockToken.address, parseEther("10000")]);

    // 记录初始零利用率下的基础利率
    const initialRate = await spoke.read.getBorrowRate([mockToken.address]);
    assert.equal(initialRate, parseEther("0.02"), "初始利用率为0时利率应为基础费率 2%");

    // 2. 用户存入抵押品并大量借款(例如借出 850 代币,使利用率达到 85%,跨越通常的 80% 拐点)
    const collateralAmount = parseEther("2000");
    const highBorrowAmount = parseEther("850");

    await mockToken.write.transfer([user1.account.address, collateralAmount]);
    const tokenAsUser1 = await viem.getContractAt("BoykaYuriToken", mockToken.address, { client: { wallet: user1 } });
    await tokenAsUser1.write.approve([spoke.address, collateralAmount]);

    const spokeAsUser1 = await viem.getContractAt("AaveV4Spoke", spoke.address, { client: { wallet: user1 } });
    await spokeAsUser1.write.addCollateral([mockToken.address, collateralAmount]);
    
    // 执行高比例借款
    await spokeAsUser1.write.borrow([mockToken.address, 1n, mockToken.address, 1n, highBorrowAmount]);

    // 3. 验证高利用率下的借款利率
    const highUtilizationRate = await spoke.read.getBorrowRate([mockToken.address]);
    
    // 跨越拐点后,利率应当显著高于初始基础费率(触发 Slope 2 动态飙升)
    assert.ok(
      highUtilizationRate > initialRate,
      "当资金利用率超过拐点时,借款利率应当由于触发高斜率模型而显著上升"
    );
  });

  // ==========================================================================
  // 深度测试 4:Target Health Factor 局部智能疗伤清算测试
  // ==========================================================================
  it("4. 深度清算机制:清算时应精确执行 Target Health Factor 局部智能疗伤清算(部分清算,而非全部归零)", async function () {
    const { viem, hub, spoke, mockToken, user1, liquidator } = await deployFixture();
    const depositAmount = parseEther("1000");
    const borrowAmount = parseEther("750"); // 高杠杆借款,靠近清算红线

    await mockToken.write.approve([hub.address, parseEther("10000")]);
    await hub.write.deposit([mockToken.address, parseEther("10000")]);
    await hub.write.setSpokeLimit([spoke.address, mockToken.address, parseEther("10000")]);

    await mockToken.write.transfer([user1.account.address, depositAmount]);
    const tokenAsUser1 = await viem.getContractAt("BoykaYuriToken", mockToken.address, { client: { wallet: user1 } });
    await tokenAsUser1.write.approve([spoke.address, depositAmount]);
    
    const spokeAsUser1 = await viem.getContractAt("AaveV4Spoke", spoke.address, { client: { wallet: user1 } });
    await spokeAsUser1.write.addCollateral([mockToken.address, depositAmount]);
    await spokeAsUser1.write.borrow([mockToken.address, 1n, mockToken.address, 1n, borrowAmount]);

    const tokenAsLiquidator = await viem.getContractAt("BoykaYuriToken", mockToken.address, { client: { wallet: liquidator } });
    await mockToken.write.transfer([liquidator.account.address, borrowAmount]);
    
    const spokeAsLiquidator = await viem.getContractAt("AaveV4Spoke", spoke.address, { client: { wallet: liquidator } });
    await tokenAsLiquidator.write.approve([spoke.address, borrowAmount]);

    // 触发清算(通过调整价格使健康因子跌破阈值)
    await spokeAsLiquidator.write.liquidate([
      user1.account.address,
      mockToken.address,
      9n, // 抵押品价格下跌
      mockToken.address,
      10n, // 债务价格上涨
    ]);

    const remainingDebt = await spoke.read.userDebts([user1.account.address, mockToken.address]);
    
    // 核心断言:
    // 1. 债务确实减少了 (`remainingDebt < borrowAmount`)
    // 2. 属于“局部疗伤清算”:清算后用户仍然保留了部分债务(`remainingDebt > 0n`),
    //    协议没有选择简单粗暴地把用户债务全额清零(Full Liquidation),而是精确恢复到 Target Health Factor。
    assert.ok(remainingDebt < borrowAmount, "清算后用户的债务应当减少");
    assert.ok(remainingDebt > 0n, "Target Health Factor 局部清算应为部分清算,用户应保留剩余债务");
  });

  it("5. 闪电贷安全:EIP-1153 瞬态存储加锁与防重入验证", async function () {
    const { hub, mockToken, flashReceiver } = await deployFixture();
    const loanAmount = parseEther("500");

    await mockToken.write.approve([hub.address, parseEther("2000")]);
    await hub.write.deposit([mockToken.address, parseEther("2000")]);

    const fee = (loanAmount * 9n) / 10000n;
    await mockToken.write.transfer([flashReceiver.address, fee + parseEther("10")]);

    const tx = await hub.write.flashLoan([
      flashReceiver.address,
      mockToken.address,
      loanAmount,
      "0x"
    ]);

    assert.ok(tx, "正常闪电贷应当顺利执行并清空瞬态存储锁");
  });

  it("6. 闪电贷安全:正常归还通过,重入攻击应被拦截", async function () {
    const { viem, hub, mockToken, flashReceiver } = await deployFixture();
    const loanAmount = parseEther("500");

    await mockToken.write.approve([hub.address, parseEther("2000")]);
    await hub.write.deposit([mockToken.address, parseEther("2000")]);

    const fee = (loanAmount * 9n) / 10000n;
    await mockToken.write.transfer([flashReceiver.address, fee + parseEther("50")]);

    const receiverContract = await viem.getContractAt("MockFlashLoanReceiver", flashReceiver.address);
    await receiverContract.write.setReentrancyTest([true, hub.address]);

    let errorCaught = false;
    try {
      await hub.write.flashLoan([
        flashReceiver.address,
        mockToken.address,
        loanAmount,
        "0x"
      ]);
    } catch (err: any) {
      errorCaught = true;
    }
    assert.equal(errorCaught, true, "瞬态存储锁应当成功拦截重入攻击并引发回滚");
  });
});

六、 总结

通过采用 Hub-Spoke 架构、EIP-1153 瞬态存储防重入 以及 目标健康因子(Target Health Factor)局部清算机制,Aave V4 在保障系统资金安全的同时,极大地提升了 Gas 效率和用户的清算体验。