// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "./LendingStorage.sol"; import "./LendingMath.sol"; import "./interfaces/ILending.sol"; import "./interfaces/IPriceFeed.sol"; /** * @title Lending * @notice 借贷池核心合约 */ contract Lending is ILending, LendingStorage, UUPSUpgradeable, OwnableUpgradeable, PausableUpgradeable, ReentrancyGuardUpgradeable { using SafeERC20 for IERC20; /// @custom:oz-upgrades-unsafe-allow constructor constructor() { _disableInitializers(); } /** * @notice 初始化函数 * @param config 市场配置 */ function initialize(Configuration calldata config) external initializer { __UUPSUpgradeable_init(); __Ownable_init(msg.sender); __Pausable_init(); __ReentrancyGuard_init(); // 设置基础配置 baseToken = config.baseToken; baseTokenPriceFeed = config.baseTokenPriceFeed; // 常量:一年的秒数 uint256 SECONDS_PER_YEAR = 365 * 24 * 60 * 60; // 31,536,000 // 设置利率参数 supplyKink = config.supplyKink; supplyPerSecondInterestRateSlopeLow = uint64(config.supplyPerYearInterestRateSlopeLow / SECONDS_PER_YEAR); supplyPerSecondInterestRateSlopeHigh = uint64(config.supplyPerYearInterestRateSlopeHigh / SECONDS_PER_YEAR); supplyPerSecondInterestRateBase = uint64(config.supplyPerYearInterestRateBase / SECONDS_PER_YEAR); borrowKink = config.borrowKink; borrowPerSecondInterestRateSlopeLow = uint64(config.borrowPerYearInterestRateSlopeLow / SECONDS_PER_YEAR); borrowPerSecondInterestRateSlopeHigh = uint64(config.borrowPerYearInterestRateSlopeHigh / SECONDS_PER_YEAR); borrowPerSecondInterestRateBase = uint64(config.borrowPerYearInterestRateBase / SECONDS_PER_YEAR); // 设置其他参数 storeFrontPriceFactor = config.storeFrontPriceFactor; trackingIndexScale = config.trackingIndexScale; baseBorrowMin = config.baseBorrowMin; targetReserves = config.targetReserves; // 初始化利息累计因子 supplyIndex = 1e18; borrowIndex = 1e18; lastAccrualTime = block.timestamp; // 设置抵押资产配置 for (uint i = 0; i < config.assetConfigs.length; i++) { AssetConfig memory assetConfig = config.assetConfigs[i]; // 验证参数合法性(必须 < 1) if(assetConfig.liquidationFactor >= 1e18) revert InvalidLiquidationFactor(); if(assetConfig.borrowCollateralFactor >= 1e18) revert InvalidBorrowCollateralFactor(); if(assetConfig.liquidateCollateralFactor >= 1e18) revert InvalidLiquidateCollateralFactor(); assetConfigs[assetConfig.asset] = assetConfig; assetList.push(assetConfig.asset); } } /** * @dev 授权升级函数 - 只有 owner 可以升级 */ function _authorizeUpgrade(address newImplementation) internal override onlyOwner {} /** * @notice 暂停合约 */ function pause() external onlyOwner { _pause(); } /** * @notice 恢复合约 */ function unpause() external onlyOwner { _unpause(); } /** * @notice 计提利息 */ function accrueInterest() public { uint256 timeElapsed = block.timestamp - lastAccrualTime; if (timeElapsed == 0) return; // 计算实际的 totalSupply 和 totalBorrow(含利息) // 注意:totalSupplyBase 和 totalBorrowBase 都是正数本金 // supplyIndex 用于存款,borrowIndex 用于借款 uint256 totalSupply = (uint256(totalSupplyBase) * supplyIndex) / 1e18; uint256 totalBorrow = (uint256(totalBorrowBase) * borrowIndex) / 1e18; uint64 utilization = LendingMath.getUtilization(totalSupply, totalBorrow); // 计算供应利率和借款利率(每秒利率) uint64 supplyRate = LendingMath.getSupplyRate( utilization, supplyKink, supplyPerSecondInterestRateSlopeLow, supplyPerSecondInterestRateSlopeHigh, supplyPerSecondInterestRateBase ); uint64 borrowRate = LendingMath.getBorrowRate( utilization, borrowKink, borrowPerSecondInterestRateSlopeLow, borrowPerSecondInterestRateSlopeHigh, borrowPerSecondInterestRateBase ); // 更新利息累计因子(使用每秒利率,计算更精确且 Gas 更低) supplyIndex = LendingMath.accrueInterest(supplyIndex, supplyRate, timeElapsed); borrowIndex = LendingMath.accrueInterest(borrowIndex, borrowRate, timeElapsed); lastAccrualTime = block.timestamp; } /** * @notice 存入基础资产 */ function supply(uint256 amount) external override nonReentrant whenNotPaused { accrueInterest(); IERC20(baseToken).safeTransferFrom(msg.sender, address(this), amount); // 获取用户当前本金 UserBasic memory user = userBasic[msg.sender]; int104 oldPrincipal = user.principal; // 计算当前实际余额(含利息) uint256 index = oldPrincipal >= 0 ? supplyIndex : borrowIndex; int256 oldBalance = LendingMath.principalToBalance(oldPrincipal, index); // 计算新余额(增加存款) int256 newBalance = oldBalance + int256(amount); // 转换为新本金(可能从借款变为存款) uint256 newIndex = newBalance >= 0 ? supplyIndex : borrowIndex; int104 newPrincipal = LendingMath.balanceToPrincipal(newBalance, newIndex); // 计算还款和存款金额 (uint104 repayAmount, uint104 supplyAmount) = LendingMath.repayAndSupplyAmount(oldPrincipal, newPrincipal); // 更新全局状态 totalBorrowBase -= repayAmount; totalSupplyBase += supplyAmount; // 更新用户本金 userBasic[msg.sender].principal = newPrincipal; emit Supply(msg.sender, msg.sender, amount); } /** * @notice 取出基础资产(如果余额不足会自动借款) */ function withdraw(uint256 amount) external override nonReentrant whenNotPaused { accrueInterest(); // 获取用户当前本金 UserBasic memory user = userBasic[msg.sender]; int104 oldPrincipal = user.principal; // 计算当前实际余额(含利息) uint256 index = oldPrincipal >= 0 ? supplyIndex : borrowIndex; int256 oldBalance = LendingMath.principalToBalance(oldPrincipal, index); // 计算新余额 int256 newBalance = oldBalance - int256(amount); // 转换为新本金 uint256 newIndex = newBalance >= 0 ? supplyIndex : borrowIndex; int104 newPrincipal = LendingMath.balanceToPrincipal(newBalance, newIndex); // 计算提取和借款金额 (uint104 withdrawAmount, uint104 borrowAmount) = LendingMath.withdrawAndBorrowAmount(oldPrincipal, newPrincipal); // 更新全局状态 totalSupplyBase -= withdrawAmount; totalBorrowBase += borrowAmount; // 更新用户本金 userBasic[msg.sender].principal = newPrincipal; // 如果变成负余额(借款),检查抵押品 if (newBalance < 0) { if (uint256(-newBalance) < baseBorrowMin) revert BorrowTooSmall(); if (!_isSolvent(msg.sender)) revert InsufficientCollateral(); } IERC20(baseToken).safeTransfer(msg.sender, amount); emit Withdraw(msg.sender, msg.sender, amount); } /** * @notice 存入抵押品 */ function supplyCollateral(address asset, uint256 amount) external override nonReentrant whenNotPaused { AssetConfig memory config = assetConfigs[asset]; if (config.asset == address(0)) revert Unauthorized(); uint256 newTotal = userCollateral[msg.sender][asset] + amount; if (newTotal > config.supplyCap) revert SupplyCapExceeded(); IERC20(asset).safeTransferFrom(msg.sender, address(this), amount); userCollateral[msg.sender][asset] += amount; emit SupplyCollateral(msg.sender, msg.sender, asset, amount); } /** * @notice 取出抵押品 */ function withdrawCollateral(address asset, uint256 amount) external override nonReentrant whenNotPaused { accrueInterest(); if (userCollateral[msg.sender][asset] < amount) revert InsufficientBalance(); userCollateral[msg.sender][asset] -= amount; // 检查是否仍有足够的抵押品(如果有债务) int104 principal = userBasic[msg.sender].principal; if (principal < 0) { if (!_isSolvent(msg.sender)) revert InsufficientCollateral(); } IERC20(asset).safeTransfer(msg.sender, amount); emit WithdrawCollateral(msg.sender, msg.sender, asset, amount); } /** * @notice 借款 * @dev baseBorrowMin 是用户借款的最小金额,如果用户借款后,余额小于 baseBorrowMin(由正数变为负数同理),则抛出 BorrowTooSmall 错误 */ function borrow(uint256 amount) external override nonReentrant whenNotPaused { accrueInterest(); // 获取用户当前本金 UserBasic memory user = userBasic[msg.sender]; int104 oldPrincipal = user.principal; // 计算当前实际余额(含利息) // 如果 principal >= 0(存款),使用 supplyIndex // 如果 principal < 0(借款),使用 borrowIndex uint256 index = oldPrincipal >= 0 ? supplyIndex : borrowIndex; int256 oldBalance = LendingMath.principalToBalance(oldPrincipal, index); // 计算新余额(减去借款额) int256 newBalance = oldBalance - int256(amount); // 检查最小借款额 if (newBalance < 0 && uint256(-newBalance) < baseBorrowMin) revert BorrowTooSmall(); // 转换为新本金(新状态可能从存款变为借款) uint256 newIndex = newBalance >= 0 ? supplyIndex : borrowIndex; int104 newPrincipal = LendingMath.balanceToPrincipal(newBalance, newIndex); // 计算提取和借款金额 (uint104 withdrawAmount, uint104 borrowAmount) = LendingMath.withdrawAndBorrowAmount(oldPrincipal, newPrincipal); // 更新全局状态 totalSupplyBase -= withdrawAmount; totalBorrowBase += borrowAmount; // 更新用户本金,方便检查更新后的用户本金是否大于还是小于抵押品价值 userBasic[msg.sender].principal = newPrincipal; // 检查抵押品是否充足 if (!_isSolvent(msg.sender)) revert InsufficientCollateral(); IERC20(baseToken).safeTransfer(msg.sender, amount); emit Withdraw(msg.sender, msg.sender, amount); } /** * @notice 清算不良债务(内部实现) * @dev 当用户抵押品由于乘以liquidateCollateralFactor后,小于债务价值时,会进行清算,清算后,如果实际抵押品价值大于债务价值,则将差额部分作为用户本金(本金以baseToken显示),否则将差额部分作为坏账,由协议承担 */ function _absorbInternal(address absorber, address borrower) internal { if (!isLiquidatable(borrower)) revert NotLiquidatable(); // 获取用户当前本金 UserBasic memory user = userBasic[borrower]; int104 oldPrincipal = user.principal; // 计算当前实际余额(含利息累计的债务) int256 oldBalance = LendingMath.principalToBalance(oldPrincipal, borrowIndex); if (oldBalance >= 0) revert NotLiquidatable(); // 计算所有抵押品的总价值(按 liquidationFactor 折扣) uint256 basePrice = IPriceFeed(baseTokenPriceFeed).getPrice(); uint256 totalCollateralValue = 0; for (uint i = 0; i < assetList.length; i++) { address asset = assetList[i]; uint256 collateralAmount = userCollateral[borrower][asset]; if (collateralAmount > 0) { AssetConfig memory assetConfig = assetConfigs[asset]; uint256 assetPrice = IPriceFeed(assetConfig.priceFeed).getPrice(); // 计算抵押品价值(USD,8位精度) uint256 assetScale = 10 ** assetConfig.decimals; uint256 collateralValueUSD = (collateralAmount * assetPrice) / assetScale; // 应用 liquidationFactor 折扣 uint256 discountedValue = (collateralValueUSD * assetConfig.liquidationFactor) / 1e18; totalCollateralValue += discountedValue; // 将抵押品转移到清算库存 userCollateral[borrower][asset] = 0; collateralReserves[asset] += collateralAmount; // 发射抵押品吸收事件 emit AbsorbCollateral(absorber, borrower, asset, collateralAmount, collateralValueUSD); } } // 将抵押品价值转换为 baseToken 数量 uint256 baseScale = 10 ** IERC20Metadata(baseToken).decimals(); uint256 collateralInBase = (totalCollateralValue * baseScale) / basePrice; // 计算新余额:oldBalance(负数)+ 抵押品价值 int256 newBalance = oldBalance + int256(collateralInBase); // 如果新余额仍为负,强制归零(坏账由协议承担) if (newBalance < 0) { newBalance = 0; } // 转换为新本金 int104 newPrincipal = LendingMath.balanceToPrincipal(newBalance, supplyIndex); // 更新用户本金 userBasic[borrower].principal = newPrincipal; // 计算偿还和供应金额 (uint104 repayAmount, uint104 supplyAmount) = LendingMath.repayAndSupplyAmount(oldPrincipal, newPrincipal); // 更新全局状态 // 储备金通过减少 totalBorrowBase 和增加 totalSupplyBase 来承担坏账 totalSupplyBase += supplyAmount; totalBorrowBase -= repayAmount; // 计算协议支付的债务(坏账部分) uint256 basePaidOut = uint256(newBalance - oldBalance); uint256 valueOfBasePaidOut = (basePaidOut * basePrice) / baseScale; // 发射债务吸收事件 emit AbsorbDebt(absorber, borrower, basePaidOut, valueOfBasePaidOut); } /** * @notice 清算不良债务(单个) */ function absorb(address borrower) external override nonReentrant whenNotPaused { accrueInterest(); _absorbInternal(msg.sender, borrower); } /** * @notice 批量清算不良债务 */ function absorbMultiple(address absorber, address[] calldata accounts) external override nonReentrant whenNotPaused { accrueInterest(); for (uint i = 0; i < accounts.length; ) { _absorbInternal(absorber, accounts[i]); unchecked { i++; } } } /** * @notice 购买清算后的抵押品 */ function buyCollateral( address asset, uint256 minAmount, uint256 baseAmount, address recipient ) external override nonReentrant whenNotPaused { if (collateralReserves[asset] == 0) revert InsufficientBalance(); // 计算可购买的抵押品数量 uint256 collateralAmount = quoteCollateral(asset, baseAmount); // 验证数量 if (collateralAmount < minAmount) revert InsufficientBalance(); if (collateralAmount > collateralReserves[asset]) revert InsufficientBalance(); // 检查储备金是否充足(如果已达到目标,不再出售抵押品) uint256 balance = IERC20(baseToken).balanceOf(address(this)); uint256 totalSupply = (uint256(totalSupplyBase) * supplyIndex) / 1e18; uint256 totalBorrow = (uint256(totalBorrowBase) * borrowIndex) / 1e18; int256 currentReserves = int256(balance) - int256(totalSupply) + int256(totalBorrow); if (currentReserves >= 0 && uint256(currentReserves) >= targetReserves) { revert InsufficientBalance(); // 储备金充足,无需出售 } // 收取清算人支付的资金 IERC20(baseToken).safeTransferFrom(msg.sender, address(this), baseAmount); // 抵押品出库 collateralReserves[asset] -= collateralAmount; // 转账抵押品到指定接收人 IERC20(asset).safeTransfer(recipient, collateralAmount); // 注意:收入会自动体现在 getReserves() 中,因为 balance 增加了 emit BuyCollateral(msg.sender, asset, baseAmount, collateralAmount); } /** * @notice 计算支付指定baseAmount可购买的抵押品数量 */ function quoteCollateral(address asset, uint256 baseAmount) public view override returns (uint256) { AssetConfig memory assetConfig = assetConfigs[asset]; uint256 assetPrice = IPriceFeed(assetConfig.priceFeed).getPrice(); uint256 basePrice = IPriceFeed(baseTokenPriceFeed).getPrice(); // 计算折扣率 // discountFactor = storeFrontPriceFactor * (FACTOR_SCALE - liquidationFactor) / FACTOR_SCALE uint256 FACTOR_SCALE = 1e18; uint256 discountFactor = (storeFrontPriceFactor * (FACTOR_SCALE - assetConfig.liquidationFactor)) / FACTOR_SCALE; // 计算折扣后的资产价格 // assetPriceDiscounted = assetPrice * (FACTOR_SCALE - discountFactor) / FACTOR_SCALE uint256 assetPriceDiscounted = (assetPrice * (FACTOR_SCALE - discountFactor)) / FACTOR_SCALE; // 计算可购买的抵押品数量 // 公式:(basePrice * baseAmount * assetScale) / (assetPriceDiscounted * baseScale) uint256 baseScale = 10 ** uint256(IERC20Metadata(baseToken).decimals()); uint256 assetScale = 10 ** uint256(assetConfig.decimals); // 使用中间变量分步计算,避免潜在的溢出 // 先计算分子和分母,再进行除法 return (basePrice * baseAmount * assetScale) / (assetPriceDiscounted * baseScale); } /** * @notice 检查账户偿付能力 */ function _isSolvent(address account) internal view returns (bool) { int104 principal = userBasic[account].principal; if (principal >= 0) return true; // 计算实际债务(含利息)- 使用 borrowIndex int256 balance = LendingMath.principalToBalance(principal, borrowIndex); uint256 debt = uint256(-balance); // 将 debt 转换为美元价值(使用 baseToken 价格) uint256 basePrice = IPriceFeed(baseTokenPriceFeed).getPrice(); uint256 baseDecimals = IERC20Metadata(baseToken).decimals(); uint256 debtValue = (debt * basePrice) / (10 ** baseDecimals); // 计算借款能力(抵押品价值已经在 _getCollateralValue 中应用了借款系数) uint256 borrowCapacity = _getCollateralValue(account); // 比较:借款能力 >= 债务价值 return borrowCapacity >= debtValue; } /** * @notice 计算账户抵押品总价值 */ function _getCollateralValue(address account) internal view returns (uint256) { uint256 totalValue = 0; for (uint i = 0; i < assetList.length; i++) { address asset = assetList[i]; uint256 amount = userCollateral[account][asset]; if (amount > 0) { AssetConfig memory config = assetConfigs[asset]; uint256 price = IPriceFeed(config.priceFeed).getPrice(); uint256 value = LendingMath.getCollateralValue(amount, price, config.decimals); totalValue += (value * config.borrowCollateralFactor) / 1e18; } } return totalValue; } /** * @notice 获取最小借款抵押率 */ function _getMinBorrowCollateralFactor() internal view returns (uint64) { uint64 minFactor = type(uint64).max; for (uint i = 0; i < assetList.length; i++) { uint64 factor = assetConfigs[assetList[i]].borrowCollateralFactor; if (factor < minFactor) minFactor = factor; } return minFactor; } // ========== View Functions ========== function getBalance(address account) external view override returns (int256) { int104 principal = userBasic[account].principal; // 使用 supplyIndex 计算实际余额(含利息) return LendingMath.principalToBalance(principal, supplyIndex); } function balanceOf(address account) external view override returns (uint256) { int104 principal = userBasic[account].principal; if (principal <= 0) return 0; // 只返回正余额(存款) return uint256(LendingMath.principalToBalance(principal, supplyIndex)); } function borrowBalanceOf(address account) external view override returns (uint256) { int104 principal = userBasic[account].principal; if (principal >= 0) return 0; // 只返回负余额(借款),转为正数 int256 balance = LendingMath.principalToBalance(principal, borrowIndex); return uint256(-balance); } function getCollateral(address account, address asset) external view override returns (uint256) { return userCollateral[account][asset]; } function isLiquidatable(address account) public view override returns (bool) { int104 principal = userBasic[account].principal; if (principal >= 0) return false; // 计算实际债务(含利息) int256 balance = LendingMath.principalToBalance(principal, borrowIndex); uint256 debt = uint256(-balance); // 将 debt 转换为美元价值(使用 baseToken 价格和 price feed 精度) uint256 basePrice = IPriceFeed(baseTokenPriceFeed).getPrice(); uint256 baseDecimals = IERC20Metadata(baseToken).decimals(); uint256 debtValue = (debt * basePrice) / (10 ** baseDecimals); // 计算抵押品总价值(清算阈值) uint256 collateralValue = 0; for (uint i = 0; i < assetList.length; i++) { address asset = assetList[i]; uint256 amount = userCollateral[account][asset]; if (amount > 0) { AssetConfig memory config = assetConfigs[asset]; uint256 price = IPriceFeed(config.priceFeed).getPrice(); uint256 value = LendingMath.getCollateralValue(amount, price, config.decimals); collateralValue += (value * config.liquidateCollateralFactor) / 1e18; } } // 比较:债务价值 > 抵押品清算阈值价值 return debtValue > collateralValue; } function getTotalSupply() external view returns (uint256) { return (uint256(totalSupplyBase) * supplyIndex) / 1e18; } function getTotalBorrow() external view returns (uint256) { return (uint256(totalBorrowBase) * borrowIndex) / 1e18; } function getCollateralReserves(address asset) external view override returns (uint256) { return collateralReserves[asset]; } function getReserves() external view override returns (int256) { // 计算实际总供应和总借款(含利息) uint256 balance = IERC20(baseToken).balanceOf(address(this)); uint256 totalSupply = (uint256(totalSupplyBase) * supplyIndex) / 1e18; uint256 totalBorrow = (uint256(totalBorrowBase) * borrowIndex) / 1e18; // reserves = balance - totalSupply + totalBorrow return int256(balance) - int256(totalSupply) + int256(totalBorrow); } function getUtilization() external view override returns (uint256) { uint256 totalSupply = (uint256(totalSupplyBase) * supplyIndex) / 1e18; uint256 totalBorrow = (uint256(totalBorrowBase) * borrowIndex) / 1e18; return LendingMath.getUtilization(totalSupply, totalBorrow); } function getSupplyRate() external view override returns (uint64) { uint256 totalSupply = (uint256(totalSupplyBase) * supplyIndex) / 1e18; uint256 totalBorrow = (uint256(totalBorrowBase) * borrowIndex) / 1e18; uint64 utilization = LendingMath.getUtilization(totalSupply, totalBorrow); uint64 perSecondRate = LendingMath.getSupplyRate( utilization, supplyKink, supplyPerSecondInterestRateSlopeLow, supplyPerSecondInterestRateSlopeHigh, supplyPerSecondInterestRateBase ); // 转换为年化利率(APY) return perSecondRate * 31536000; // SECONDS_PER_YEAR } /** * @notice 提取协议储备金(仅 owner) */ function withdrawReserves(address to, uint256 amount) external override onlyOwner nonReentrant { uint256 balance = IERC20(baseToken).balanceOf(address(this)); uint256 totalSupply = (uint256(totalSupplyBase) * supplyIndex) / 1e18; uint256 totalBorrow = (uint256(totalBorrowBase) * borrowIndex) / 1e18; int256 currentReserves = int256(balance) - int256(totalSupply) + int256(totalBorrow); // 检查储备金是否充足 if (currentReserves < 0 || amount > uint256(currentReserves)) { revert InsufficientReserves(); } // 转账储备金 IERC20(baseToken).safeTransfer(to, amount); emit WithdrawReserves(to, amount); } function getBorrowRate() external view override returns (uint64) { uint256 totalSupply = (uint256(totalSupplyBase) * supplyIndex) / 1e18; uint256 totalBorrow = (uint256(totalBorrowBase) * borrowIndex) / 1e18; uint64 utilization = LendingMath.getUtilization(totalSupply, totalBorrow); uint64 perSecondRate = LendingMath.getBorrowRate( utilization, borrowKink, borrowPerSecondInterestRateSlopeLow, borrowPerSecondInterestRateSlopeHigh, borrowPerSecondInterestRateBase ); // 转换为年化利率(APY) return perSecondRate * 31536000; // SECONDS_PER_YEAR } }