Aave V3 开发者集成指南

1. 开发环境搭建

1.1 安装依赖

# 使用 npm
npm install @aave/core-v3 @aave/periphery-v3 @aave/safety-module-v3
 
# 使用 yarn
yarn add @aave/core-v3 @aave/periphery-v3 @aave/safety-module-v3
 
# 使用 forge
forge install aave/aave-v3-core aave/aave-v3-periphery

1.2 合约地址

以太坊主网

合约地址
Pool0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2
PoolAddressesProvider0x2f39d218133AFaB8F2B819B1066c7E434Ad94E9e
PoolDataProvider0x7B4EB56E7CD4b454BA8ff71E4518426369a138a3
Oracle0x54586bE62E3c3580375aE3723C145253060Ca0C2
WETHGateway0x893411580e590D62dDBca8a703d61Cc4A8c7b2b9

Polygon

合约地址
Pool0x794a61358D6845594F94dc1DB02A252b5b4814aD
PoolAddressesProvider0xa97684ead0e402dC232d5A977953DF7ECBaB3CDb

Arbitrum

合约地址
Pool0x794a61358D6845594F94dc1DB02A252b5b4814aD
PoolAddressesProvider0xa97684ead0e402dC232d5A977953DF7ECBaB3CDb

1.3 接口导入

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;
 
import {IPool} from "@aave/core-v3/contracts/interfaces/IPool.sol";
import {IPoolAddressesProvider} from "@aave/core-v3/contracts/interfaces/IPoolAddressesProvider.sol";
import {IPoolDataProvider} from "@aave/core-v3/contracts/interfaces/IPoolDataProvider.sol";
import {IAToken} from "@aave/core-v3/contracts/interfaces/IAToken.sol";
import {IVariableDebtToken} from "@aave/core-v3/contracts/interfaces/IVariableDebtToken.sol";
import {IStableDebtToken} from "@aave/core-v3/contracts/interfaces/IStableDebtToken.sol";
import {IPriceOracle} from "@aave/core-v3/contracts/interfaces/IPriceOracle.sol";
import {DataTypes} from "@aave/core-v3/contracts/protocol/libraries/types/DataTypes.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

2. 基础集成

2.1 存款

contract AaveSupply {
    using SafeERC20 for IERC20;
 
    IPool public immutable pool;
 
    constructor(address _pool) {
        pool = IPool(_pool);
    }
 
    /**
     * @notice 存入资产到 Aave
     * @param asset 资产地址
     * @param amount 存款金额
     * @param onBehalfOf 接收 aToken 的地址
     */
    function supply(
        address asset,
        uint256 amount,
        address onBehalfOf
    ) external {
        // 1. 从用户转移资产
        IERC20(asset).safeTransferFrom(msg.sender, address(this), amount);
 
        // 2. 授权 Pool 使用资产
        IERC20(asset).safeApprove(address(pool), amount);
 
        // 3. 执行存款
        pool.supply(asset, amount, onBehalfOf, 0);
    }
 
    /**
     * @notice 存入 ETH
     */
    function supplyETH(address onBehalfOf) external payable {
        // 使用 WETHGateway
        IWETHGateway gateway = IWETHGateway(WETH_GATEWAY);
        gateway.depositETH{value: msg.value}(address(pool), onBehalfOf, 0);
    }
}

2.2 借贷

contract AaveBorrow {
    using SafeERC20 for IERC20;
 
    IPool public immutable pool;
 
    /**
     * @notice 借出资产
     * @param asset 借贷资产地址
     * @param amount 借贷金额
     * @param interestRateMode 1=固定利率, 2=浮动利率
     */
    function borrow(
        address asset,
        uint256 amount,
        uint256 interestRateMode
    ) external {
        // 确保用户有足够抵押品
        (
            uint256 totalCollateralBase,
            uint256 totalDebtBase,
            uint256 availableBorrowsBase,
            ,
            ,
            uint256 healthFactor
        ) = pool.getUserAccountData(msg.sender);
 
        require(availableBorrowsBase > 0, "No borrowing power");
 
        // 执行借贷
        pool.borrow(asset, amount, interestRateMode, 0, msg.sender);
 
        // 转移借出的资产给用户
        IERC20(asset).safeTransfer(msg.sender, amount);
    }
 
    /**
     * @notice 使用信用委托借贷
     */
    function borrowWithDelegation(
        address asset,
        uint256 amount,
        uint256 interestRateMode,
        address delegator
    ) external {
        // delegator 必须已经授权 msg.sender 借贷额度
        pool.borrow(asset, amount, interestRateMode, 0, delegator);
        IERC20(asset).safeTransfer(msg.sender, amount);
    }
}

