AAVE V1 源码阅读

1 架构鸟瞰(Bird’s-eye View)
图 4 提供了 Aave V1 在执行层与状态层之间的完整流向:用户与外部协议主要通过 LendingPool 交互,这个无状态的业务控制器只负责校验与流程调度;资金托管、计息、估值与状态存储都被剥离到 LendingPoolCore。为将白皮书中的抽象模型映射到源码,本章按照“入口 → 状态 → 策略”的顺序梳理模块职责与依赖关系,后续章节将沿这些箭头展开函数与事件的细节。
sequenceDiagram
User->>LendingPool: deposit/borrow/repay
LendingPool->>LendingPoolCore: updateState*()
LendingPoolCore-->>LendingPool: 累计索引与可用流动性
LendingPool->>AToken: mint/burn/transfer
AToken-->>User: 凭证余额变动
LendingPool->>DataProvider: global/account data
Governance->>AddressesProvider: 更新各组件地址
sequenceDiagram
User->>LendingPool: deposit/borrow/repay
LendingPool->>LendingPoolCore: updateState*()
LendingPoolCore-->>LendingPool: 累计索引与可用流动性
LendingPool->>AToken: mint/burn/transfer
AToken-->>User: 凭证余额变动
LendingPool->>DataProvider: global/account data
Governance->>AddressesProvider: 更新各组件地址
sequenceDiagram
User->>LendingPool: deposit/borrow/repay
LendingPool->>LendingPoolCore: updateState*()
LendingPoolCore-->>LendingPool: 累计索引与可用流动性
LendingPool->>AToken: mint/burn/transfer
AToken-->>User: 凭证余额变动
LendingPool->>DataProvider: global/account data
Governance->>AddressesProvider: 更新各组件地址sequenceDiagram
User->>LendingPool: deposit/borrow/repay
LendingPool->>LendingPoolCore: updateState*()
LendingPoolCore-->>LendingPool: 累计索引与可用流动性
LendingPool->>AToken: mint/burn/transfer
AToken-->>User: 凭证余额变动
LendingPool->>DataProvider: global/account data
Governance->>AddressesProvider: 更新各组件地址
1.1 用户入口与门面层
contracts/lendingpool/LendingPool.sol 对外暴露 deposit / borrow / repay / redeem / swap / flashLoan,内部遵循“权限验证 → 状态更新 → 资产划转”的三段式流程,各种修饰器(onlyActiveReserve、onlyOverlyingAToken 等)统一了安全边界。赎回场景中,用户往往直接与 AToken.redeem 交互,由 aToken 回调 LendingPool.redeemUnderlying 以销毁凭证并释放底层资产。LendingPoolLiquidationManager.sol 不是独立入口,而是通过 delegatecall 挂载到 LendingPool 上下文中执行清算逻辑,既复用了校验,又避免核心合约字节码过大。flashLoan 则在控制器内部完成计费与余额校验,再把控制权交给实现了 IFlashLoanReceiver 的外部合约;FlashLoanReceiverBase.sol 只是这些外部合约的开发模板,负责取核心地址和归还资金,并非协议自身的安全防线。所有依赖由 LendingPoolAddressesProvider 统一注册与注入,确保升级和组件寻址在同一入口完成。
sequenceDiagram
User->>LendingPool: 调用入口函数
LendingPool->>AddressesProvider: 查询依赖 (Core/DataProvider/FeeProvider)
LendingPool->>LendingPoolLiquidationManager: delegatecall 清算
FlashLoanReceiver->>LendingPool: flashLoan
LendingPool-->>FlashLoanReceiver: executeOperation 回调
sequenceDiagram
User->>LendingPool: 调用入口函数
LendingPool->>AddressesProvider: 查询依赖 (Core/DataProvider/FeeProvider)
LendingPool->>LendingPoolLiquidationManager: delegatecall 清算
FlashLoanReceiver->>LendingPool: flashLoan
LendingPool-->>FlashLoanReceiver: executeOperation 回调
sequenceDiagram
User->>LendingPool: 调用入口函数
LendingPool->>AddressesProvider: 查询依赖 (Core/DataProvider/FeeProvider)
LendingPool->>LendingPoolLiquidationManager: delegatecall 清算
FlashLoanReceiver->>LendingPool: flashLoan
LendingPool-->>FlashLoanReceiver: executeOperation 回调sequenceDiagram
User->>LendingPool: 调用入口函数
LendingPool->>AddressesProvider: 查询依赖 (Core/DataProvider/FeeProvider)
LendingPool->>LendingPoolLiquidationManager: delegatecall 清算
FlashLoanReceiver->>LendingPool: flashLoan
LendingPool-->>FlashLoanReceiver: executeOperation 回调
1.2 状态与估值层
contracts/lendingpool/LendingPoolCore.sol 是 V1 的“保险库”:底层资产直接托管在 Core 中,而非像 V2/V3 那样散布在 aToken。它维护 ReserveData、UserReserveData、liquidityIndex、borrowIndex 等惰性计息变量,并通过 CoreLibrary.sol 与 WadRayMath.sol 完成高精度利息累积与索引更新。tokenization/AToken.sol 将这些核心状态映射成 ERC20 凭证,结合 principalBalance + redirectedBalance 让余额随时间自动增长。LendingPoolDataProvider.sol 聚合视图数据:它直接从 Core 读取原始头寸,借助 IPriceOracleGetter 折算成 ETH 计价的 LTV、Liquidation Threshold、Health Factor 以及可借额度(注意:calculateUserGlobalData 等函数在估算费用时依赖 msg.sender,即结果与调用者相关)。三者形成“状态存储 → 数据聚合 → 头寸表示”的闭环,并把资金与会计逻辑与业务入口解耦。
sequenceDiagram
LendingPool->>LendingPoolCore: 读写 ReserveData/UserReserveData
LendingPoolCore->>AToken: getReserveATokenAddress()
AToken->>LendingPoolCore: balanceOf()/normalizedIncome
DataProvider->>LendingPoolCore: getReserves()/getUserBasicReserveData
DataProvider->>PriceOracle: getAssetPrice()
sequenceDiagram
LendingPool->>LendingPoolCore: 读写 ReserveData/UserReserveData
LendingPoolCore->>AToken: getReserveATokenAddress()
AToken->>LendingPoolCore: balanceOf()/normalizedIncome
DataProvider->>LendingPoolCore: getReserves()/getUserBasicReserveData
DataProvider->>PriceOracle: getAssetPrice()
sequenceDiagram
LendingPool->>LendingPoolCore: 读写 ReserveData/UserReserveData
LendingPoolCore->>AToken: getReserveATokenAddress()
AToken->>LendingPoolCore: balanceOf()/normalizedIncome
DataProvider->>LendingPoolCore: getReserves()/getUserBasicReserveData
DataProvider->>PriceOracle: getAssetPrice()sequenceDiagram
LendingPool->>LendingPoolCore: 读写 ReserveData/UserReserveData
LendingPoolCore->>AToken: getReserveATokenAddress()
AToken->>LendingPoolCore: balanceOf()/normalizedIncome
DataProvider->>LendingPoolCore: getReserves()/getUserBasicReserveData
DataProvider->>PriceOracle: getAssetPrice()
1.3 策略、治理与注册表
LendingPoolAddressesProvider.sol 扮演注册表(协议的 DNS):它保存最新的 LendingPool、Core、Configurator 等地址,为可升级系统提供解耦。DefaultReserveInterestRateStrategy.sol 基于储备利用率(Kink 曲线)与 ILendingRateOracle 输出稳定/浮动利率,FeeProvider.sol 统一计算开仓费用;LendingPoolConfigurator.sol 则拥有极高权限,负责 initReserve、启用/禁用借款、调整 LTV、Liquidation Threshold、利率曲线等风险参数。通过注册表、策略合约和治理配置三件套,协议可以在不触碰资产托管层的前提下根据市场环境调整策略,与上游的价格预言机与数据提供层一道,形成完整的“参数 + 策略 + 注册表”控制面板。
sequenceDiagram
Governance->>AddressesProvider: setLendingPoolImpl()/...
LendingPool->>AddressesProvider: getLendingPoolCore()
Configurator->>LendingPoolCore: enableReserveAsCollateral()
Core->>InterestRateStrategy: calculateInterestRates()
LendingPool->>FeeProvider: calculateLoanOriginationFee()
sequenceDiagram
Governance->>AddressesProvider: setLendingPoolImpl()/...
LendingPool->>AddressesProvider: getLendingPoolCore()
Configurator->>LendingPoolCore: enableReserveAsCollateral()
Core->>InterestRateStrategy: calculateInterestRates()
LendingPool->>FeeProvider: calculateLoanOriginationFee()
sequenceDiagram
Governance->>AddressesProvider: setLendingPoolImpl()/...
LendingPool->>AddressesProvider: getLendingPoolCore()
Configurator->>LendingPoolCore: enableReserveAsCollateral()
Core->>InterestRateStrategy: calculateInterestRates()
LendingPool->>FeeProvider: calculateLoanOriginationFee()sequenceDiagram
Governance->>AddressesProvider: setLendingPoolImpl()/...
LendingPool->>AddressesProvider: getLendingPoolCore()
Configurator->>LendingPoolCore: enableReserveAsCollateral()
Core->>InterestRateStrategy: calculateInterestRates()
LendingPool->>FeeProvider: calculateLoanOriginationFee()
本章确定了合约之间的依赖图谱。接下来将以每个合约为单位,结合白皮书对应章节,分析函数实现、状态变迁以及跨模块的接口契约。
2 LendingPool:入口逻辑
拆解关键流程(deposit/borrow/repay/flashLoan/liquidationCall),说明校验顺序、与 Core/DataProvider/FeeProvider 的交互、常见修饰器。可穿插函数调用图或伪代码。
sequenceDiagram
User->>LendingPool: 调用入口函数
LendingPool->>DataProvider: calculateUserGlobalData()
LendingPool->>FeeProvider: calculateLoanOriginationFee()
LendingPool->>LendingPoolCore: updateState*()
LendingPool->>AToken: mint/burn/redeem
LendingPool-->>User: 事件 + 资产划转结果
sequenceDiagram
User->>LendingPool: 调用入口函数
LendingPool->>DataProvider: calculateUserGlobalData()
LendingPool->>FeeProvider: calculateLoanOriginationFee()
LendingPool->>LendingPoolCore: updateState*()
LendingPool->>AToken: mint/burn/redeem
LendingPool-->>User: 事件 + 资产划转结果
sequenceDiagram
User->>LendingPool: 调用入口函数
LendingPool->>DataProvider: calculateUserGlobalData()
LendingPool->>FeeProvider: calculateLoanOriginationFee()
LendingPool->>LendingPoolCore: updateState*()
LendingPool->>AToken: mint/burn/redeem
LendingPool-->>User: 事件 + 资产划转结果sequenceDiagram
User->>LendingPool: 调用入口函数
LendingPool->>DataProvider: calculateUserGlobalData()
LendingPool->>FeeProvider: calculateLoanOriginationFee()
LendingPool->>LendingPoolCore: updateState*()
LendingPool->>AToken: mint/burn/redeem
LendingPool-->>User: 事件 + 资产划转结果
2.1 deposit
存款是 V1 流动性的来源,对应白皮书 §3.1 图 6 的“资产转移 → aToken 铸造 → 状态更新”。LendingPool.deposit 在入口层仍然先做安全校验,再把状态计算、凭证铸造与资金托管分发给下层组件:
- 权限与状态检查:
nonReentrant、onlyActiveReserve、onlyUnfreezedReserve、onlyAmountGreaterThanZero共同保证储备可用、金额有效、防止重入攻击。 - 识别首次存款者:读取
aToken.balanceOf(msg.sender)判定isFirstDeposit,方便 Core 在更新状态时将useAsCollateral从 0 切换到 1。 - 状态更新:
core.updateStateOnDeposit刷新储备的流动性指数和时间戳,并在首次存款时开启抵押标记;实际余额的变动由随后transferToReserve的资金转账体现。 - aToken 铸造:
aToken.mintOnDeposit将_amount直映为 ERC20 余额,不需要额外汇率,因为 aToken 余额后续会乘liquidityIndex自动增长。 - 资金划转与事件:
core.transferToReserve将资产真正存入保险库(ERC20 路径调用safeTransferFrom,ETH 路径根据msg.value进账并退还多余金额),随后Deposit事件把储备、金额、推荐码广播给前端或积分系统。 - 资产类型处理:函数被标记为
payable方便 ETH 存款,而 ERC20 存款所需的授权与转账逻辑全部封装在 Core 内部,协议代码显式区分两种资产路径。 - 隐式前置条件:用户在存 ERC20 前必须先对
LendingPoolCoreapprove足够额度;协议以组合操作的形式优化 gas 成本,因此需要前端流程引导。
/**
* @dev 将基础资产存入储备池中。同时会铸造相应数量的覆盖资产(aToken)
* @param _reserve reserve 的地址
* @param _amount 存入金额
* @param _referralCode integrators are assigned a referral code and can potentially receive rewards.
**/
function deposit(
address _reserve,
uint256 _amount,
uint16 _referralCode
)
external
payable
nonReentrant
onlyActiveReserve(_reserve) // 检查储备池是否处于活跃状态
onlyUnfreezedReserve(_reserve) // 检查储备池是否处于冻结状态
onlyAmountGreaterThanZero(_amount) // 检查存入金额是否大于0
{
// 获取 aToken 合约地址
AToken aToken = AToken(core.getReserveATokenAddress(_reserve));
// 判断是否是第一次存入
bool isFirstDeposit = aToken.balanceOf(msg.sender) == 0;
// 更新状态
core.updateStateOnDeposit(
_reserve,
msg.sender,
_amount,
isFirstDeposit
);
// 按特定兑换率以1:1比例向用户铸造AToken
aToken.mintOnDeposit(msg.sender, _amount);
// 将资产转账到核心合约
core.transferToReserve.value(msg.value)(_reserve, msg.sender, _amount);
//solium-disable-next-line
// 发出存款事件
emit Deposit(
_reserve,
msg.sender,
_amount,
_referralCode,
block.timestamp
);
}sequenceDiagram
User->>LendingPool: borrow(_reserve, _amount, _rateMode)
LendingPool->>LendingPoolCore: isReserveBorrowingEnabled()/getReserveAvailableLiquidity()
LendingPool->>LendingPoolDataProvider: calculateUserGlobalData()
LendingPool->>FeeProvider: calculateLoanOriginationFee()
LendingPool->>LendingPoolCore: updateStateOnBorrow(...)
LendingPoolCore-->>LendingPool: 最终借款利率、borrowBalanceIncrease
LendingPool->>User: transferToUser(_reserve, _amount)
sequenceDiagram
User->>LendingPool: borrow(_reserve, _amount, _rateMode)
LendingPool->>LendingPoolCore: isReserveBorrowingEnabled()/getReserveAvailableLiquidity()
LendingPool->>LendingPoolDataProvider: calculateUserGlobalData()
LendingPool->>FeeProvider: calculateLoanOriginationFee()
LendingPool->>LendingPoolCore: updateStateOnBorrow(...)
LendingPoolCore-->>LendingPool: 最终借款利率、borrowBalanceIncrease
LendingPool->>User: transferToUser(_reserve, _amount)
sequenceDiagram
User->>LendingPool: borrow(_reserve, _amount, _rateMode)
LendingPool->>LendingPoolCore: isReserveBorrowingEnabled()/getReserveAvailableLiquidity()
LendingPool->>LendingPoolDataProvider: calculateUserGlobalData()
LendingPool->>FeeProvider: calculateLoanOriginationFee()
LendingPool->>LendingPoolCore: updateStateOnBorrow(...)
LendingPoolCore-->>LendingPool: 最终借款利率、borrowBalanceIncrease
LendingPool->>User: transferToUser(_reserve, _amount)sequenceDiagram
User->>LendingPool: borrow(_reserve, _amount, _rateMode)
LendingPool->>LendingPoolCore: isReserveBorrowingEnabled()/getReserveAvailableLiquidity()
LendingPool->>LendingPoolDataProvider: calculateUserGlobalData()
LendingPool->>FeeProvider: calculateLoanOriginationFee()
LendingPool->>LendingPoolCore: updateStateOnBorrow(...)
LendingPoolCore-->>LendingPool: 最终借款利率、borrowBalanceIncrease
LendingPool->>User: transferToUser(_reserve, _amount)
sequenceDiagram
User->>LendingPool: deposit(_reserve, _amount)
LendingPool->>LendingPoolCore: updateStateOnDeposit(...)
LendingPoolCore-->>LendingPool: 索引更新完成
LendingPool->>AToken: mintOnDeposit(msg.sender, _amount)
AToken-->>User: 增加 aToken 余额
LendingPool->>LendingPoolCore: transferToReserve(...)
sequenceDiagram
User->>LendingPool: deposit(_reserve, _amount)
LendingPool->>LendingPoolCore: updateStateOnDeposit(...)
LendingPoolCore-->>LendingPool: 索引更新完成
LendingPool->>AToken: mintOnDeposit(msg.sender, _amount)
AToken-->>User: 增加 aToken 余额
LendingPool->>LendingPoolCore: transferToReserve(...)
sequenceDiagram
User->>LendingPool: deposit(_reserve, _amount)
LendingPool->>LendingPoolCore: updateStateOnDeposit(...)
LendingPoolCore-->>LendingPool: 索引更新完成
LendingPool->>AToken: mintOnDeposit(msg.sender, _amount)
AToken-->>User: 增加 aToken 余额
LendingPool->>LendingPoolCore: transferToReserve(...)sequenceDiagram
User->>LendingPool: deposit(_reserve, _amount)
LendingPool->>LendingPoolCore: updateStateOnDeposit(...)
LendingPoolCore-->>LendingPool: 索引更新完成
LendingPool->>AToken: mintOnDeposit(msg.sender, _amount)
AToken-->>User: 增加 aToken 余额
LendingPool->>LendingPoolCore: transferToReserve(...)
2.2 borrow
借款对应白皮书 §3.2 中“抵押额度 → 借款模式 → 资金释放”的流程,V1 中的实现完全落在 LendingPool.borrow。函数使用 BorrowLocalVars 暂存数据,避免 “Stack too deep” 并保持流水线式的校验顺序:
- 入口修饰器与储备可借性:
nonReentrant、onlyActiveReserve、onlyUnfreezedReserve、onlyAmountGreaterThanZero校验基本条件,随后core.isReserveBorrowingEnabled、core.getReserveAvailableLiquidity保障储备层允许借款且流动性足够。传入的_interestRateMode必须是 1 (STABLE) 或 2 (VARIABLE),否则直接 revert。 - 账户总览校验:通过
dataProvider.calculateUserGlobalData(msg.sender)一次性取回用户的抵押余额、借款余额、总费用、LTV、Liquidation Threshold 以及healthFactorBelowThreshold标记。要求抵押余额大于 0 且当前 Health Factor 不低于 1,否则禁止继续借款。 - 开仓费用与抵押需求:
feeProvider.calculateLoanOriginationFee对借款金额计算开仓费用,并且要求费用 > 0(防止金额过小)。接着使用dataProvider.calculateCollateralNeededInETH折算为 ETH 计价的最小抵押物需求,该公式将_amount、borrowFee、已有借款与费用一并纳入,只有当userCollateralBalanceETH≥amountOfCollateralNeededETH时才能借款。 - 稳定利率额外限制:若用户选择稳定利率,
core.isUserAllowedToBorrowAtStable会检查储备是否开启稳定模式、用户抵押资产是否与借款资产过度同质,以及当前金额是否允许;parametersProvider.getMaxStableRateBorrowSizePercent则限制稳定利率借款在总可用流动性中的占比,防止一笔交易吸干储备。 - 状态刷新与资金划转:
core.updateStateOnBorrow将债务指数与principalBalance更新为最新值,返回实际借款利率 (finalUserBorrowRate) 与自上次操作以来累积的borrowBalanceIncrease;状态更新后才调用core.transferToUser把_amount下发给借款人,最后Borrow事件记录利率模式、费用和推荐码。
function borrow(
address _reserve,
uint256 _amount,
uint256 _interestRateMode,
uint16 _referralCode
)
external
nonReentrant
onlyActiveReserve(_reserve)
onlyUnfreezedReserve(_reserve)
onlyAmountGreaterThanZero(_amount)
{
BorrowLocalVars memory vars;
// 储备借款开关、利率模式、可用流动性检查
...
// 将利率模式转换为 coreLibrary.interestRateMode
vars.rateMode = CoreLibrary.InterestRateMode(_interestRateMode);
// 检查储备池中是否有足够的可用金额,可用流动性即为核心合约的余额。
vars.availableLiquidity = core.getReserveAvailableLiquidity(_reserve);
...
(
,
vars.userCollateralBalanceETH,
vars.userBorrowBalanceETH,
vars.userTotalFeesETH,
vars.currentLtv,
vars.currentLiquidationThreshold,
,
vars.healthFactorBelowThreshold
) = dataProvider.calculateUserGlobalData(msg.sender); // 一次性获取抵押、借款、LTV、健康度
// 抵押>0 与 Health Factor>1 的 require
...
vars.borrowFee = feeProvider.calculateLoanOriginationFee(
msg.sender,
_amount
); // 计算开仓费
...
vars.amountOfCollateralNeededETH = dataProvider
.calculateCollateralNeededInETH(
_reserve,
_amount,
vars.borrowFee,
vars.userBorrowBalanceETH,
vars.userTotalFeesETH,
vars.currentLtv
); // 根据 LTV 推导所需抵押
// 抵押覆盖校验
...
/**
* 如果用户以稳定利率借款,需要满足以下条件:
* 1. 储备池必须启用稳定利率借款
* 2. 用户不能从储备池借款,如果他们的抵押品主要是他们正在借款的货币,以防止滥用。
* 3. 用户只能借款储备池总流动性的一个相对较小、可配置的金额。
*/
if (vars.rateMode == CoreLibrary.InterestRateMode.STABLE) {
// 检查借款模式是否为稳定利率,并且储备池是否启用了稳定利率借款
...
uint256 maxLoanPercent = parametersProvider
.getMaxStableRateBorrowSizePercent();
uint256 maxLoanSizeStable = vars
.availableLiquidity
.mul(maxLoanPercent)
.div(100);
require(
_amount <= maxLoanSizeStable,
"User is trying to borrow too much liquidity at a stable rate"
);
}
// 更新储备池状态
(vars.finalUserBorrowRate, vars.borrowBalanceIncrease) = core
.updateStateOnBorrow(
_reserve,
msg.sender,
_amount,
vars.borrowFee,
vars.rateMode // 同步更新储备曲线和用户债务指数
);
// 转移储备池中的资产到借款人
core.transferToUser(_reserve, msg.sender, _amount);
emit Borrow(
_reserve,
msg.sender,
_amount,
_interestRateMode,
vars.finalUserBorrowRate,
vars.borrowFee,
vars.borrowBalanceIncrease,
_referralCode,
block.timestamp
);
}sequenceDiagram
User->>LendingPool: repay(_reserve, _amount, _onBehalfOf)
LendingPool->>LendingPoolCore: getUserBorrowBalances()
LendingPool->>LendingPoolCore: getUserOriginationFee()
LendingPool->>LendingPoolCore: updateStateOnRepay(...)
alt 仅偿还费用
LendingPool->>LendingPoolCore: transferToFeeCollectionAddress()
else 本金 + 费用
LendingPool->>LendingPoolCore: transferToFeeCollectionAddress()
LendingPool->>LendingPoolCore: transferToReserve(...)
end
LendingPool-->>User: Repay 事件
sequenceDiagram
User->>LendingPool: repay(_reserve, _amount, _onBehalfOf)
LendingPool->>LendingPoolCore: getUserBorrowBalances()
LendingPool->>LendingPoolCore: getUserOriginationFee()
LendingPool->>LendingPoolCore: updateStateOnRepay(...)
alt 仅偿还费用
LendingPool->>LendingPoolCore: transferToFeeCollectionAddress()
else 本金 + 费用
LendingPool->>LendingPoolCore: transferToFeeCollectionAddress()
LendingPool->>LendingPoolCore: transferToReserve(...)
end
LendingPool-->>User: Repay 事件
sequenceDiagram
User->>LendingPool: repay(_reserve, _amount, _onBehalfOf)
LendingPool->>LendingPoolCore: getUserBorrowBalances()
LendingPool->>LendingPoolCore: getUserOriginationFee()
LendingPool->>LendingPoolCore: updateStateOnRepay(...)
alt 仅偿还费用
LendingPool->>LendingPoolCore: transferToFeeCollectionAddress()
else 本金 + 费用
LendingPool->>LendingPoolCore: transferToFeeCollectionAddress()
LendingPool->>LendingPoolCore: transferToReserve(...)
end
LendingPool-->>User: Repay 事件sequenceDiagram
User->>LendingPool: repay(_reserve, _amount, _onBehalfOf)
LendingPool->>LendingPoolCore: getUserBorrowBalances()
LendingPool->>LendingPoolCore: getUserOriginationFee()
LendingPool->>LendingPoolCore: updateStateOnRepay(...)
alt 仅偿还费用
LendingPool->>LendingPoolCore: transferToFeeCollectionAddress()
else 本金 + 费用
LendingPool->>LendingPoolCore: transferToFeeCollectionAddress()
LendingPool->>LendingPoolCore: transferToReserve(...)
end
LendingPool-->>User: Repay 事件
2.3 repay
repay 负责关闭或部分偿还债务,对应白皮书 §3.3 的“偿还 + 费用回收”。函数既允许借款人自还,也允许别人带着 _onBehalfOf 参数帮忙清算,UINT_MAX_VALUE(uint256(-1)) 则代表“尽可能还多”:
- 读取债务与费用:通过
core.getUserBorrowBalances得到principalBorrowBalance、compoundedBorrowBalance、borrowBalanceIncrease,再取出core.getUserOriginationFee。如果债务为零直接 revert。 - 确定还款金额:默认
paybackAmount = compounded + originationFee,若调用者传入_amount != UINT_MAX_VALUE且更小,则裁剪,且要求“代还”场景必须显式金额。ETH 储备下msg.value至少等于paybackAmount。 - 仅偿还费用的分支:当
paybackAmount <= originationFee时,说明用户只需偿还费用。core.updateStateOnRepay会以 0 principal 更新状态,并把vars.paybackAmount全额通过core.transferToFeeCollectionAddress送往TokenDistributor(注意:此分支下费用从_onBehalfOf账户扣除)。 - 标准偿还流程:
paybackAmountMinusFees = paybackAmount - originationFee,随后调用core.updateStateOnRepay写入本金偿还金额、费用和借款增量,并标记是否还清。若存在 origination fee,则先transferToFeeCollectionAddress,剩余本金部分通过core.transferToReserve入库;ETH 路径会把msg.value - fee作为value入参,多余 ETH 在transferToReserve内退还。在此分支中,origination fee 由msg.sender支付(ERC20 需msg.sender对 Core 授权)。 - 事件记录:无论是哪条路径,最终都会
emit Repay,携带_reserve、被还债用户、repayer 地址、偿还本金、费用与borrowBalanceIncrease。
function repay(
address _reserve,
uint256 _amount,ß
address payable _onBehalfOf
)
external
payable
nonReentrant
onlyActiveReserve(_reserve)
onlyAmountGreaterThanZero(_amount)
{
RepayLocalVars memory vars;
( // 查询本金、复利余额与利息增量
vars.principalBorrowBalance,
vars.compoundedBorrowBalance,
vars.borrowBalanceIncrease
) = core.getUserBorrowBalances(_reserve, _onBehalfOf);
vars.originationFee = core.getUserOriginationFee(_reserve, _onBehalfOf);
vars.isETH = EthAddressLib.ethAddress() == _reserve;
... // require: 仍有债务、msg.sender 代还约束、msg.value 校验
vars.paybackAmount = vars.compoundedBorrowBalance.add(
vars.originationFee // 需要偿还的总额 = 债务 + 开仓费
);
if (_amount != UINT_MAX_VALUE && _amount < vars.paybackAmount) {
vars.paybackAmount = _amount;
}
... // ETH 分支需要携带足量 msg.value
// 如果还款金额小于初始费用,直接转给费用接收地址
if (vars.paybackAmount <= vars.originationFee) {
// 只剩开仓费待付,principal 不变
core.updateStateOnRepay(
_reserve,
_onBehalfOf,
0,
vars.paybackAmount,
vars.borrowBalanceIncrease,
false
);
... // core.transferToFeeCollectionAddress(... vars.paybackAmount ...)
emit Repay(
_reserve,
_onBehalfOf,
msg.sender,
0,
vars.paybackAmount,
vars.borrowBalanceIncrease,
block.timestamp
);
return;
}
vars.paybackAmountMinusFees = vars.paybackAmount.sub(
vars.originationFee // 真正还掉的本金
);
core.updateStateOnRepay(
_reserve,
_onBehalfOf,
vars.paybackAmountMinusFees,
vars.originationFee,
vars.borrowBalanceIncrease,
vars.compoundedBorrowBalance == vars.paybackAmountMinusFees
);
// 如果没有支付初始费用,将费用转给费用接收地址
if (vars.originationFee > 0) {
... // core.transferToFeeCollectionAddress(... vars.originationFee ...)
}
// 发送总 msg.value(如果是 ETH)。
// transferToReserve() 函数会负责将多余的 ETH 退还给调用者。
core.transferToReserve.value(
vars.isETH ? msg.value.sub(vars.originationFee) : 0
)(_reserve, msg.sender, vars.paybackAmountMinusFees);
emit Repay(
_reserve,
_onBehalfOf,
msg.sender,
vars.paybackAmountMinusFees,
vars.originationFee,
vars.borrowBalanceIncrease,
block.timestamp
);
}sequenceDiagram
FlashLoanReceiver->>LendingPool: flashLoan(_reserve, _amount)
LendingPool->>LendingPoolCore: 查询 availableLiquidityBefore
LendingPool->>ParametersProvider: getFlashLoanFeesInBips()
LendingPool->>FlashLoanReceiver: transferToUser(_amount)
FlashLoanReceiver-->>LendingPool: executeOperation 回调
LendingPool->>LendingPoolCore: 校验 availableLiquidityAfter
LendingPool->>LendingPoolCore: updateStateOnFlashLoan(...)
sequenceDiagram
FlashLoanReceiver->>LendingPool: flashLoan(_reserve, _amount)
LendingPool->>LendingPoolCore: 查询 availableLiquidityBefore
LendingPool->>ParametersProvider: getFlashLoanFeesInBips()
LendingPool->>FlashLoanReceiver: transferToUser(_amount)
FlashLoanReceiver-->>LendingPool: executeOperation 回调
LendingPool->>LendingPoolCore: 校验 availableLiquidityAfter
LendingPool->>LendingPoolCore: updateStateOnFlashLoan(...)
sequenceDiagram
FlashLoanReceiver->>LendingPool: flashLoan(_reserve, _amount)
LendingPool->>LendingPoolCore: 查询 availableLiquidityBefore
LendingPool->>ParametersProvider: getFlashLoanFeesInBips()
LendingPool->>FlashLoanReceiver: transferToUser(_amount)
FlashLoanReceiver-->>LendingPool: executeOperation 回调
LendingPool->>LendingPoolCore: 校验 availableLiquidityAfter
LendingPool->>LendingPoolCore: updateStateOnFlashLoan(...)sequenceDiagram
FlashLoanReceiver->>LendingPool: flashLoan(_reserve, _amount)
LendingPool->>LendingPoolCore: 查询 availableLiquidityBefore
LendingPool->>ParametersProvider: getFlashLoanFeesInBips()
LendingPool->>FlashLoanReceiver: transferToUser(_amount)
FlashLoanReceiver-->>LendingPool: executeOperation 回调
LendingPool->>LendingPoolCore: 校验 availableLiquidityAfter
LendingPool->>LendingPoolCore: updateStateOnFlashLoan(...)
2.4 flashLoan
闪电贷允许外部合约在一次交易内借出资金并归还,入口函数贯彻“余额守恒 + 费用拆分”原则:
- 准备与计费:
onlyActiveReserve、onlyAmountGreaterThanZero后,函数根据资产种类读取availableLiquidityBefore(ETH 取address(core).balance,ERC20 读取IERC20(_reserve).balanceOf(address(core))),确认储备足以覆盖_amount。parametersProvider.getFlashLoanFeesInBips返回总费率与协议分润,amountFee = _amount * totalFeeBips / 10000,protocolFee = amountFee * protocolFeeBips / 10000,并要求这两个值都大于 0。 - 借出与回调:将
_receiver强转为IFlashLoanReceiver,通过core.transferToUser把_amount划给receiver,随后调用receiver.executeOperation(_reserve, _amount, amountFee, _params)。此回调必须在同一交易内完成。 - 归还与校验:执行完回调后再次读取
availableLiquidityAfter,要求其等于availableLiquidityBefore + amountFee,即本金 + 总费用都已经回到 Core;若不满足则 revert。 - 状态更新与事件:
core.updateStateOnFlashLoan把amountFee - protocolFee计入储备收益、将protocolFee发送到费用收集地址;事件FlashLoan记录_receiver、_reserve、_amount、总费用与协议分润。
function flashLoan(
address _receiver,
address _reserve,
uint256 _amount,
bytes memory _params
)
public
nonReentrant
onlyActiveReserve(_reserve)
onlyAmountGreaterThanZero(_amount)
{
// 检查储备池是否有足够的可用流动性
// 避免使用 LendingPoolCore 中的 getAvailableLiquidity() 函数以节省 gas
uint256 availableLiquidityBefore = _reserve ==
EthAddressLib.ethAddress()
? address(core).balance
: IERC20(_reserve).balanceOf(address(core));
... // require: 储备流动性必须覆盖 _amount
(uint256 totalFeeBips, uint256 protocolFeeBips) = parametersProvider
.getFlashLoanFeesInBips();
uint256 amountFee = _amount.mul(totalFeeBips).div(10000);
// 协议费用是 amountFee 中为协议保留的部分 - 剩余部分归存款人所有
uint256 protocolFee = amountFee.mul(protocolFeeBips).div(10000);
... // require: 金额足够大且费用非零
IFlashLoanReceiver receiver = IFlashLoanReceiver(_receiver);
address payable userPayable = address(uint160(_receiver));
// 转移资金给接收者
core.transferToUser(_reserve, userPayable, _amount);
receiver.executeOperation(_reserve, _amount, amountFee, _params); // 外部合约需在此回调内完成借款逻辑
// 检查核心合约的实际余额是否包含返回的金额
uint256 availableLiquidityAfter = _reserve == EthAddressLib.ethAddress()
? address(core).balance
: IERC20(_reserve).balanceOf(address(core));
require(
availableLiquidityAfter == availableLiquidityBefore.add(amountFee),
"The actual balance of the protocol is inconsistent"
);
// 更新储备池状态
core.updateStateOnFlashLoan(
_reserve,
availableLiquidityBefore,
amountFee.sub(protocolFee),
protocolFee // 结息:把收益分给存款人,协议抽象余
);
emit FlashLoan(
_receiver,
_reserve,
_amount,
amountFee,
protocolFee,
block.timestamp
);
}sequenceDiagram
Liquidator->>LendingPool: liquidationCall(_collateral, _reserve, _user, ...)
LendingPool->>LiquidationManager: delegatecall liquidationCall(...)
LiquidationManager->>DataProvider: calculateUserGlobalData(_user)
LiquidationManager->>LendingPoolCore: getUserUnderlyingAssetBalance()/getUserBorrowBalances()
LiquidationManager->>PriceOracle: getAssetPrice()
LiquidationManager->>LendingPoolCore: updateStateOnLiquidation(...)
alt receive aToken
LiquidationManager->>AToken: transferOnLiquidation()
else receive underlying
LiquidationManager->>AToken: burnOnLiquidation()
LiquidationManager->>LendingPoolCore: transferToUser()
end
Liquidator->>LendingPoolCore: transferToReserve(...)
sequenceDiagram
Liquidator->>LendingPool: liquidationCall(_collateral, _reserve, _user, ...)
LendingPool->>LiquidationManager: delegatecall liquidationCall(...)
LiquidationManager->>DataProvider: calculateUserGlobalData(_user)
LiquidationManager->>LendingPoolCore: getUserUnderlyingAssetBalance()/getUserBorrowBalances()
LiquidationManager->>PriceOracle: getAssetPrice()
LiquidationManager->>LendingPoolCore: updateStateOnLiquidation(...)
alt receive aToken
LiquidationManager->>AToken: transferOnLiquidation()
else receive underlying
LiquidationManager->>AToken: burnOnLiquidation()
LiquidationManager->>LendingPoolCore: transferToUser()
end
Liquidator->>LendingPoolCore: transferToReserve(...)
sequenceDiagram
Liquidator->>LendingPool: liquidationCall(_collateral, _reserve, _user, ...)
LendingPool->>LiquidationManager: delegatecall liquidationCall(...)
LiquidationManager->>DataProvider: calculateUserGlobalData(_user)
LiquidationManager->>LendingPoolCore: getUserUnderlyingAssetBalance()/getUserBorrowBalances()
LiquidationManager->>PriceOracle: getAssetPrice()
LiquidationManager->>LendingPoolCore: updateStateOnLiquidation(...)
alt receive aToken
LiquidationManager->>AToken: transferOnLiquidation()
else receive underlying
LiquidationManager->>AToken: burnOnLiquidation()
LiquidationManager->>LendingPoolCore: transferToUser()
end
Liquidator->>LendingPoolCore: transferToReserve(...)sequenceDiagram
Liquidator->>LendingPool: liquidationCall(_collateral, _reserve, _user, ...)
LendingPool->>LiquidationManager: delegatecall liquidationCall(...)
LiquidationManager->>DataProvider: calculateUserGlobalData(_user)
LiquidationManager->>LendingPoolCore: getUserUnderlyingAssetBalance()/getUserBorrowBalances()
LiquidationManager->>PriceOracle: getAssetPrice()
LiquidationManager->>LendingPoolCore: updateStateOnLiquidation(...)
alt receive aToken
LiquidationManager->>AToken: transferOnLiquidation()
else receive underlying
LiquidationManager->>AToken: burnOnLiquidation()
LiquidationManager->>LendingPoolCore: transferToUser()
end
Liquidator->>LendingPoolCore: transferToReserve(...)
2.5 liquidationCall
清算入口通过 delegatecall 把执行上下文交给 LendingPoolLiquidationManager,后者在同一个存储上下文中完成所有校验与资产转移:
- 入口与委托:
LendingPool.liquidationCall只做储备活跃性检查和nonReentrant保护,然后对addressesProvider.getLendingPoolLiquidationManager()发起delegatecall。如果返回的(returnCode, returnMessage)非 0,会拼接字符串并 revert,确保只要清算失败就回滚整笔交易。 - 健康度与抵押检查:管理器内首先调用
dataProvider.calculateUserGlobalData(_user),只有当healthFactorBelowThreshold == true时才允许继续。随后读取core.getUserUnderlyingAssetBalance(_collateral)、core.isReserveUsageAsCollateralEnabled和core.isUserUseReserveAsCollateralEnabled,确认该抵押品可被清算,再通过core.getUserBorrowBalances(_reserve, _user)检查该用户确实借入了指定的债务资产。 - 可清算额度计算:遵循
LIQUIDATION_CLOSE_FACTOR_PERCENT = 50,vars.maxPrincipalAmountToLiquidate = userCompoundedBorrowBalance * 50%,实际处理金额取_purchaseAmount与上限的较小值。calculateAvailableCollateralToLiquidate利用价格预言机读取_collateral与_reserve的 ETH 价格,再乘以core.getReserveLiquidationBonus(_collateral)得到最多可 seize 的抵押资产数量;若该抵押不足以覆盖_purchaseAmount,函数会下调actualAmountToLiquidate。 - 费用与资产转移:若用户仍有
originationFee,会再调用calculateAvailableCollateralToLiquidate分配一部分抵押作为费用抵押并记账给协议。对于_receiveAToken == true,直接AToken.transferOnLiquidation将 aToken 余额转给清算人;否则先burnOnLiquidation销毁 aToken,再由core.transferToUser发放等值底层资产。同时,清算人偿还的本金(ETH 或 ERC20)由core.transferToReserve.value(msg.value)收进协议,若费用被清算则额外core.liquidateFee发送到TokenDistributor并触发OriginationFeeLiquidated。 - 状态更新与事件:
core.updateStateOnLiquidation在资金移动前已经写入债务减少、抵押扣减、fee 分配以及borrowBalanceIncrease,最终LiquidationCall事件会记录 collateral、reserve、用户、实际偿还金额、被扣抵押数量、利息增量、清算人地址以及是否领取 aToken。
// LendingPool.sol
function liquidationCall(
address _collateral,
address _reserve,
address _user,
uint256 _purchaseAmount,
bool _receiveAToken
)
external
payable
nonReentrant
onlyActiveReserve(_reserve)
onlyActiveReserve(_collateral)
{
address liquidationManager = addressesProvider.getLendingPoolLiquidationManager();
(bool success, bytes memory result) = liquidationManager.delegatecall(
abi.encodeWithSignature(
"liquidationCall(address,address,address,uint256,bool)",
_collateral,
_reserve,
_user,
_purchaseAmount,
_receiveAToken
)
);
require(success, "Liquidation call failed");
(uint256 returnCode, string memory returnMessage) = abi.decode(
result,
(uint256, string)
);
require(returnCode == 0, string(abi.encodePacked("Liquidation failed: ", returnMessage)));
} // LendingPoolLiquidationManager.sol
function liquidationCall(
address _collateral,
address _reserve,
address _user,
uint256 _purchaseAmount,
bool _receiveAToken
) external payable returns (uint256, string memory) {
LiquidationCallLocalVars memory vars;
(, , , , , , , vars.healthFactorBelowThreshold) = dataProvider.calculateUserGlobalData(
_user
);
if (!vars.healthFactorBelowThreshold) {
return (
uint256(LiquidationErrors.HEALTH_FACTOR_ABOVE_THRESHOLD),
"Health factor is not below the threshold"
);
}
vars.userCollateralBalance = core.getUserUnderlyingAssetBalance(_collateral, _user);
if (vars.userCollateralBalance == 0) {
return (
uint256(LiquidationErrors.NO_COLLATERAL_AVAILABLE),
"Invalid collateral to liquidate"
);
}
vars.isCollateralEnabled =
core.isReserveUsageAsCollateralEnabled(_collateral) &&
core.isUserUseReserveAsCollateralEnabled(_collateral, _user);
if (!vars.isCollateralEnabled) {
return (
uint256(LiquidationErrors.COLLATERAL_CANNOT_BE_LIQUIDATED),
"The collateral chosen cannot be liquidated"
);
}
(, vars.userCompoundedBorrowBalance, vars.borrowBalanceIncrease) = core.getUserBorrowBalances(
_reserve,
_user
);
if (vars.userCompoundedBorrowBalance == 0) {
return (
uint256(LiquidationErrors.CURRRENCY_NOT_BORROWED),
"User did not borrow the specified currency"
);
}
vars.maxPrincipalAmountToLiquidate = vars
.userCompoundedBorrowBalance
.mul(LIQUIDATION_CLOSE_FACTOR_PERCENT)
.div(100);
vars.actualAmountToLiquidate = _purchaseAmount > vars.maxPrincipalAmountToLiquidate
? vars.maxPrincipalAmountToLiquidate
: _purchaseAmount;
(
uint256 maxCollateralToLiquidate,
uint256 principalAmountNeeded
) = calculateAvailableCollateralToLiquidate(
_collateral,
_reserve,
vars.actualAmountToLiquidate,
vars.userCollateralBalance
);
vars.originationFee = core.getUserOriginationFee(_reserve, _user);
if (vars.originationFee > 0) {
(
vars.liquidatedCollateralForFee,
vars.feeLiquidated
) = calculateAvailableCollateralToLiquidate(
_collateral,
_reserve,
vars.originationFee,
vars.userCollateralBalance.sub(maxCollateralToLiquidate)
);
}
if (principalAmountNeeded < vars.actualAmountToLiquidate) {
vars.actualAmountToLiquidate = principalAmountNeeded;
}
if (!_receiveAToken) {
uint256 availableCollateral = core.getReserveAvailableLiquidity(_collateral);
if (availableCollateral < maxCollateralToLiquidate) {
return (
uint256(LiquidationErrors.NOT_ENOUGH_LIQUIDITY),
"There isn't enough liquidity available to liquidate"
);
}
}
core.updateStateOnLiquidation(
_reserve,
_collateral,
_user,
vars.actualAmountToLiquidate,
maxCollateralToLiquidate,
vars.feeLiquidated,
vars.liquidatedCollateralForFee,
vars.borrowBalanceIncrease,
_receiveAToken
);
AToken collateralAtoken = AToken(core.getReserveATokenAddress(_collateral));
if (_receiveAToken) {
collateralAtoken.transferOnLiquidation(_user, msg.sender, maxCollateralToLiquidate);
} else {
collateralAtoken.burnOnLiquidation(_user, maxCollateralToLiquidate);
core.transferToUser(_collateral, msg.sender, maxCollateralToLiquidate);
}
core.transferToReserve.value(msg.value)(
_reserve,
msg.sender,
vars.actualAmountToLiquidate
);
if (vars.feeLiquidated > 0) {
collateralAtoken.burnOnLiquidation(_user, vars.liquidatedCollateralForFee);
core.liquidateFee(
_collateral,
vars.liquidatedCollateralForFee,
addressesProvider.getTokenDistributor()
);
emit OriginationFeeLiquidated(
_collateral,
_reserve,
_user,
vars.feeLiquidated,
vars.liquidatedCollateralForFee,
block.timestamp
);
}
emit LiquidationCall(
_collateral,
_reserve,
_user,
vars.actualAmountToLiquidate,
maxCollateralToLiquidate,
vars.borrowBalanceIncrease,
msg.sender,
_receiveAToken,
block.timestamp
);
return (uint256(LiquidationErrors.NO_ERROR), "No errors");
}3 LendingPoolCore:资金与索引
LendingPoolCore 保管全部底层资产,并把协议状态拆成 reserves[address] 与 usersReserveData[user][reserve] 两层,业务入口只在 LendingPool 中做权限校验,具体的利率积累、余额记账、资产转移都下沉到 Core 与 CoreLibrary。这一层的所有金额在 WadRayMath 的 ray 精度 (1e27) 下运算,确保利息累积不会丢精度。
3.1 状态结构:ReserveData 与 UserReserveData
CoreLibrary.ReserveData同时保存资金侧指标(lastLiquidityCumulativeIndex、lastVariableBorrowCumulativeIndex、currentLiquidityRate、totalBorrowsStable/Variable)和风险参数(baseLTVasCollateral、liquidationThreshold、liquidationBonus、borrowingEnabled、isStableBorrowRateEnabled等)。初始化时init()把两个指数都设为1e27,后续所有收益都在此基础上累乘。CoreLibrary.UserReserveData记录单个用户在某个储备上的本金、借款指数、originationFee和stableBorrowRate,再辅以lastUpdateTimestamp与useAsCollateral标记。入口层通过setUserUseReserveAsCollateral打开或关闭抵押权,所有借款模式切换和稳定利率重平衡都直接写入这份结构。reservesList/getReserves()提供了一个可迭代的储备数组,LendingPoolDataProvider.calculateUserGlobalData就依赖它遍历所有资产。
sequenceDiagram
LendingPoolConfigurator->>LendingPoolCore: initReserve()
LendingPoolCore->>CoreLibrary: init(ReserveData)
LendingPool->>LendingPoolCore: getUserBasicReserveData()
LendingPoolCore-->>LendingPool: UserReserveData(useAsCollateral, principal, fee)
sequenceDiagram
LendingPoolConfigurator->>LendingPoolCore: initReserve()
LendingPoolCore->>CoreLibrary: init(ReserveData)
LendingPool->>LendingPoolCore: getUserBasicReserveData()
LendingPoolCore-->>LendingPool: UserReserveData(useAsCollateral, principal, fee)
sequenceDiagram
LendingPoolConfigurator->>LendingPoolCore: initReserve()
LendingPoolCore->>CoreLibrary: init(ReserveData)
LendingPool->>LendingPoolCore: getUserBasicReserveData()
LendingPoolCore-->>LendingPool: UserReserveData(useAsCollateral, principal, fee)sequenceDiagram
LendingPoolConfigurator->>LendingPoolCore: initReserve()
LendingPoolCore->>CoreLibrary: init(ReserveData)
LendingPool->>LendingPoolCore: getUserBasicReserveData()
LendingPoolCore-->>LendingPool: UserReserveData(useAsCollateral, principal, fee)
3.2 惰性计息与状态刷新
- 每个流转动作都会先
reserves[_reserve].updateCumulativeIndexes(),再调用updateReserveInterestRatesAndTimestampInternal()。updateCumulativeIndexes通过calculateLinearInterest和calculateCompoundedInterest把当前利率与上次时间戳之间的利息累加到两个指数上,只有totalBorrows>0时才真正更新。 updateStateOnDeposit与updateStateOnRedeem分别在_amount作为正负流动性注入/抽离后刷新利率;首次存款时会把useAsCollateral切成true,赎回至 0 则反向关闭。updateStateOnBorrow把储备借款指标与用户借款指标拆开处理:updateReserveStateOnBorrowInternal更新totalBorrows、平均利率与时间戳,updateUserStateOnBorrowInternal把用户本金加上新借款,并记录_borrowFee与利率模式,返回最新的borrowBalanceIncrease提供给事件。updateStateOnRepay、updateStateOnLiquidation和updateStateOnRebalance则把刚才的模式镜像回来,按“先储备 -> 后用户 -> 再刷新利率”的顺序执行,保证索引与本金始终匹配。- 闪电贷路径中,
updateStateOnFlashLoan通过cumulateToLiquidityIndex(totalLiquidityBefore, income)把额外收益一次性打入流动性指数,再将protocolFee单独转给TokenDistributor,实现“收益给存款人,抽成给协议”的两段拆分。
关键函数片段如下(留意入口限定 onlyLendingPool,确保 Core 不直接暴露敏感操作):
function updateStateOnDeposit(
address _reserve,
address _user,
uint256 _amount,
bool _isFirstDeposit
) external onlyLendingPool {
// 累积上一个区块的收益,防止旧利率污染当前操作
reserves[_reserve].updateCumulativeIndexes();
// 将本次存入金额计入储备,刷新流动性利率与时间戳
updateReserveInterestRatesAndTimestampInternal(_reserve, _amount, 0);
if (_isFirstDeposit) {
setUserUseReserveAsCollateral(_reserve, _user, true); // 首次存款直接作为抵押
}
}
function updateStateOnBorrow(
address _reserve,
address _user,
uint256 _amountBorrowed,
uint256 _borrowFee,
CoreLibrary.InterestRateMode _rateMode
) external onlyLendingPool returns (uint256, uint256) {
(uint256 principalBorrowBalance, , uint256 balanceIncrease) = getUserBorrowBalances(_reserve, _user);
// 先把储备维度的借款统计更新,否则用户状态会依赖旧的 `totalBorrows`
updateReserveStateOnBorrowInternal(
_reserve,
_user,
principalBorrowBalance,
balanceIncrease,
_amountBorrowed,
_rateMode
);
// 再记录到用户维度:本金、费用与利率模式
updateUserStateOnBorrowInternal(
_reserve,
_user,
_amountBorrowed,
balanceIncrease,
_borrowFee,
_rateMode
);
// 借款等价于抽离流动性,因此在负方向刷新利率
updateReserveInterestRatesAndTimestampInternal(_reserve, 0, _amountBorrowed);
return (getUserCurrentBorrowRate(_reserve, _user), balanceIncrease);
}sequenceDiagram
LendingPool->>LendingPoolCore: updateStateOnDeposit()
LendingPoolCore->>CoreLibrary: updateCumulativeIndexes()
CoreLibrary-->>LendingPoolCore: 新的 liquidityIndex/variableIndex
LendingPool->>LendingPoolCore: updateStateOnBorrow()
LendingPoolCore->>CoreLibrary: updateReserveStateOnBorrowInternal()
LendingPoolCore-->>LendingPool: balanceIncrease/borrowRate
sequenceDiagram
LendingPool->>LendingPoolCore: updateStateOnDeposit()
LendingPoolCore->>CoreLibrary: updateCumulativeIndexes()
CoreLibrary-->>LendingPoolCore: 新的 liquidityIndex/variableIndex
LendingPool->>LendingPoolCore: updateStateOnBorrow()
LendingPoolCore->>CoreLibrary: updateReserveStateOnBorrowInternal()
LendingPoolCore-->>LendingPool: balanceIncrease/borrowRate
sequenceDiagram
LendingPool->>LendingPoolCore: updateStateOnDeposit()
LendingPoolCore->>CoreLibrary: updateCumulativeIndexes()
CoreLibrary-->>LendingPoolCore: 新的 liquidityIndex/variableIndex
LendingPool->>LendingPoolCore: updateStateOnBorrow()
LendingPoolCore->>CoreLibrary: updateReserveStateOnBorrowInternal()
LendingPoolCore-->>LendingPool: balanceIncrease/borrowRatesequenceDiagram
LendingPool->>LendingPoolCore: updateStateOnDeposit()
LendingPoolCore->>CoreLibrary: updateCumulativeIndexes()
CoreLibrary-->>LendingPoolCore: 新的 liquidityIndex/variableIndex
LendingPool->>LendingPoolCore: updateStateOnBorrow()
LendingPoolCore->>CoreLibrary: updateReserveStateOnBorrowInternal()
LendingPoolCore-->>LendingPool: balanceIncrease/borrowRate
3.3 资金入口与费用路径
- Core 对 ETH 与 ERC20 的资金流分别封装:
transferToReserve负责收款(ERC20 走safeTransferFrom,ETH 要求msg.value >= amount并主动退回多余部分);transferToUser则在出金时对 ETH 使用带 50k gas 的call(contracts/lendingpool/LendingPoolCore.sol:439-441)。 - 所有费用都走单独通道:
transferToFeeCollectionAddress用于借款开仓费、liquidateFee用于清算费,调用方需要显式传入_destination,从而把费用直接打到TokenDistributor。 - 合约
fallback()禁止 EOA 主动给 Core 汇入 ETH,只有合约才能向它转账,避免用户误充。闪电贷归还逻辑正是借助这一点,通过LendingPool检查 Core 的address(this).balance是否等于借出前余额加手续费。
function transferToUser(
address _reserve,
address payable _user,
uint256 _amount
) external onlyLendingPool {
if (_reserve != EthAddressLib.ethAddress()) {
// ERC20 路径直接将资产转出给用户
ERC20(_reserve).safeTransfer(_user, _amount);
} else {
// ETH 路径使用 call,固定 50k gas 以降低重入面
(bool result, ) = _user.call.value(_amount).gas(50000)("");
require(result, "Transfer of ETH failed");
}
}
function transferToReserve(
address _reserve,
address payable _user,
uint256 _amount
) external payable onlyLendingPool {
if (_reserve != EthAddressLib.ethAddress()) {
// ERC20 入金必须通过 `safeTransferFrom`,防止多余 ETH
require(msg.value == 0, "User is sending ETH along with the ERC20 transfer.");
ERC20(_reserve).safeTransferFrom(_user, address(this), _amount);
} else {
// ETH 入金需要附带足量 msg.value,并在多付时立刻退款
require(msg.value >= _amount, "The amount and the value sent to deposit do not match");
if (msg.value > _amount) {
uint256 excessAmount = msg.value.sub(_amount);
(bool result, ) = _user.call.value(excessAmount).gas(50000)("");
require(result, "Transfer of ETH failed");
}
}
}
function transferToFeeCollectionAddress(
address _token,
address _user,
uint256 _amount,
address _destination
) external payable onlyLendingPool {
address payable feeAddress = address(uint160(_destination));
if (_token != EthAddressLib.ethAddress()) {
// 借款费的 ERC20 路径直接由 Core 从用户账户划走
require(msg.value == 0, "User is sending ETH along with the ERC20 transfer...");
ERC20(_token).safeTransferFrom(_user, feeAddress, _amount);
} else {
// ETH 费用需要携带 value,并直接转发给 TokenDistributor
require(msg.value >= _amount, "The amount and the value sent to deposit do not match");
(bool result, ) = feeAddress.call.value(_amount).gas(50000)("");
require(result, "Transfer of ETH failed");
}
}sequenceDiagram
LendingPool->>LendingPoolCore: transferToReserve()
alt ERC20
LendingPoolCore->>ERC20: safeTransferFrom(user, core, amount)
else ETH
LendingPoolCore->>User: refund excess ETH
end
LendingPool->>LendingPoolCore: transferToUser()
LendingPoolCore-->>User: ERC20/ETH
LendingPool->>LendingPoolCore: transferToFeeCollectionAddress()
LendingPoolCore-->>TokenDistributor: 协议费用
sequenceDiagram
LendingPool->>LendingPoolCore: transferToReserve()
alt ERC20
LendingPoolCore->>ERC20: safeTransferFrom(user, core, amount)
else ETH
LendingPoolCore->>User: refund excess ETH
end
LendingPool->>LendingPoolCore: transferToUser()
LendingPoolCore-->>User: ERC20/ETH
LendingPool->>LendingPoolCore: transferToFeeCollectionAddress()
LendingPoolCore-->>TokenDistributor: 协议费用
sequenceDiagram
LendingPool->>LendingPoolCore: transferToReserve()
alt ERC20
LendingPoolCore->>ERC20: safeTransferFrom(user, core, amount)
else ETH
LendingPoolCore->>User: refund excess ETH
end
LendingPool->>LendingPoolCore: transferToUser()
LendingPoolCore-->>User: ERC20/ETH
LendingPool->>LendingPoolCore: transferToFeeCollectionAddress()
LendingPoolCore-->>TokenDistributor: 协议费用sequenceDiagram
LendingPool->>LendingPoolCore: transferToReserve()
alt ERC20
LendingPoolCore->>ERC20: safeTransferFrom(user, core, amount)
else ETH
LendingPoolCore->>User: refund excess ETH
end
LendingPool->>LendingPoolCore: transferToUser()
LendingPoolCore-->>User: ERC20/ETH
LendingPool->>LendingPoolCore: transferToFeeCollectionAddress()
LendingPoolCore-->>TokenDistributor: 协议费用
3.4 利用率 :白皮书 §1.2.3 ↔ getReserveUtilizationRate
公式:(当 )
contracts/lendingpool/LendingPoolCore.sol:962-975直接以totalBorrows.rayDiv(availableLiquidity.add(totalBorrows))计算 RAY 精度的 (若 totalBorrows 和 availableLiquidity 均为 0,则 U=0)。这里的rayDiv就是白皮书中提到的“按 精度表示的利用率”。totalBorrows由CoreLibrary.ReserveData.getTotalBorrows()给出,即 ;availableLiquidity为核心合约持有的实际余额(Balance);totalLiquidity = availableLiquidity + totalBorrows,对应白皮书的 。- 该函数被
updateReserveInterestRatesAndTimestampInternal()调用,将 传入DefaultReserveInterestRateStrategy.calculateInterestRates(),触发白皮书 §1.2.4 描述的分段利率曲线。 - 当 时返回 0,避免了除零情况,也符合白皮书对“无人借款时资金成本为 0”的假设。
3.5 指数体系:白皮书 §1.2.8 的链上实现
,, 为“不落地的指数快照”。
contracts/libraries/CoreLibrary.sol:94-155的getNormalizedIncome()、updateCumulativeIndexes()完整复现上述公式:calculateLinearInterest(currentLiquidityRate, lastUpdateTimestamp)等价于 。calculateCompoundedInterest(currentVariableBorrowRate, lastUpdateTimestamp)借助WadRayMath.rayPow计算 。- 把二者分别乘上
lastLiquidityCumulativeIndex、lastVariableBorrowCumulativeIndex,即可得到白皮书所述的惰性累计。
contracts/lendingpool/LendingPoolCore.sol:1407-1476所有updateStateOn*在修改本金前都会先调用reserves[_reserve].updateCumulativeIndexes(),使用的是和白皮书一样的“先补齐指数,再变更本金”的顺序。AToken.balanceOf()依赖core.getReserveNormalizedIncome()(白皮书的 ),将指数差转换为余额增长,见 §4.1。
3.6 利率刷新:白皮书 §1.2.4/§1.2.7 ↔ DefaultReserveInterestRateStrategy
contracts/lendingpool/DefaultReserveInterestRateStrategy.sol:1180-1218 内部重新计算 utilizationRate = totalBorrows.rayDiv(available.add(totalBorrows)),并按照白皮书的 Kink 曲线输出浮动/稳定/流动性利率:
- 当 :
currentVariableBorrowRate = baseVariableBorrowRate + (U/U_{optimal}) * variableRateSlope1currentStableBorrowRate = marketBorrowRate + (U/U_{optimal}) * stableRateSlope1
- 当 :
- 先算
excess = (U - U_{optimal}) / (1 - U_{optimal}) - 变量/稳定利率分别额外叠加
slope2 * excess
- 先算
currentLiquidityRate = getOverallBorrowRateInternal(...) * utilizationRate,正是白皮书公式 。
这些新利率由 LendingPoolCore.updateReserveInterestRatesAndTimestampInternal() 写回 ReserveData,再由指数函数消化,形成“公式 → 状态 → 余额”的闭环。
3.7 数据出口与抵押开关
- Core 自身提供
getUserBasicReserveData、getReserveConfiguration、getReserveNormalizedIncome、getReserveCurrent[Stable|Variable]BorrowRate等视图函数,DataProvider 直接从这里取原始值,外部前端不用触碰复杂的存储结构。 isUserAllowedToBorrowAtStable、isReserveBorrowingEnabled、isUserUseReserveAsCollateralEnabled这些布尔接口被入口层频繁调用,用于组合出“是否可以进行稳定利率借款”“余额减少是否被允许”等策略。- 对于上层治理,
LendingPoolConfigurator只是代理,把各种 set/enable/disable 直接委托给 Core 执行,Core 保持最小权限并只负责最终数据写入。
function getUserBasicReserveData(address _reserve, address _user)
external
view
returns (uint256, uint256, uint256, bool)
{
CoreLibrary.ReserveData storage reserve = reserves[_reserve];
CoreLibrary.UserReserveData storage user = usersReserveData[_user][_reserve];
uint256 underlyingBalance = getUserUnderlyingAssetBalance(_reserve, _user);
if (user.principalBorrowBalance == 0) {
// 没有借款时只需要返回存款余额与抵押标记
return (underlyingBalance, 0, 0, user.useAsCollateral);
}
return (
underlyingBalance,
// 通过用户的借款指数计算复利后的债务
user.getCompoundedBorrowBalance(reserve),
user.originationFee,
user.useAsCollateral
);
}
function isUserAllowedToBorrowAtStable(
address _reserve,
address _user,
uint256 _amount
) external view returns (bool) {
CoreLibrary.ReserveData storage reserve = reserves[_reserve];
CoreLibrary.UserReserveData storage user = usersReserveData[_user][_reserve];
if (!reserve.isStableBorrowRateEnabled) return false;
return
// 若用户未将该储备作为抵押或储备本身不可抵押,则允许稳定模式
!user.useAsCollateral ||
!reserve.usageAsCollateralEnabled ||
// 否则要求借款金额大于其抵押余额,避免“同资产自借”套利
_amount > getUserUnderlyingAssetBalance(_reserve, _user);
}sequenceDiagram
LendingPool->>LendingPoolCore: getUserBasicReserveData()
LendingPoolCore-->>LendingPool: (liquidity, borrow, fee, collateralFlag)
LendingPool->>LendingPoolCore: isUserAllowedToBorrowAtStable()
LendingPoolCore-->>LendingPool: true/false
DataProvider->>LendingPoolCore: getReserveConfiguration()
sequenceDiagram
LendingPool->>LendingPoolCore: getUserBasicReserveData()
LendingPoolCore-->>LendingPool: (liquidity, borrow, fee, collateralFlag)
LendingPool->>LendingPoolCore: isUserAllowedToBorrowAtStable()
LendingPoolCore-->>LendingPool: true/false
DataProvider->>LendingPoolCore: getReserveConfiguration()
sequenceDiagram
LendingPool->>LendingPoolCore: getUserBasicReserveData()
LendingPoolCore-->>LendingPool: (liquidity, borrow, fee, collateralFlag)
LendingPool->>LendingPoolCore: isUserAllowedToBorrowAtStable()
LendingPoolCore-->>LendingPool: true/false
DataProvider->>LendingPoolCore: getReserveConfiguration()sequenceDiagram
LendingPool->>LendingPoolCore: getUserBasicReserveData()
LendingPoolCore-->>LendingPool: (liquidity, borrow, fee, collateralFlag)
LendingPool->>LendingPoolCore: isUserAllowedToBorrowAtStable()
LendingPoolCore-->>LendingPool: true/false
DataProvider->>LendingPoolCore: getReserveConfiguration()
4 AToken 与 DataProvider:凭证与估值
AToken 作为 LendingPoolCore 上资产的凭证,负责把 liquidityIndex 映射到 ERC20 余额,并通过 LendingPoolDataProvider 产生的全局数据来限制转账、计算健康度。本章聚焦两个文件:contracts/tokenization/AToken.sol 与 contracts/lendingpool/LendingPoolDataProvider.sol。
4.1 aToken 的指数余额模型
mintOnDeposit、burnOnLiquidation、transferOnLiquidation都只能被LendingPool调用;铸造或销毁之前,cumulateBalanceInternal会把用户的balanceIncrease铸造成一小段额外 aToken,并刷新userIndexes[address] = core.getReserveNormalizedIncome().redeem先累加余额、校验isTransferAllowed,再调用pool.redeemUnderlying把资产解锁,同时在余额被清零时通过resetDataOnZeroBalanceInternal清除利息重定向与用户索引。balanceOf会把本金与重定向来的余额一起乘以core.getReserveNormalizedIncome / userIndex,totalSupply也用同样的指数,所以存款人无需单独领取收益,余额在链上自动随时间增长。
function mintOnDeposit(address _account, uint256 _amount) external onlyLendingPool {
// 先把已有利息累积到本金,保证指数与余额同步
(, , uint256 balanceIncrease, uint256 index) = cumulateBalanceInternal(_account);
updateRedirectedBalanceOfRedirectionAddressInternal(
_account,
balanceIncrease.add(_amount),
0
); // 若用户正在 redirect,先把新增余额同步给重定向地址
_mint(_account, _amount);
emit MintOnDeposit(_account, _amount, balanceIncrease, index);
}
function redeem(uint256 _amount) external {
(, uint256 currentBalance, uint256 balanceIncrease, uint256 index) = cumulateBalanceInternal(msg.sender);
uint256 amountToRedeem = _amount == UINT_MAX_VALUE ? currentBalance : _amount;
require(amountToRedeem <= currentBalance, "User cannot redeem more than the available balance");
require(isTransferAllowed(msg.sender, amountToRedeem), "Transfer cannot be allowed.");
updateRedirectedBalanceOfRedirectionAddressInternal(
msg.sender,
balanceIncrease,
amountToRedeem
);
_burn(msg.sender, amountToRedeem);
// 若余额被清空,重置利息重定向以减少存储
bool userIndexReset = currentBalance.sub(amountToRedeem) == 0 && resetDataOnZeroBalanceInternal(msg.sender);
pool.redeemUnderlying(underlyingAssetAddress, msg.sender, amountToRedeem, currentBalance.sub(amountToRedeem));
emit Redeem(msg.sender, amountToRedeem, balanceIncrease, userIndexReset ? 0 : index);
}sequenceDiagram
LendingPool->>AToken: mintOnDeposit(account, amount)
AToken->>LendingPoolCore: cumulateBalanceInternal(account)
LendingPoolCore-->>AToken: normalizedIncome/index
AToken-->>User: 铸造 aToken
User->>AToken: redeem(amount)
AToken->>LendingPool: redeemUnderlying(...)
sequenceDiagram
LendingPool->>AToken: mintOnDeposit(account, amount)
AToken->>LendingPoolCore: cumulateBalanceInternal(account)
LendingPoolCore-->>AToken: normalizedIncome/index
AToken-->>User: 铸造 aToken
User->>AToken: redeem(amount)
AToken->>LendingPool: redeemUnderlying(...)
sequenceDiagram
LendingPool->>AToken: mintOnDeposit(account, amount)
AToken->>LendingPoolCore: cumulateBalanceInternal(account)
LendingPoolCore-->>AToken: normalizedIncome/index
AToken-->>User: 铸造 aToken
User->>AToken: redeem(amount)
AToken->>LendingPool: redeemUnderlying(...)sequenceDiagram
LendingPool->>AToken: mintOnDeposit(account, amount)
AToken->>LendingPoolCore: cumulateBalanceInternal(account)
LendingPoolCore-->>AToken: normalizedIncome/index
AToken-->>User: 铸造 aToken
User->>AToken: redeem(amount)
AToken->>LendingPool: redeemUnderlying(...)
4.2 健康因子 与 DataProvider:白皮书 §1.1/§1.2.10
白皮书公式:
contracts/lendingpool/LendingPoolDataProvider.sol:70-155的calculateUserGlobalData会:- 遍历所有储备,以预言机价格把存款/借款折算成 ETH;
- 按照储备的
baseLtv与liquidationThreshold加权求出全局 与 ; - 返回
healthFactor,其内部调用calculateHealthFactorFromBalancesInternal,完全等价于白皮书的 公式。
calculateHealthFactorFromBalancesInternal(L322-L334)直接复刻公式,且以HEALTH_FACTOR_LIQUIDATION_THRESHOLD = 1e18作为“是否低于 1”的判断基准。- 需要注意
currentLiquidationThreshold在储备配置中是以“百分比(0-100)”储存的,因此函数内部先执行collateralBalanceETH.mul(liquidationThreshold).div(100)把抵押价值按阈值折算,再交给wadDiv以 1e18 精度除以借款与费用。白皮书里的 同样是 0-1 的比率,所以链上实现与公式在单位上保持一致。 balanceDecreaseAllowed(L180-L247)在 aToken 转账/赎回前调用同样的健康因子计算:把要转出的_amount转为 ETH,重新计算 与新的抵押余额,再判断healthFactorAfterDecrease > 1e18。这就是白皮书 §3.2/§3.3 中“赎回/转移必须保持 ”的实现。calculateCollateralNeededInETH(L258-L284)则给出了“为了新增借款需要多少抵押”的推导,依赖前面求得的加权 LTV,和白皮书 §3.3 的理论一致。
function calculateHealthFactorFromBalancesInternal(
uint256 _collateralBalanceETH,
uint256 _borrowBalanceETH,
uint256 _totalFeesETH,
uint256 _currentLiquidationThreshold
) internal pure returns (uint256) {
if (_borrowBalanceETH == 0) return uint256(-1);
return
_collateralBalanceETH
.wadMul(_currentLiquidationThreshold)
.wadDiv(_borrowBalanceETH.add(_totalFeesETH));
}wadMul/wadDiv 保证 以 精度计算,避免了浮点误差。
4.3 清算二次校验:balanceDecreaseAllowed 与 liquidationCall
- 当清算人调用
LendingPool.liquidationCall时,LendingPoolLiquidationManager会再次通过calculateUserGlobalData获取healthFactorBelowThreshold(contracts/lendingpool/LendingPoolLiquidationManager.sol:534-542),只有 才会继续执行,这与白皮书“以健康因子判断是否可被清算”保持一致。 calculateAvailableCollateralToLiquidate(L585-L600)是白皮书清算公式的链上版本: 当抵押不足以覆盖Amount时,会反推principalAmountNeeded,实现白皮书所述“按比例扣抵押”的机制。- Origination fee 清算逻辑(L591-L605)则将费用视作额外的
Amount再跑一次上述公式,确保费用也能按 liquidation bonus 折价买入。
4.4 转账与利息重定向(延续原笔记)
- 所有
_transfer都走whenTransferAllowed修饰器,它会调用balanceDecreaseAllowed,因此上一节的健康因子计算同样适用于普通转账/赎回。 executeTransferInternal在实际转账前会分别对from和to做一次cumulateBalanceInternal,并通过updateRedirectedBalanceOfRedirectionAddressInternal把应计利息同步到重定向地址。这样即便用户开启了redirectInterestStream,其委托人也能接收到每一笔余额变化。redirectInterestStream/redirectInterestStreamOf允许用户或被授权者把未来利息重定向到另一个地址,allowInterestRedirectionTo和redirectedBalances则负责授信与记账;清算与赎回完成后若余额归零,利息重定向也会被自动重置。
function executeTransferInternal(
address _from,
address _to,
uint256 _value
) internal {
require(_value > 0, "Transferred amount needs to be greater than zero");
(, uint256 fromBalance, uint256 fromBalanceIncrease, uint256 fromIndex) = cumulateBalanceInternal(_from);
(, , uint256 toBalanceIncrease, uint256 toIndex) = cumulateBalanceInternal(_to);
updateRedirectedBalanceOfRedirectionAddressInternal(_from, fromBalanceIncrease, _value);
updateRedirectedBalanceOfRedirectionAddressInternal(_to, toBalanceIncrease.add(_value), 0);
super._transfer(_from, _to, _value);
bool fromIndexReset = fromBalance.sub(_value) == 0 && resetDataOnZeroBalanceInternal(_from);
emit BalanceTransfer(
_from,
_to,
_value,
fromBalanceIncrease,
toBalanceIncrease,
fromIndexReset ? 0 : fromIndex,
toIndex
);
}5 数学底座:WadRayMath 与利率分段策略
5.1 Wad/Ray 精度:白皮书 §1.2.8 的数值基础
公式中的所有利率、指数都以 ray()或 wad()表示
contracts/libraries/WadRayMath.sol:37-84定义了wadMul/wadDiv/rayMul/rayDiv,它们分别实现 通过在分子加半个单位实现四舍五入(half-up rounding),而非银行家舍入(ties-to-even),从而保证利率/指数乘除不会因为 Solidityuint256整除而失去精度。wadToRay/rayToWad用WAD_RAY_RATIO = 1e9在两个精度之间转换,供calculateHealthFactorFromBalancesInternal等函数把 LTV/HealthFactor(wad)与指数(ray)混用。rayPow则是白皮书 的实现:它通过“二进制快速幂 + 每次迭代的rayMul”计算指数函数,供CoreLibrary.calculateCompoundedInterest调用。因为rayPow只接受 ray 单位,整个协议的复利计算才能保持高精度。
5.2 分段利率策略再访
DefaultReserveInterestRateStrategy的 piecewise 函数前面已经在 §3.6 讨论,这里强调该策略与WadRayMath完全绑定:- 所有 slope/基础利率都以 ray 表示;
utilizationRate、excessUtilizationRateRatio使用rayDiv,避免浮点误差;getOverallBorrowRateInternal将稳定/浮动借款金额先wadToRay再rayMul,与白皮书“按金额加权”一致。
- 这种“Ray 精度 + 两段斜率”的组合确保白皮书中的连续函数可以直接映射为 Solidity 算术。
function redirectInterestStreamInternal(address _from, address _to) internal {
address currentRedirectionAddress = interestRedirectionAddresses[_from];
require(_to != currentRedirectionAddress, "Interest is already redirected to the user");
(
uint256 previousPrincipalBalance,
uint256 fromBalance,
uint256 balanceIncrease,
uint256 fromIndex
) = cumulateBalanceInternal(_from);
require(fromBalance > 0, "Interest stream can only be redirected if there is a valid balance");
if (currentRedirectionAddress != address(0)) {
// 旧重定向账户需要减去用户本金,否则会重复计息
updateRedirectedBalanceOfRedirectionAddressInternal(_from, 0, previousPrincipalBalance);
}
if (_to == _from) {
// 将利息重定向给自己意味着撤销重定向
interestRedirectionAddresses[_from] = address(0);
emit InterestStreamRedirected(_from, address(0), fromBalance, balanceIncrease, fromIndex);
return;
}
interestRedirectionAddresses[_from] = _to;
updateRedirectedBalanceOfRedirectionAddressInternal(_from, fromBalance, 0);
emit InterestStreamRedirected(_from, _to, fromBalance, balanceIncrease, fromIndex);
}sequenceDiagram
User->>AToken: transfer(to, value)
AToken->>LendingPoolDataProvider: balanceDecreaseAllowed()
AToken->>LendingPoolCore: cumulateBalanceInternal(from/to)
AToken->>AToken: updateRedirectedBalanceOfRedirectionAddressInternal()
AToken-->>User: BalanceTransfer 事件
User->>AToken: redirectInterestStream(target)
AToken->>LendingPoolCore: cumulateBalanceInternal(user)
sequenceDiagram
User->>AToken: transfer(to, value)
AToken->>LendingPoolDataProvider: balanceDecreaseAllowed()
AToken->>LendingPoolCore: cumulateBalanceInternal(from/to)
AToken->>AToken: updateRedirectedBalanceOfRedirectionAddressInternal()
AToken-->>User: BalanceTransfer 事件
User->>AToken: redirectInterestStream(target)
AToken->>LendingPoolCore: cumulateBalanceInternal(user)
sequenceDiagram
User->>AToken: transfer(to, value)
AToken->>LendingPoolDataProvider: balanceDecreaseAllowed()
AToken->>LendingPoolCore: cumulateBalanceInternal(from/to)
AToken->>AToken: updateRedirectedBalanceOfRedirectionAddressInternal()
AToken-->>User: BalanceTransfer 事件
User->>AToken: redirectInterestStream(target)
AToken->>LendingPoolCore: cumulateBalanceInternal(user)sequenceDiagram
User->>AToken: transfer(to, value)
AToken->>LendingPoolDataProvider: balanceDecreaseAllowed()
AToken->>LendingPoolCore: cumulateBalanceInternal(from/to)
AToken->>AToken: updateRedirectedBalanceOfRedirectionAddressInternal()
AToken-->>User: BalanceTransfer 事件
User->>AToken: redirectInterestStream(target)
AToken->>LendingPoolCore: cumulateBalanceInternal(user)
4.3 LendingPoolDataProvider 的聚合视图
calculateUserGlobalData遍历core.getReserves(),为每个储备读取getUserBasicReserveData和getReserveConfiguration,再结合IPriceOracleGetter估值成 ETH,计算totalLiquidityBalanceETH/totalCollateralBalanceETH/totalBorrowBalanceETH/totalFeesETH、加权 LTV 与 Liquidation Threshold,并输出healthFactor与healthFactorBelowThreshold。balanceDecreaseAllowed、calculateCollateralNeededInETH、calculateAvailableBorrowsETHInternal和calculateHealthFactorFromBalancesInternal是入口层风险控制的支撑:前者用于 aToken 转账/赎回时的余额减少校验,后两者则告诉借款路径“想再借多少需要多少抵押物”“当前抵押还能借多少 ETH”。- 对前端或分析工具,
getReserveData、getReserveConfigurationData、getUserAccountData与getUserReserveData提供了完整的储备与账户视图,免去了直接读 Core 底层存储的复杂度。
function calculateUserGlobalData(address _user)
public
view
returns (
uint256 totalLiquidityBalanceETH,
uint256 totalCollateralBalanceETH,
uint256 totalBorrowBalanceETH,
uint256 totalFeesETH,
uint256 currentLtv,
uint256 currentLiquidationThreshold,
uint256 healthFactor,
bool healthFactorBelowThreshold
)
{
IPriceOracleGetter oracle = IPriceOracleGetter(addressesProvider.getPriceOracle());
address[] memory reserves = core.getReserves();
for (uint256 i = 0; i < reserves.length; i++) {
(
uint256 compoundedLiquidityBalance,
uint256 compoundedBorrowBalance,
uint256 originationFee,
bool userUsesReserveAsCollateral
) = core.getUserBasicReserveData(reserves[i], _user);
if (compoundedLiquidityBalance == 0 && compoundedBorrowBalance == 0) continue;
(
uint256 reserveDecimals,
uint256 baseLtv,
uint256 liquidationThreshold,
bool usageAsCollateralEnabled
) = core.getReserveConfiguration(reserves[i]);
uint256 tokenUnit = 10 ** reserveDecimals;
uint256 reserveUnitPrice = oracle.getAssetPrice(reserves[i]);
if (compoundedLiquidityBalance > 0) {
// 使用预言机价格将存款折算成 ETH
uint256 liquidityBalanceETH = reserveUnitPrice.mul(compoundedLiquidityBalance).div(tokenUnit);
totalLiquidityBalanceETH = totalLiquidityBalanceETH.add(liquidityBalanceETH);
if (usageAsCollateralEnabled && userUsesReserveAsCollateral) {
// 只有开启抵押的储备才会累加到 collateral
totalCollateralBalanceETH = totalCollateralBalanceETH.add(liquidityBalanceETH);
currentLtv = currentLtv.add(liquidityBalanceETH.mul(baseLtv));
currentLiquidationThreshold = currentLiquidationThreshold.add(
liquidityBalanceETH.mul(liquidationThreshold)
);
}
}
if (compoundedBorrowBalance > 0) {
// 借款和 origination fee 同样按 ETH 计价
totalBorrowBalanceETH = totalBorrowBalanceETH.add(
reserveUnitPrice.mul(compoundedBorrowBalance).div(tokenUnit)
);
totalFeesETH = totalFeesETH.add(
originationFee.mul(reserveUnitPrice).div(tokenUnit)
);
}
}
currentLtv = totalCollateralBalanceETH > 0 ? currentLtv.div(totalCollateralBalanceETH) : 0;
currentLiquidationThreshold = totalCollateralBalanceETH > 0
? currentLiquidationThreshold.div(totalCollateralBalanceETH)
: 0;
// Health Factor < 1 即可被清算
healthFactor = calculateHealthFactorFromBalancesInternal(
totalCollateralBalanceETH,
totalBorrowBalanceETH,
totalFeesETH,
currentLiquidationThreshold
);
healthFactorBelowThreshold = healthFactor < HEALTH_FACTOR_LIQUIDATION_THRESHOLD;
}sequenceDiagram
Frontend->>LendingPoolDataProvider: calculateUserGlobalData(user)
DataProvider->>LendingPoolCore: getReserves()
loop 每个储备
DataProvider->>LendingPoolCore: getUserBasicReserveData()
DataProvider->>LendingPoolCore: getReserveConfiguration()
DataProvider->>PriceOracle: getAssetPrice(reserve)
end
DataProvider-->>Frontend: collateral/borrow/healthFactor
AToken->>LendingPoolDataProvider: balanceDecreaseAllowed()
sequenceDiagram
Frontend->>LendingPoolDataProvider: calculateUserGlobalData(user)
DataProvider->>LendingPoolCore: getReserves()
loop 每个储备
DataProvider->>LendingPoolCore: getUserBasicReserveData()
DataProvider->>LendingPoolCore: getReserveConfiguration()
DataProvider->>PriceOracle: getAssetPrice(reserve)
end
DataProvider-->>Frontend: collateral/borrow/healthFactor
AToken->>LendingPoolDataProvider: balanceDecreaseAllowed()
sequenceDiagram
Frontend->>LendingPoolDataProvider: calculateUserGlobalData(user)
DataProvider->>LendingPoolCore: getReserves()
loop 每个储备
DataProvider->>LendingPoolCore: getUserBasicReserveData()
DataProvider->>LendingPoolCore: getReserveConfiguration()
DataProvider->>PriceOracle: getAssetPrice(reserve)
end
DataProvider-->>Frontend: collateral/borrow/healthFactor
AToken->>LendingPoolDataProvider: balanceDecreaseAllowed()sequenceDiagram
Frontend->>LendingPoolDataProvider: calculateUserGlobalData(user)
DataProvider->>LendingPoolCore: getReserves()
loop 每个储备
DataProvider->>LendingPoolCore: getUserBasicReserveData()
DataProvider->>LendingPoolCore: getReserveConfiguration()
DataProvider->>PriceOracle: getAssetPrice(reserve)
end
DataProvider-->>Frontend: collateral/borrow/healthFactor
AToken->>LendingPoolDataProvider: balanceDecreaseAllowed()
function balanceDecreaseAllowed(address _reserve, address _user, uint256 _amount)
external
view
returns (bool)
{
balanceDecreaseAllowedLocalVars memory vars;
(
vars.decimals,
,
vars.reserveLiquidationThreshold,
vars.reserveUsageAsCollateralEnabled
) = core.getReserveConfiguration(_reserve);
if (
!vars.reserveUsageAsCollateralEnabled ||
!core.isUserUseReserveAsCollateralEnabled(_reserve, _user)
) {
return true;
}
(
,
vars.collateralBalanceETH,
vars.borrowBalanceETH,
vars.totalFeesETH,
,
vars.currentLiquidationThreshold,
,
) = calculateUserGlobalData(_user);
if (vars.borrowBalanceETH == 0) {
// 没有借款则随意转出
return true;
}
IPriceOracleGetter oracle = IPriceOracleGetter(addressesProvider.getPriceOracle());
vars.amountToDecreaseETH = oracle.getAssetPrice(_reserve).mul(_amount).div(10 ** vars.decimals);
vars.collateralBalancefterDecrease = vars.collateralBalanceETH.sub(vars.amountToDecreaseETH);
if (vars.collateralBalancefterDecrease == 0) {
return false;
}
vars.liquidationThresholdAfterDecrease = vars
.collateralBalanceETH
.mul(vars.currentLiquidationThreshold)
.sub(vars.amountToDecreaseETH.mul(vars.reserveLiquidationThreshold))
.div(vars.collateralBalancefterDecrease);
uint256 healthFactorAfterDecrease = calculateHealthFactorFromBalancesInternal(
vars.collateralBalancefterDecrease,
vars.borrowBalanceETH,
vars.totalFeesETH,
vars.liquidationThresholdAfterDecrease
);
// 只有在转出后 health factor 仍大于阈值时才允许
return healthFactorAfterDecrease > HEALTH_FACTOR_LIQUIDATION_THRESHOLD;
}5 策略与治理模块
入口层与核心层之间还有一组“控制平面”合约,负责利率曲线、费用、参数治理与地址注册,确保状态与策略可以独立升级。
5.1 利率策略:DefaultReserveInterestRateStrategy
DefaultReserveInterestRateStrategy在构造时固定OPTIMAL_UTILIZATION_RATE = 80%,把利用率划成两段 Kink 曲线:未超过拐点时按baseVariableBorrowRate + utilization/optimal * slope1增长,一旦突破则用slope2快速抬升利率。- 稳定利率部分以上述曲线叠加
ILendingRateOracle提供的市场利率;变量利率则直接围绕baseVariableBorrowRate调整。calculateInterestRates返回 (liquidityRate, stableBorrowRate, variableBorrowRate),再由 Core 缓存成储备的当前利率。 getOverallBorrowRateInternal把稳定/浮动借款金额按权重换算成单一利率,供liquidityRate = utilization * overallBorrowRate使用,从而把借款收入在指数中摊给存款人。
function calculateInterestRates(
address _reserve,
uint256 _availableLiquidity,
uint256 _totalBorrowsStable,
uint256 _totalBorrowsVariable,
uint256 _averageStableBorrowRate
)
external
view
returns (uint256 currentLiquidityRate, uint256 currentStableBorrowRate, uint256 currentVariableBorrowRate)
{
uint256 totalBorrows = _totalBorrowsStable.add(_totalBorrowsVariable);
uint256 utilizationRate = (totalBorrows == 0 && _availableLiquidity == 0)
? 0
: totalBorrows.rayDiv(_availableLiquidity.add(totalBorrows));
// 稳定利率以预言机的市场借款利率为基线
currentStableBorrowRate = ILendingRateOracle(addressesProvider.getLendingRateOracle())
.getMarketBorrowRate(_reserve);
if (utilizationRate > OPTIMAL_UTILIZATION_RATE) {
uint256 excessUtilizationRateRatio = utilizationRate
.sub(OPTIMAL_UTILIZATION_RATE)
.rayDiv(EXCESS_UTILIZATION_RATE);
// 超过拐点后使用 slope2 快速抬升借款利率
currentStableBorrowRate = currentStableBorrowRate
.add(stableRateSlope1)
.add(stableRateSlope2.rayMul(excessUtilizationRateRatio));
currentVariableBorrowRate = baseVariableBorrowRate
.add(variableRateSlope1)
.add(variableRateSlope2.rayMul(excessUtilizationRateRatio));
} else {
// 未超过拐点时按 slope1 线性插值
currentStableBorrowRate = currentStableBorrowRate.add(
stableRateSlope1.rayMul(
utilizationRate.rayDiv(OPTIMAL_UTILIZATION_RATE)
)
);
currentVariableBorrowRate = baseVariableBorrowRate.add(
utilizationRate.rayDiv(OPTIMAL_UTILIZATION_RATE).rayMul(variableRateSlope1)
);
}
currentLiquidityRate = getOverallBorrowRateInternal(
_totalBorrowsStable,
_totalBorrowsVariable,
currentVariableBorrowRate,
_averageStableBorrowRate
).rayMul(utilizationRate); // 存款收益 = 利用率 * 借款平均利率
}sequenceDiagram
LendingPoolCore->>InterestRateStrategy: calculateInterestRates(reserveData)
InterestRateStrategy->>LendingRateOracle: getMarketBorrowRate()
InterestRateStrategy-->>LendingPoolCore: (liquidityRate, stableRate, variableRate)
LendingPoolCore-->>Reserves: 更新 currentLiquidityRate/currentBorrowRates
sequenceDiagram
LendingPoolCore->>InterestRateStrategy: calculateInterestRates(reserveData)
InterestRateStrategy->>LendingRateOracle: getMarketBorrowRate()
InterestRateStrategy-->>LendingPoolCore: (liquidityRate, stableRate, variableRate)
LendingPoolCore-->>Reserves: 更新 currentLiquidityRate/currentBorrowRates
sequenceDiagram
LendingPoolCore->>InterestRateStrategy: calculateInterestRates(reserveData)
InterestRateStrategy->>LendingRateOracle: getMarketBorrowRate()
InterestRateStrategy-->>LendingPoolCore: (liquidityRate, stableRate, variableRate)
LendingPoolCore-->>Reserves: 更新 currentLiquidityRate/currentBorrowRatessequenceDiagram
LendingPoolCore->>InterestRateStrategy: calculateInterestRates(reserveData)
InterestRateStrategy->>LendingRateOracle: getMarketBorrowRate()
InterestRateStrategy-->>LendingPoolCore: (liquidityRate, stableRate, variableRate)
LendingPoolCore-->>Reserves: 更新 currentLiquidityRate/currentBorrowRates
5.2 费用与参数提供者
FeeProvider目前把originationFeePercentage固定为 0.25% (0.0025 * 1e18),calculateLoanOriginationFee简单地对_amount做wadMul。如果未来要做折扣或分级收费,也可以通过替换实现来完成。LendingPoolParametersProvider给入口层提供全局的常量,如MAX_STABLE_RATE_BORROW_SIZE_PERCENT = 25(单次稳定利率借款最多占可用流动性的 25%)、REBALANCE_DOWN_RATE_DELTA(触发稳定利率重平衡的阈值)以及闪电贷费率(FLASHLOAN_FEE_TOTAL=35, FLASHLOAN_FEE_PROTOCOL=3000)。这些值直接被LendingPool.borrow、flashLoan和LendingPoolLiquidationManager使用。
function calculateLoanOriginationFee(address _user, uint256 _amount)
external
view
returns (uint256)
{
// 目前未根据 _user 区分费率,直接返回金额 * 0.25%
return _amount.wadMul(originationFeePercentage);
}
function getFlashLoanFeesInBips() external pure returns (uint256, uint256) {
// (总费率, 协议分润);其余部分将流向存款人
return (FLASHLOAN_FEE_TOTAL, FLASHLOAN_FEE_PROTOCOL);
}sequenceDiagram
LendingPool->>FeeProvider: calculateLoanOriginationFee(user, amount)
FeeProvider-->>LendingPool: amount * originationFee%
LendingPool->>ParametersProvider: getFlashLoanFeesInBips()
ParametersProvider-->>LendingPool: (totalFee, protocolFee)
sequenceDiagram
LendingPool->>FeeProvider: calculateLoanOriginationFee(user, amount)
FeeProvider-->>LendingPool: amount * originationFee%
LendingPool->>ParametersProvider: getFlashLoanFeesInBips()
ParametersProvider-->>LendingPool: (totalFee, protocolFee)
sequenceDiagram
LendingPool->>FeeProvider: calculateLoanOriginationFee(user, amount)
FeeProvider-->>LendingPool: amount * originationFee%
LendingPool->>ParametersProvider: getFlashLoanFeesInBips()
ParametersProvider-->>LendingPool: (totalFee, protocolFee)sequenceDiagram
LendingPool->>FeeProvider: calculateLoanOriginationFee(user, amount)
FeeProvider-->>LendingPool: amount * originationFee%
LendingPool->>ParametersProvider: getFlashLoanFeesInBips()
ParametersProvider-->>LendingPool: (totalFee, protocolFee)
5.3 配置器:LendingPoolConfigurator
LendingPoolConfigurator只允许addressesProvider.getLendingPoolManager()调用,负责所有储备级别的治理操作。initReserve会部署一个新的AToken、把它注册到 Core,并绑定外部提供的利率策略;removeLastAddedReserve则可删除尾部储备。- 参数修改方面,包括
enable/disableBorrowingOnReserve、enableReserveAsCollateral、setReserveBaseLTVasCollateral、setReserveLiquidationThreshold/Bonus/Decimals、enableReserveStableBorrowRate、activate/deactivate/freeze/unfreezeReserve等函数。治理合约或多签只需调用 Configurator,就能同步调整 Core 内的所有风险字段。
function initReserve(
address _reserve,
uint8 _underlyingAssetDecimals,
address _interestRateStrategyAddress
) external onlyLendingPoolManager {
ERC20Detailed asset = ERC20Detailed(_reserve);
string memory aTokenName = string(abi.encodePacked("Aave Interest bearing ", asset.name()));
string memory aTokenSymbol = string(abi.encodePacked("a", asset.symbol()));
// 若底层 ERC20 未提供 name/decimals,可使用 initReserveWithData 直接传自定义值
initReserveWithData(
_reserve,
aTokenName,
aTokenSymbol,
_underlyingAssetDecimals,
_interestRateStrategyAddress
);
}
function enableBorrowingOnReserve(address _reserve, bool _stableRateEnabled)
external
onlyLendingPoolManager
{
LendingPoolCore core = LendingPoolCore(poolAddressesProvider.getLendingPoolCore());
// 直接调用 Core 的可借开关,顺便设置是否允许稳定利率
core.enableBorrowingOnReserve(_reserve, _stableRateEnabled);
emit BorrowingEnabledOnReserve(_reserve, _stableRateEnabled);
}
function enableReserveAsCollateral(
address _reserve,
uint256 _baseLTVasCollateral,
uint256 _liquidationThreshold,
uint256 _liquidationBonus
) external onlyLendingPoolManager {
LendingPoolCore core = LendingPoolCore(poolAddressesProvider.getLendingPoolCore());
core.enableReserveAsCollateral(
_reserve,
_baseLTVasCollateral,
_liquidationThreshold,
_liquidationBonus
);
// 事件中会带出新的 LTV、阈值与清算奖励,方便前端追踪
emit ReserveEnabledAsCollateral(
_reserve,
_baseLTVasCollateral,
_liquidationThreshold,
_liquidationBonus
);
}sequenceDiagram
Governance->>Configurator: initReserve(reserve,...)
Configurator->>LendingPoolCore: initReserve()
Governance->>Configurator: enableBorrowingOnReserve()
Configurator->>LendingPoolCore: enableBorrowingOnReserve()
Governance->>Configurator: enableReserveAsCollateral()
Configurator->>LendingPoolCore: enableReserveAsCollateral()
sequenceDiagram
Governance->>Configurator: initReserve(reserve,...)
Configurator->>LendingPoolCore: initReserve()
Governance->>Configurator: enableBorrowingOnReserve()
Configurator->>LendingPoolCore: enableBorrowingOnReserve()
Governance->>Configurator: enableReserveAsCollateral()
Configurator->>LendingPoolCore: enableReserveAsCollateral()
sequenceDiagram
Governance->>Configurator: initReserve(reserve,...)
Configurator->>LendingPoolCore: initReserve()
Governance->>Configurator: enableBorrowingOnReserve()
Configurator->>LendingPoolCore: enableBorrowingOnReserve()
Governance->>Configurator: enableReserveAsCollateral()
Configurator->>LendingPoolCore: enableReserveAsCollateral()sequenceDiagram
Governance->>Configurator: initReserve(reserve,...)
Configurator->>LendingPoolCore: initReserve()
Governance->>Configurator: enableBorrowingOnReserve()
Configurator->>LendingPoolCore: enableBorrowingOnReserve()
Governance->>Configurator: enableReserveAsCollateral()
Configurator->>LendingPoolCore: enableReserveAsCollateral()
5.4 注册表:LendingPoolAddressesProvider
LendingPoolAddressesProvider是协议的 DNS,所有上层组件在初始化时都会向它拉取地址。setLendingPoolImpl、setLendingPoolCoreImpl、setLendingPoolConfiguratorImpl等函数内部统一调用updateImplInternal,使用InitializableAdminUpgradeabilityProxy把新实现部署到同一代理地址上,实现平滑升级。- 对于不适合走代理的合约(如
LendingPoolLiquidationManager需要delegatecall),地址提供者则直接_setAddress。同时它还统一存储了价格预言机、借贷利率预言机、FeeProvider、TokenDistributor、LendingPoolManager 等治理位置信息。 - 因为所有组件都依赖这一注册表,升级流程通常是:部署新实现 -> 通过地址提供者设置代理 -> 相关合约在下一次调用时读取到新地址,从而实现模块化演进。
function setLendingPoolImpl(address _pool) public onlyOwner {
// 所有实现升级都统一通过 updateImplInternal,保持代理地址不变
updateImplInternal(LENDING_POOL, _pool);
emit LendingPoolUpdated(_pool);
}
function setLendingPoolCoreImpl(address _lendingPoolCore) public onlyOwner {
updateImplInternal(LENDING_POOL_CORE, _lendingPoolCore);
emit LendingPoolCoreUpdated(_lendingPoolCore);
}
function setLendingPoolConfiguratorImpl(address _configurator) public onlyOwner {
updateImplInternal(LENDING_POOL_CONFIGURATOR, _configurator);
emit LendingPoolConfiguratorUpdated(_configurator);
}
function updateImplInternal(bytes32 _id, address _newAddress) internal {
address payable proxyAddress = address(uint160(getAddress(_id)));
InitializableAdminUpgradeabilityProxy proxy = InitializableAdminUpgradeabilityProxy(proxyAddress);
bytes memory params = abi.encodeWithSignature("initialize(address)", address(this));
if (proxyAddress == address(0)) {
// 首次设置时直接部署新的代理,并把实现与 admin 指向当前 provider
proxy = new InitializableAdminUpgradeabilityProxy();
proxy.initialize(_newAddress, address(this), params);
_setAddress(_id, address(proxy));
emit ProxyCreated(_id, address(proxy));
} else {
// 已存在则直接调用 upgradeToAndCall 完成滚动升级
proxy.upgradeToAndCall(_newAddress, params);
}
}sequenceDiagram
Governance->>AddressesProvider: setLendingPoolImpl(newImpl)
AddressesProvider->>Proxy: updateImplInternal()
Proxy-->>Governance: ProxyCreated/upgrade event
LendingPool->>AddressesProvider: getLendingPoolCore()
AddressesProvider-->>LendingPool: proxy address
sequenceDiagram
Governance->>AddressesProvider: setLendingPoolImpl(newImpl)
AddressesProvider->>Proxy: updateImplInternal()
Proxy-->>Governance: ProxyCreated/upgrade event
LendingPool->>AddressesProvider: getLendingPoolCore()
AddressesProvider-->>LendingPool: proxy address
sequenceDiagram
Governance->>AddressesProvider: setLendingPoolImpl(newImpl)
AddressesProvider->>Proxy: updateImplInternal()
Proxy-->>Governance: ProxyCreated/upgrade event
LendingPool->>AddressesProvider: getLendingPoolCore()
AddressesProvider-->>LendingPool: proxy addresssequenceDiagram
Governance->>AddressesProvider: setLendingPoolImpl(newImpl)
AddressesProvider->>Proxy: updateImplInternal()
Proxy-->>Governance: ProxyCreated/upgrade event
LendingPool->>AddressesProvider: getLendingPoolCore()
AddressesProvider-->>LendingPool: proxy address