audit
This commit is contained in:
@@ -9,11 +9,6 @@ import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
||||
import "../../interfaces/IUSDY.sol";
|
||||
import "../../interfaces/IYTPriceFeed.sol";
|
||||
|
||||
/**
|
||||
* @title YTVault
|
||||
* @notice 核心资金池,处理YT代币的存储、交换和动态手续费
|
||||
* @dev UUPS可升级合约
|
||||
*/
|
||||
contract YTVault is Initializable, UUPSUpgradeable, ReentrancyGuardUpgradeable {
|
||||
using SafeERC20 for IERC20;
|
||||
|
||||
@@ -50,38 +45,32 @@ contract YTVault is Initializable, UUPSUpgradeable, ReentrancyGuardUpgradeable {
|
||||
address public priceFeed;
|
||||
address public usdy;
|
||||
|
||||
mapping(address => bool) public isSwapper; // 授权的swap调用者
|
||||
mapping(address => bool) public isSwapper;
|
||||
|
||||
bool public isSwapEnabled;
|
||||
bool public emergencyMode;
|
||||
|
||||
// 代币白名单
|
||||
address[] public allWhitelistedTokens;
|
||||
mapping(address => bool) public whitelistedTokens;
|
||||
mapping(address => bool) public stableTokens; // 稳定币标记
|
||||
mapping(address => bool) public stableTokens;
|
||||
mapping(address => uint256) public tokenDecimals;
|
||||
mapping(address => uint256) public tokenWeights;
|
||||
uint256 public totalTokenWeights;
|
||||
|
||||
// 池子资产
|
||||
mapping(address => uint256) public poolAmounts;
|
||||
mapping(address => uint256) public tokenBalances; // 跟踪实际代币余额
|
||||
mapping(address => uint256) public tokenBalances;
|
||||
|
||||
// USDY债务追踪(用于动态手续费)
|
||||
mapping(address => uint256) public usdyAmounts;
|
||||
mapping(address => uint256) public maxUsdyAmounts;
|
||||
|
||||
// 手续费配置
|
||||
uint256 public swapFeeBasisPoints;
|
||||
uint256 public stableSwapFeeBasisPoints;
|
||||
uint256 public taxBasisPoints;
|
||||
uint256 public stableTaxBasisPoints;
|
||||
bool public hasDynamicFees;
|
||||
|
||||
// 全局滑点保护
|
||||
uint256 public maxSwapSlippageBps; // 10% 最大滑点
|
||||
uint256 public maxSwapSlippageBps;
|
||||
|
||||
// 单笔交易限额
|
||||
mapping(address => uint256) public maxSwapAmount;
|
||||
|
||||
event Swap(
|
||||
@@ -129,11 +118,6 @@ contract YTVault is Initializable, UUPSUpgradeable, ReentrancyGuardUpgradeable {
|
||||
_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 初始化合约
|
||||
* @param _usdy USDY代币地址
|
||||
* @param _priceFeed 价格预言机地址
|
||||
*/
|
||||
function initialize(address _usdy, address _priceFeed) external initializer {
|
||||
if (_usdy == address(0) || _priceFeed == address(0)) revert InvalidAddress();
|
||||
|
||||
@@ -144,7 +128,6 @@ contract YTVault is Initializable, UUPSUpgradeable, ReentrancyGuardUpgradeable {
|
||||
usdy = _usdy;
|
||||
priceFeed = _priceFeed;
|
||||
|
||||
// 初始化默认值
|
||||
isSwapEnabled = true;
|
||||
emergencyMode = false;
|
||||
swapFeeBasisPoints = 30;
|
||||
@@ -152,16 +135,11 @@ contract YTVault is Initializable, UUPSUpgradeable, ReentrancyGuardUpgradeable {
|
||||
taxBasisPoints = 50;
|
||||
stableTaxBasisPoints = 20;
|
||||
hasDynamicFees = true;
|
||||
maxSwapSlippageBps = 1000; // 10% 最大滑点
|
||||
maxSwapSlippageBps = 1000;
|
||||
|
||||
// 将 USDY 标记为稳定币,这样 USDY ↔ 稳定币的互换可以享受低费率
|
||||
stableTokens[_usdy] = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 授权升级(仅gov可调用)
|
||||
* @param newImplementation 新实现合约地址
|
||||
*/
|
||||
function _authorizeUpgrade(address newImplementation) internal override onlyGov {}
|
||||
|
||||
function setGov(address _gov) external onlyGov {
|
||||
@@ -248,7 +226,7 @@ contract YTVault is Initializable, UUPSUpgradeable, ReentrancyGuardUpgradeable {
|
||||
}
|
||||
|
||||
function setMaxSwapSlippageBps(uint256 _slippageBps) external onlyGov {
|
||||
if (_slippageBps > 2000) revert SlippageTooHigh(); // 最大20%
|
||||
if (_slippageBps > 2000) revert SlippageTooHigh();
|
||||
maxSwapSlippageBps = _slippageBps;
|
||||
}
|
||||
|
||||
@@ -256,12 +234,6 @@ contract YTVault is Initializable, UUPSUpgradeable, ReentrancyGuardUpgradeable {
|
||||
maxSwapAmount[_token] = _amount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 用YT代币购买USDY(添加流动性时调用)
|
||||
* @param _token YT代币地址
|
||||
* @param _receiver USDY接收地址
|
||||
* @return usdyAmountAfterFees 实际获得的USDY数量
|
||||
*/
|
||||
function buyUSDY(address _token, address _receiver)
|
||||
external
|
||||
onlyPoolManager
|
||||
@@ -298,12 +270,6 @@ contract YTVault is Initializable, UUPSUpgradeable, ReentrancyGuardUpgradeable {
|
||||
return usdyAmountAfterFees;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 用USDY卖出换取YT代币(移除流动性时调用)
|
||||
* @param _token YT代币地址
|
||||
* @param _receiver YT代币接收地址
|
||||
* @return amountOutAfterFees 实际获得的YT代币数量
|
||||
*/
|
||||
function sellUSDY(address _token, address _receiver)
|
||||
external
|
||||
onlyPoolManager
|
||||
@@ -319,29 +285,23 @@ contract YTVault is Initializable, UUPSUpgradeable, ReentrancyGuardUpgradeable {
|
||||
|
||||
uint256 price = _getPrice(_token, true);
|
||||
|
||||
// 计算赎回金额(扣费前)
|
||||
uint256 redemptionAmount = usdyAmount * PRICE_PRECISION / price;
|
||||
redemptionAmount = _adjustForDecimals(redemptionAmount, usdy, _token);
|
||||
if (redemptionAmount == 0) revert InvalidAmount();
|
||||
|
||||
// 计算手续费和实际转出金额
|
||||
uint256 feeBasisPoints = _getSwapFeeBasisPoints(usdy, _token, redemptionAmount);
|
||||
uint256 amountOut = redemptionAmount * (BASIS_POINTS_DIVISOR - feeBasisPoints) / BASIS_POINTS_DIVISOR;
|
||||
if (amountOut == 0) revert InvalidAmount();
|
||||
if (poolAmounts[_token] < amountOut) revert InsufficientPool();
|
||||
|
||||
// 计算实际转出的代币对应的USDY价值(用于减少usdyAmount记账)
|
||||
uint256 usdyAmountOut = amountOut * price / PRICE_PRECISION;
|
||||
usdyAmountOut = _adjustForDecimals(usdyAmountOut, _token, usdy);
|
||||
|
||||
// 手续费留在池子:只减少实际转出的部分
|
||||
_decreasePoolAmount(_token, amountOut);
|
||||
_decreaseUsdyAmount(_token, usdyAmountOut);
|
||||
|
||||
// 销毁USDY
|
||||
IUSDY(usdy).burn(address(this), usdyAmount);
|
||||
|
||||
// 转出代币
|
||||
IERC20(_token).safeTransfer(_receiver, amountOut);
|
||||
_updateTokenBalance(_token);
|
||||
|
||||
@@ -350,13 +310,6 @@ contract YTVault is Initializable, UUPSUpgradeable, ReentrancyGuardUpgradeable {
|
||||
return amountOut;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice YT代币互换
|
||||
* @param _tokenIn 输入代币地址
|
||||
* @param _tokenOut 输出代币地址
|
||||
* @param _receiver 接收地址
|
||||
* @return amountOutAfterFees 实际获得的输出代币数量
|
||||
*/
|
||||
function swap(
|
||||
address _tokenIn,
|
||||
address _tokenOut,
|
||||
@@ -370,7 +323,6 @@ contract YTVault is Initializable, UUPSUpgradeable, ReentrancyGuardUpgradeable {
|
||||
uint256 amountIn = _transferIn(_tokenIn);
|
||||
if (amountIn == 0) revert InvalidAmount();
|
||||
|
||||
// 检查单笔交易限额
|
||||
if (maxSwapAmount[_tokenIn] > 0) {
|
||||
if (amountIn > maxSwapAmount[_tokenIn]) revert AmountExceedsLimit();
|
||||
}
|
||||
@@ -390,7 +342,6 @@ contract YTVault is Initializable, UUPSUpgradeable, ReentrancyGuardUpgradeable {
|
||||
if (amountOutAfterFees == 0) revert InvalidAmount();
|
||||
if (poolAmounts[_tokenOut] < amountOutAfterFees) revert InsufficientPool();
|
||||
|
||||
// 全局滑点保护(10%)
|
||||
_validateSwapSlippage(amountIn, amountOutAfterFees, priceIn, priceOut);
|
||||
|
||||
_increasePoolAmount(_tokenIn, amountIn);
|
||||
@@ -407,26 +358,14 @@ contract YTVault is Initializable, UUPSUpgradeable, ReentrancyGuardUpgradeable {
|
||||
return amountOutAfterFees;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 获取代币价格(带价差)
|
||||
* @param _token 代币地址
|
||||
* @param _maximise true=最大价格, false=最小价格
|
||||
* @return 价格(30位精度)
|
||||
*/
|
||||
function getPrice(address _token, bool _maximise) external view returns (uint256) {
|
||||
return _getPrice(_token, _maximise);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 获取最大价格
|
||||
*/
|
||||
function getMaxPrice(address _token) external view returns (uint256) {
|
||||
return _getPrice(_token, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 获取最小价格
|
||||
*/
|
||||
function getMinPrice(address _token) external view returns (uint256) {
|
||||
return _getPrice(_token, false);
|
||||
}
|
||||
@@ -435,11 +374,6 @@ contract YTVault is Initializable, UUPSUpgradeable, ReentrancyGuardUpgradeable {
|
||||
return allWhitelistedTokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 获取池子总价值
|
||||
* @param _maximise true=使用最大价格(对协议有利), false=使用最小价格(对用户有利)
|
||||
* @return 池子总价值(USDY计价)
|
||||
*/
|
||||
function getPoolValue(bool _maximise) external view returns (uint256) {
|
||||
uint256 totalValue = 0;
|
||||
for (uint256 i = 0; i < allWhitelistedTokens.length; i++) {
|
||||
@@ -476,13 +410,6 @@ contract YTVault is Initializable, UUPSUpgradeable, ReentrancyGuardUpgradeable {
|
||||
usdyAmounts[_token] = value - _amount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 获取swap手续费率(公开方法,供前端调用)
|
||||
* @param _tokenIn 输入代币
|
||||
* @param _tokenOut 输出代币
|
||||
* @param _usdyAmount USDY数量
|
||||
* @return 手续费率(basis points)
|
||||
*/
|
||||
function getSwapFeeBasisPoints(
|
||||
address _tokenIn,
|
||||
address _tokenOut,
|
||||
@@ -549,14 +476,11 @@ contract YTVault is Initializable, UUPSUpgradeable, ReentrancyGuardUpgradeable {
|
||||
? nextAmount - targetAmount
|
||||
: targetAmount - nextAmount;
|
||||
|
||||
// 改善平衡 → 降低手续费
|
||||
if (nextDiff < initialDiff) {
|
||||
uint256 rebateBps = _taxBasisPoints * initialDiff / targetAmount;
|
||||
return rebateBps > _feeBasisPoints ? 0 : _feeBasisPoints - rebateBps;
|
||||
}
|
||||
|
||||
// 恶化平衡 → 提高手续费
|
||||
// taxBps = tax * (a + b) / (2 * target)
|
||||
uint256 sumDiff = initialDiff + nextDiff;
|
||||
if (sumDiff / 2 > targetAmount) {
|
||||
sumDiff = targetAmount * 2;
|
||||
@@ -596,10 +520,8 @@ contract YTVault is Initializable, UUPSUpgradeable, ReentrancyGuardUpgradeable {
|
||||
uint256 _priceIn,
|
||||
uint256 _priceOut
|
||||
) private view {
|
||||
// 计算预期输出(不含手续费)
|
||||
uint256 expectedOut = _amountIn * _priceIn / _priceOut;
|
||||
|
||||
// 计算实际滑点
|
||||
if (expectedOut > _amountOut) {
|
||||
uint256 slippage = (expectedOut - _amountOut) * BASIS_POINTS_DIVISOR / expectedOut;
|
||||
if (slippage > maxSwapSlippageBps) revert SlippageTooHigh();
|
||||
@@ -629,10 +551,5 @@ contract YTVault is Initializable, UUPSUpgradeable, ReentrancyGuardUpgradeable {
|
||||
return _amount * (10 ** (decimalsTo - decimalsFrom));
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev 预留存储空间,用于未来升级时添加新的状态变量
|
||||
* 50个slot = 50 * 32 bytes = 1600 bytes
|
||||
*/
|
||||
uint256[50] private __gap;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user