2.3 还款

contract AaveRepay {
    using SafeERC20 for IERC20;
 
    IPool public immutable pool;
 
    /**
     * @notice 还款
     * @param asset 债务资产地址
     * @param amount 还款金额,type(uint256).max 表示全额还款
     * @param interestRateMode 1=固定利率, 2=浮动利率
     */
    function repay(
        address asset,
        uint256 amount,
        uint256 interestRateMode
    ) external returns (uint256) {
        // 转移资产到合约
        IERC20(asset).safeTransferFrom(msg.sender, address(this), amount);
 
        // 授权 Pool
        IERC20(asset).safeApprove(address(pool), amount);
 
        // 执行还款
        uint256 repaidAmount = pool.repay(asset, amount, interestRateMode, msg.sender);
 
        // 退还多余资产
        uint256 remaining = amount - repaidAmount;
        if (remaining > 0) {
            IERC20(asset).safeTransfer(msg.sender, remaining);
        }
 
        return repaidAmount;
    }
 
    /**
     * @notice 使用 aToken 还款
     */
    function repayWithATokens(
        address asset,
        uint256 amount,
        uint256 interestRateMode
    ) external returns (uint256) {
        // 不需要转移资产,直接使用用户的 aToken
        return pool.repayWithATokens(asset, amount, interestRateMode);
    }
}

2.4 取款

contract AaveWithdraw {
    using SafeERC20 for IERC20;
 
    IPool public immutable pool;
 
    /**
     * @notice 取款
     * @param asset 资产地址
     * @param amount 取款金额,type(uint256).max 表示全额取款
     */
    function withdraw(
        address asset,
        uint256 amount
    ) external returns (uint256) {
        return pool.withdraw(asset, amount, msg.sender);
    }
 
    /**
     * @notice 安全取款(检查健康因子)
     */
    function safeWithdraw(
        address asset,
        uint256 amount,
        uint256 minHealthFactor
    ) external returns (uint256) {
        // 检查取款后的健康因子
        (
            uint256 totalCollateralBase,
            uint256 totalDebtBase,
            ,
            uint256 currentLiquidationThreshold,
            ,
 
        ) = pool.getUserAccountData(msg.sender);
 
        // 获取资产价格
        address oracle = pool.getAddresses().getPriceOracle();
        uint256 assetPrice = IPriceOracle(oracle).getAssetPrice(asset);
 
        // 计算取款后的健康因子
        uint256 withdrawValue = amount * assetPrice / 1e18;
        uint256 newCollateral = totalCollateralBase - withdrawValue;
        uint256 newHealthFactor = (newCollateral * currentLiquidationThreshold / 10000) * 1e18 / totalDebtBase;
 
        require(newHealthFactor >= minHealthFactor, "Health factor too low");
 
        return pool.withdraw(asset, amount, msg.sender);
    }
}

3. 高级集成

3.1 闪电贷集成

import {IFlashLoanSimpleReceiver} from "@aave/core-v3/contracts/flashloan/base/FlashLoanSimpleReceiverBase.sol";
 
