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-periphery1.2 合约地址
以太坊主网:
| 合约 | 地址 |
|---|---|
| Pool | 0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2 |
| PoolAddressesProvider | 0x2f39d218133AFaB8F2B819B1066c7E434Ad94E9e |
| PoolDataProvider | 0x7B4EB56E7CD4b454BA8ff71E4518426369a138a3 |
| Oracle | 0x54586bE62E3c3580375aE3723C145253060Ca0C2 |
| WETHGateway | 0x893411580e590D62dDBca8a703d61Cc4A8c7b2b9 |
Polygon:
| 合约 | 地址 |
|---|---|
| Pool | 0x794a61358D6845594F94dc1DB02A252b5b4814aD |
| PoolAddressesProvider | 0xa97684ead0e402dC232d5A977953DF7ECBaB3CDb |
Arbitrum:
| 合约 | 地址 |
|---|---|
| Pool | 0x794a61358D6845594F94dc1DB02A252b5b4814aD |
| PoolAddressesProvider | 0xa97684ead0e402dC232d5A977953DF7ECBaB3CDb |
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. 常见错误处理
| 错误码 | 含义 | 解决方案 |
|---|---|---|
1 | Invalid amount | 检查金额 > 0 |
27 | Reserve frozen | 储备已冻结,无法操作 |
28 | Reserve paused | 储备已暂停 |
35 | Health factor lower than threshold | 增加抵押品或减少借贷 |
36 | Collateral cannot cover new borrow | 抵押品不足 |
38 | Borrow cap exceeded | 借贷上限已满 |
39 | Supply cap exceeded | 存款上限已满 |
9. 小结
集成检查清单:
- 正确导入接口和库
- 使用 SafeERC20 进行代币操作
- 添加输入验证和权限检查
- 实现重入保护
- 添加健康因子检查
- 编写全面的测试
- 在测试网验证后再部署主网
推荐开发流程:
- 在 Foundry 中 fork 主网进行本地测试
- 部署到测试网(Sepolia/Goerli)验证
- 审计合约代码
- 部署到主网
祝您开发顺利!