Fix & Optimize contract
This commit is contained in:
@@ -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();
|
||||
|
||||
// 计算抵押品价值(USD,8位精度)
|
||||
// 计算抵押品价值(USD,8位精度)- 用于事件记录
|
||||
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)) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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 代币名称
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
Reference in New Issue
Block a user