contract MyFlashLoanReceiver is IFlashLoanSimpleReceiver {
    IPool public immutable POOL;
    IPoolAddressesProvider public immutable ADDRESSES_PROVIDER;
 
    constructor(address provider) {
        ADDRESSES_PROVIDER = IPoolAddressesProvider(provider);
        POOL = IPool(ADDRESSES_PROVIDER.getPool());
    }
 
    /**
     * @notice 发起闪电贷
     */
    function requestFlashLoan(
        address asset,
        uint256 amount,
        bytes calldata params
    ) external {
        POOL.flashLoanSimple(
            address(this),
            asset,
            amount,
            params,
            0  // referralCode
        );
    }
 
    /**
     * @notice 闪电贷回调
     */
    function executeOperation(
        address asset,
        uint256 amount,
        uint256 premium,
        address initiator,
        bytes calldata params
    ) external override returns (bool) {
        require(msg.sender == address(POOL), "Invalid caller");
        require(initiator == address(this), "Invalid initiator");
 
        // ============================================
        // 在这里执行自定义逻辑
        // 例如:套利、清算、再融资等
        // ============================================
 
        // 授权归还
        uint256 amountOwed = amount + premium;
        IERC20(asset).approve(address(POOL), amountOwed);
 
        return true;
    }
 
    function ADDRESSES_PROVIDER() external view returns (IPoolAddressesProvider) {
        return ADDRESSES_PROVIDER;
    }
 
    function POOL() external view returns (IPool) {
        return POOL;
    }
}

3.2 清算集成

contract LiquidationHelper {
    IPool public immutable pool;
    IPoolDataProvider public immutable dataProvider;
 
    /**
     * @notice 检查用户是否可清算
     */
    function checkLiquidation(address user) external view returns (
        bool canLiquidate,
        uint256 healthFactor,
        uint256 totalDebt
    ) {
        (
            ,
            uint256 _totalDebt,
            ,
            ,
            ,
            uint256 _healthFactor
        ) = pool.getUserAccountData(user);
 
        healthFactor = _healthFactor;
        totalDebt = _totalDebt;
        canLiquidate = _healthFactor < 1e18;
    }
 
    /**
     * @notice 获取用户债务详情
     */
    function getUserDebts(address user, address[] calldata reserves)
        external view returns (
            address[] memory debtAssets,
            uint256[] memory stableDebts,
            uint256[] memory variableDebts
        )
    {
        uint256 length = reserves.length;
        debtAssets = new address[](length);
        stableDebts = new uint256[](length);
        variableDebts = new uint256[](length);
 
        for (uint256 i = 0; i < length; i++) {
            (
                ,
                uint256 stableDebt,
                uint256 variableDebt,
                ,
                ,
                ,
                ,
                ,
 
            ) = dataProvider.getUserReserveData(reserves[i], user);
 
            if (stableDebt > 0 || variableDebt > 0) {
                debtAssets[i] = reserves[i];
                stableDebts[i] = stableDebt;
                variableDebts[i] = variableDebt;
            }
        }
    }
 
    /**
     * @notice 执行清算
     */
    function liquidate(
        address collateralAsset,
        address debtAsset,
        address user,
        uint256 debtToCover,
        bool receiveAToken
    ) external {
        IERC20(debtAsset).safeTransferFrom(msg.sender, address(this), debtToCover);
        IERC20(debtAsset).safeApprove(address(pool), debtToCover);
 
        pool.liquidationCall(
            collateralAsset,
            debtAsset,
            user,
            debtToCover,
            receiveAToken
        );
 
        // 转移获得的抵押品给调用者
        if (receiveAToken) {
            address aToken = pool.getReserveData(collateralAsset).aTokenAddress;
            uint256 aTokenBalance = IERC20(aToken).balanceOf(address(this));
            IERC20(aToken).safeTransfer(msg.sender, aTokenBalance);
        } else {
            uint256 collateralBalance = IERC20(collateralAsset).balanceOf(address(this));
            IERC20(collateralAsset).safeTransfer(msg.sender, collateralBalance);
        }
    }
}

3.3 E-Mode 集成

