Fix & Optimize contract

This commit is contained in:
2025-12-23 14:05:41 +08:00
parent b927acf3b7
commit d2e9377f78
117 changed files with 745 additions and 191 deletions

View File

@@ -96,6 +96,47 @@ contract Lending is
_unpause();
}
/**
* @notice 计算累计利息后的索引(不修改状态)
* @param timeElapsed 经过的时间
* @return 新的 supplyIndex 和 borrowIndex
*/
function accruedInterestIndices(uint256 timeElapsed) internal view returns (uint256, uint256) {
uint256 newSupplyIndex = supplyIndex;
uint256 newBorrowIndex = borrowIndex;
if (timeElapsed > 0) {
// 计算实际的 totalSupply 和 totalBorrow含利息
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
);
// 计算新的利息累计因子
newSupplyIndex = LendingMath.accrueInterest(supplyIndex, supplyRate, timeElapsed);
newBorrowIndex = LendingMath.accrueInterest(borrowIndex, borrowRate, timeElapsed);
}
return (newSupplyIndex, newBorrowIndex);
}
/**
* @notice 计提利息
*/
@@ -103,34 +144,8 @@ contract Lending is
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
);
// 更新利息累计因子(使用每秒利率)
supplyIndex = LendingMath.accrueInterest(supplyIndex, supplyRate, timeElapsed);
borrowIndex = LendingMath.accrueInterest(borrowIndex, borrowRate, timeElapsed);
// 使用辅助函数计算新索引
(supplyIndex, borrowIndex) = accruedInterestIndices(timeElapsed);
lastAccrualTime = block.timestamp;
}
@@ -323,12 +338,13 @@ contract Lending is
AssetConfig memory assetConfig = assetConfigs[asset];
uint256 assetPrice = IPriceFeed(assetConfig.priceFeed).getPrice();
// 计算抵押品价值USD8位精度
// 计算抵押品价值USD8位精度- 用于事件记录
uint256 assetScale = 10 ** assetConfig.decimals;
uint256 collateralValueUSD = (collateralAmount * assetPrice) / assetScale;
// 应用 liquidationFactor 折扣
uint256 discountedValue = (collateralValueUSD * assetConfig.liquidationFactor) / 1e18;
// 直接计算折扣后的价值,避免二次除法
// discounted = (amount * price * factor) / (scale * 1e18)
uint256 discountedValue = (collateralAmount * assetPrice * assetConfig.liquidationFactor) / (assetScale * 1e18);
totalCollateralValue += discountedValue;
// 将抵押品转移到清算库存
@@ -410,6 +426,12 @@ contract Lending is
) external override nonReentrant whenNotPaused {
if (collateralReserves[asset] == 0) revert InsufficientBalance();
// 检查储备金是否充足(使用实时计算的储备金)
int256 currentReserves = getReserves();
if (currentReserves >= 0 && uint256(currentReserves) >= targetReserves) {
revert NotForSale(); // 储备金充足,无需出售
}
// 计算可购买的抵押品数量
uint256 collateralAmount = quoteCollateral(asset, baseAmount);
@@ -417,15 +439,6 @@ contract Lending is
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 NotForSale(); // 储备金充足,无需出售
}
// 收取清算人支付的资金
IERC20(baseToken).safeTransferFrom(msg.sender, address(this), baseAmount);
@@ -448,23 +461,17 @@ contract Lending is
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);
// 折扣因子
uint256 discountFactor = (storeFrontPriceFactor * (FACTOR_SCALE - assetConfig.liquidationFactor)) / FACTOR_SCALE;
// 分子: basePrice * baseAmount * assetScale * FACTOR_SCALE
// 分母: assetPrice * (FACTOR_SCALE - discountFactor) * baseScale
return (basePrice * baseAmount * assetScale * FACTOR_SCALE) /
(assetPrice * (FACTOR_SCALE - discountFactor) * baseScale);
}
/**
@@ -579,11 +586,15 @@ contract Lending is
return collateralReserves[asset];
}
function getReserves() external view override returns (int256) {
// 计算实际总供应和总借款(含利息
function getReserves() public view override returns (int256) {
// 计算最新的利息索引(不修改状态
uint256 timeElapsed = block.timestamp - lastAccrualTime;
(uint256 newSupplyIndex, uint256 newBorrowIndex) = accruedInterestIndices(timeElapsed);
// 使用最新索引计算实际总供应和总借款(含利息)
uint256 balance = IERC20(baseToken).balanceOf(address(this));
uint256 totalSupply = (uint256(totalSupplyBase) * supplyIndex) / 1e18;
uint256 totalBorrow = (uint256(totalBorrowBase) * borrowIndex) / 1e18;
uint256 totalSupply = (uint256(totalSupplyBase) * newSupplyIndex) / 1e18;
uint256 totalBorrow = (uint256(totalBorrowBase) * newBorrowIndex) / 1e18;
// reserves = balance - totalSupply + totalBorrow
return int256(balance) - int256(totalSupply) + int256(totalBorrow);
@@ -629,10 +640,8 @@ contract Lending is
* @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);
// 使用实时计算的储备金
int256 currentReserves = getReserves();
// 检查储备金是否充足
if (currentReserves < 0 || amount > uint256(currentReserves)) {

View File

@@ -18,6 +18,11 @@ import "../../interfaces/IUSDY.sol";
contract YTPoolManager is Initializable, UUPSUpgradeable, ReentrancyGuardUpgradeable {
using SafeERC20 for IERC20;
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
error Forbidden();
error InvalidAddress();
error InvalidDuration();
@@ -64,6 +69,8 @@ contract YTPoolManager is Initializable, UUPSUpgradeable, ReentrancyGuardUpgrade
);
event CooldownDurationSet(uint256 duration);
event HandlerSet(address indexed handler, bool isActive);
event GovChanged(address indexed oldGov, address indexed newGov);
event AumAdjustmentChanged(uint256 addition, uint256 deduction);
modifier onlyGov() {
if (msg.sender != gov) revert Forbidden();
@@ -109,7 +116,9 @@ contract YTPoolManager is Initializable, UUPSUpgradeable, ReentrancyGuardUpgrade
function setGov(address _gov) external onlyGov {
if (_gov == address(0)) revert InvalidAddress();
address oldGov = gov;
gov = _gov;
emit GovChanged(oldGov, _gov);
}
function setHandler(address _handler, bool _isActive) external onlyGov {
@@ -126,6 +135,7 @@ contract YTPoolManager is Initializable, UUPSUpgradeable, ReentrancyGuardUpgrade
function setAumAdjustment(uint256 _addition, uint256 _deduction) external onlyGov {
aumAddition = _addition;
aumDeduction = _deduction;
emit AumAdjustmentChanged(_addition, _deduction);
}
/**

View File

@@ -12,6 +12,11 @@ import "../../interfaces/IYTToken.sol";
*/
contract YTPriceFeed is Initializable, UUPSUpgradeable {
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
error Forbidden();
error MaxChangeTooHigh();
error PriceChangeTooLarge();

View File

@@ -18,6 +18,11 @@ import "../../interfaces/IYTVault.sol";
contract YTRewardRouter is Initializable, UUPSUpgradeable, ReentrancyGuardUpgradeable, PausableUpgradeable {
using SafeERC20 for IERC20;
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
error Forbidden();
error AlreadyInitialized();
error InvalidAddress();

View File

@@ -17,6 +17,11 @@ import "../../interfaces/IYTPriceFeed.sol";
contract YTVault is Initializable, UUPSUpgradeable, ReentrancyGuardUpgradeable {
using SafeERC20 for IERC20;
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
error Forbidden();
error OnlyPoolManager();
error NotSwapper();
@@ -101,6 +106,8 @@ contract YTVault is Initializable, UUPSUpgradeable, ReentrancyGuardUpgradeable {
);
event EmergencyModeSet(bool enabled);
event SwapEnabledSet(bool enabled);
event GovChanged(address indexed oldGov, address indexed newGov);
event PoolManagerChanged(address indexed oldManager, address indexed newManager);
modifier onlyGov() {
if (msg.sender != gov) revert Forbidden();
@@ -159,12 +166,16 @@ contract YTVault is Initializable, UUPSUpgradeable, ReentrancyGuardUpgradeable {
function setGov(address _gov) external onlyGov {
if (_gov == address(0)) revert InvalidAddress();
address oldGov = gov;
gov = _gov;
emit GovChanged(oldGov, _gov);
}
function setPoolManager(address _manager) external onlyGov {
if (_manager == address(0)) revert InvalidAddress();
address oldManager = ytPoolManager;
ytPoolManager = _manager;
emit PoolManagerChanged(oldManager, _manager);
}
function setSwapper(address _swapper, bool _isActive) external onlyGov {
@@ -545,11 +556,12 @@ contract YTVault is Initializable, UUPSUpgradeable, ReentrancyGuardUpgradeable {
}
// 恶化平衡 → 提高手续费
uint256 averageDiff = (initialDiff + nextDiff) / 2;
if (averageDiff > targetAmount) {
averageDiff = targetAmount;
// taxBps = tax * (a + b) / (2 * target)
uint256 sumDiff = initialDiff + nextDiff;
if (sumDiff / 2 > targetAmount) {
sumDiff = targetAmount * 2;
}
uint256 taxBps = _taxBasisPoints * averageDiff / targetAmount;
uint256 taxBps = _taxBasisPoints * sumDiff / (targetAmount * 2);
return _feeBasisPoints + taxBps;
}

View File

@@ -13,6 +13,11 @@ import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
*/
contract USDY is Initializable, ERC20Upgradeable, OwnableUpgradeable, UUPSUpgradeable {
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
error Forbidden();
error InvalidVault();

View File

@@ -12,6 +12,11 @@ import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
*/
contract WUSD is Initializable, ERC20Upgradeable, UUPSUpgradeable, OwnableUpgradeable {
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
/**
* @notice 初始化合约
* @param _name 代币名称

View File

@@ -13,6 +13,11 @@ import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
*/
contract YTLPToken is Initializable, ERC20Upgradeable, OwnableUpgradeable, UUPSUpgradeable {
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
error NotMinter();
error InvalidMinter();

View File

@@ -18,6 +18,11 @@ contract YTAssetFactory is Initializable, UUPSUpgradeable, OwnableUpgradeable {
error VaultNotExists();
error InvalidHardCap();
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
/// @notice YTAssetVault实现合约地址
address public vaultImplementation;

View File

@@ -23,6 +23,11 @@ contract YTAssetVault is
{
using SafeERC20 for IERC20;
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
error Forbidden();
error HardCapExceeded();
error InvalidAmount();