contract EModeHelper {
    IPool public immutable pool;
 
    /**
     * @notice 设置用户 E-Mode
     * @param categoryId 0=禁用, 1=稳定币, 2=ETH相关, 等
     */
    function setUserEMode(uint8 categoryId) external {
        pool.setUserEMode(categoryId);
    }
 
    /**
     * @notice 获取 E-Mode 类别信息
     */
    function getEModeCategory(uint8 categoryId) external view returns (
        uint16 ltv,
        uint16 liquidationThreshold,
        uint16 liquidationBonus,
        address priceSource,
        string memory label
    ) {
        DataTypes.EModeCategory memory category = pool.getEModeCategoryData(categoryId);
        return (
            category.ltv,
            category.liquidationThreshold,
            category.liquidationBonus,
            category.priceSource,
            category.label
        );
    }
 
    /**
     * @notice 检查用户是否可以进入指定 E-Mode
     */
    function canEnterEMode(address user, uint8 categoryId) external view returns (bool) {
        // 检查用户所有抵押品和债务是否都属于该类别
        // 实际实现需要遍历用户资产
        return true; // 简化示例
    }
}

4. 数据查询

4.1 用户数据查询

contract AaveDataReader {
    IPool public immutable pool;
    IPoolDataProvider public immutable dataProvider;
    IPriceOracle public immutable oracle;
 
    /**
     * @notice 获取用户账户概览
     */
    function getUserAccountData(address user) external view returns (
        uint256 totalCollateralBase,
        uint256 totalDebtBase,
        uint256 availableBorrowsBase,
        uint256 currentLiquidationThreshold,
        uint256 ltv,
        uint256 healthFactor
    ) {
        return pool.getUserAccountData(user);
    }
 
    /**
     * @notice 获取用户特定储备数据
     */
    function getUserReserveData(address asset, address user) external view returns (
        uint256 currentATokenBalance,
        uint256 currentStableDebt,
        uint256 currentVariableDebt,
        uint256 principalStableDebt,
        uint256 scaledVariableDebt,
        uint256 stableBorrowRate,
        uint256 liquidityRate,
        uint40 stableRateLastUpdated,
        bool usageAsCollateralEnabled
    ) {
        return dataProvider.getUserReserveData(asset, user);
    }
 
    /**
     * @notice 获取储备数据
     */
    function getReserveData(address asset) external view returns (
        uint256 availableLiquidity,
        uint256 totalStableDebt,
        uint256 totalVariableDebt,
        uint256 liquidityRate,
        uint256 variableBorrowRate,
        uint256 stableBorrowRate,
        uint256 averageStableBorrowRate,
        uint256 liquidityIndex,
        uint256 variableBorrowIndex
    ) {
        return dataProvider.getReserveData(asset);
    }
 
    /**
     * @notice 获取资产价格
     */
    function getAssetPrice(address asset) external view returns (uint256) {
        return oracle.getAssetPrice(asset);
    }
 
    /**
     * @notice 计算用户最大可借金额
     */
    function getMaxBorrow(address user, address asset) external view returns (uint256) {
        (
            ,
            ,
            uint256 availableBorrowsBase,
            ,
            ,
 
        ) = pool.getUserAccountData(user);
 
        uint256 assetPrice = oracle.getAssetPrice(asset);
        uint8 decimals = dataProvider.getReserveConfigurationData(asset).decimals;
 
        return (availableBorrowsBase * (10 ** decimals)) / assetPrice;
    }
}

4.2 储备配置查询

contract ReserveConfigReader {
    IPoolDataProvider public immutable dataProvider;
 
    /**
     * @notice 获取储备配置
     */
    function getReserveConfig(address asset) external view returns (
        uint256 ltv,
        uint256 liquidationThreshold,
        uint256 liquidationBonus,
        uint256 reserveFactor,
        bool usageAsCollateralEnabled,
        bool borrowingEnabled,
        bool stableBorrowingEnabled,
        bool isActive,
        bool isFrozen
    ) {
        (
            uint256 decimals,
            uint256 _ltv,
            uint256 _liquidationThreshold,
            uint256 _liquidationBonus,
            uint256 _reserveFactor,
            bool _usageAsCollateral,
            bool _borrowing,
            bool _stableBorrowing,
            bool _active,
            bool _frozen
        ) = dataProvider.getReserveConfigurationData(asset);
 
        return (
            _ltv,
            _liquidationThreshold,
            _liquidationBonus,
            _reserveFactor,
            _usageAsCollateral,
            _borrowing,
            _stableBorrowing,
            _active,
            _frozen
        );
    }
 
    /**
     * @notice 获取储备上限
     */
    function getReserveCaps(address asset) external view returns (
        uint256 borrowCap,
        uint256 supplyCap
    ) {
        return dataProvider.getReserveCaps(asset);
    }
 
    /**
     * @notice 获取储备代币地址
     */
    function getReserveTokens(address asset) external view returns (
        address aTokenAddress,
        address stableDebtTokenAddress,
        address variableDebtTokenAddress
    ) {
        return dataProvider.getReserveTokensAddresses(asset);
    }
}

5. 安全最佳实践

5.1 输入验证

contract SafeAaveIntegration {
    using SafeERC20 for IERC20;
 
    IPool public immutable pool;
 
    // 最小健康因子阈值
    uint256 public constant MIN_HEALTH_FACTOR = 1.05e18; // 1.05
 
    modifier validAmount(uint256 amount) {
        require(amount > 0, "Invalid amount");
        _;
    }
 
    modifier validAddress(address addr) {
        require(addr != address(0), "Invalid address");
        _;
    }
 
    modifier healthFactorAboveMin(address user) {
        (
            ,
            ,
            ,
            ,
            ,
            uint256 healthFactor
        ) = pool.getUserAccountData(user);
 
        require(
            healthFactor >= MIN_HEALTH_FACTOR || healthFactor == type(uint256).max,
            "Health factor too low"
        );
        _;
    }
 
    function safeBorrow(
        address asset,
        uint256 amount
    ) external validAddress(asset) validAmount(amount) healthFactorAboveMin(msg.sender) {
        pool.borrow(asset, amount, 2, 0, msg.sender);
    }
}

5.2 重入保护

import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol";
 
contract ReentrancyProtectedAave is ReentrancyGuard {
    IPool public immutable pool;
 
    function supplyAndBorrow(
        address supplyAsset,
        uint256 supplyAmount,
        address borrowAsset,
        uint256 borrowAmount
    ) external nonReentrant {
        // 存款
        IERC20(supplyAsset).safeTransferFrom(msg.sender, address(this), supplyAmount);
        IERC20(supplyAsset).safeApprove(address(pool), supplyAmount);
        pool.supply(supplyAsset, supplyAmount, msg.sender, 0);
 
        // 借贷
        pool.borrow(borrowAsset, borrowAmount, 2, 0, msg.sender);
    }
}

5.3 价格检查

contract PriceProtectedAave {
    IPool public immutable pool;
    IPriceOracle public immutable oracle;
 
    // 最大价格偏差 (5%)
    uint256 public constant MAX_PRICE_DEVIATION = 500;
 
    mapping(address => uint256) public lastKnownPrices;
 
    /**
     * @notice 检查价格是否在合理范围内
     */
    function isPriceValid(address asset) public view returns (bool) {
        uint256 currentPrice = oracle.getAssetPrice(asset);
        uint256 lastPrice = lastKnownPrices[asset];
 
        if (lastPrice == 0) return true;
 
        uint256 deviation = currentPrice > lastPrice
            ? ((currentPrice - lastPrice) * 10000) / lastPrice
            : ((lastPrice - currentPrice) * 10000) / lastPrice;
 
        return deviation <= MAX_PRICE_DEVIATION;
    }
 
    function safeSupply(address asset, uint256 amount) external {
        require(isPriceValid(asset), "Price deviation too high");
 
        IERC20(asset).safeTransferFrom(msg.sender, address(this), amount);
        IERC20(asset).safeApprove(address(pool), amount);
        pool.supply(asset, amount, msg.sender, 0);
 
        // 更新已知价格
        lastKnownPrices[asset] = oracle.getAssetPrice(asset);
    }
}

6. Gas 优化

6.1 批量操作

contract BatchAaveOperations {
    IPool public immutable pool;
 
    struct SupplyParams {
        address asset;
        uint256 amount;
    }
 
    struct BorrowParams {
        address asset;
        uint256 amount;
        uint256 interestRateMode;
    }
 
    /**
     * @notice 批量存款
     */
    function batchSupply(SupplyParams[] calldata params) external {
        for (uint256 i = 0; i < params.length; i++) {
            IERC20(params[i].asset).safeTransferFrom(
                msg.sender,
                address(this),
                params[i].amount
            );
            IERC20(params[i].asset).safeApprove(address(pool), params[i].amount);
            pool.supply(params[i].asset, params[i].amount, msg.sender, 0);
        }
    }
 
    /**
     * @notice 批量借贷
     */
    function batchBorrow(BorrowParams[] calldata params) external {
        for (uint256 i = 0; i < params.length; i++) {
            pool.borrow(
                params[i].asset,
                params[i].amount,
                params[i].interestRateMode,
                0,
                msg.sender
            );
        }
    }
}

6.2 使用 Permit

contract PermitAaveIntegration {
    IPool public immutable pool;
 
    /**
     * @notice 使用 Permit 授权后存款(节省一次交易)
     */
    function supplyWithPermit(
        address asset,
        uint256 amount,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external {
        // EIP-2612 Permit
        IERC20Permit(asset).permit(
            msg.sender,
            address(this),
            amount,
            deadline,
            v,
            r,
            s
        );
 
        IERC20(asset).safeTransferFrom(msg.sender, address(this), amount);
        IERC20(asset).safeApprove(address(pool), amount);
        pool.supply(asset, amount, msg.sender, 0);
    }
}

7. 测试

7.1 Foundry 测试示例

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;
 
import "forge-std/Test.sol";
import {IPool} from "@aave/core-v3/contracts/interfaces/IPool.sol";
 
contract AaveIntegrationTest is Test {
    IPool pool;
    address constant POOL_ADDRESS = 0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2;
    address constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
 
    address user = address(0x1234);
 
    function setUp() public {
        // Fork 主网
        vm.createSelectFork(vm.envString("ETH_RPC_URL"));
        pool = IPool(POOL_ADDRESS);
 
        // 给用户一些 USDC
        deal(USDC, user, 100000e6);
    }
 
    function testSupply() public {
        vm.startPrank(user);
 
        // 授权
        IERC20(USDC).approve(address(pool), 1000e6);
 
        // 存款
        pool.supply(USDC, 1000e6, user, 0);
 
        // 验证
        (uint256 totalCollateral, , , , , ) = pool.getUserAccountData(user);
        assertGt(totalCollateral, 0);
 
        vm.stopPrank();
    }
 
    function testBorrow() public {
        // 先存款
        testSupply();
 
        vm.startPrank(user);
 
        // 借贷
        pool.borrow(USDC, 500e6, 2, 0, user);
 
        // 验证
        (, uint256 totalDebt, , , , ) = pool.getUserAccountData(user);
        assertGt(totalDebt, 0);
 
        vm.stopPrank();
    }
 
    function testHealthFactor() public {
        testBorrow();
 
        (, , , , , uint256 healthFactor) = pool.getUserAccountData(user);
 
        // 健康因子应该大于 1
        assertGt(healthFactor, 1e18);
    }
}

8. 常见错误处理

错误码含义解决方案
1Invalid amount检查金额 > 0
27Reserve frozen储备已冻结,无法操作
28Reserve paused储备已暂停
35Health factor lower than threshold增加抵押品或减少借贷
36Collateral cannot cover new borrow抵押品不足
38Borrow cap exceeded借贷上限已满
39Supply cap exceeded存款上限已满

9. 小结

集成检查清单

  • 正确导入接口和库
  • 使用 SafeERC20 进行代币操作
  • 添加输入验证和权限检查
  • 实现重入保护
  • 添加健康因子检查
  • 编写全面的测试
  • 在测试网验证后再部署主网

推荐开发流程

  1. 在 Foundry 中 fork 主网进行本地测试
  2. 部署到测试网(Sepolia/Goerli)验证
  3. 审计合约代码
  4. 部署到主网

祝您开发顺利!