Yieldoor

整数溢出 (高)

代码如下:

uint256 index = (observationCardinality + currentIndex - i) % observationCardinality;

这里的observationCardinality和currentIndex在合约中都是uint16的类型,也就是说2^16-1

0-65535,当他们俩个相加的话就会出现整数溢出,导致依赖计算的函数都会无法进行

溢出就会导致整个合约的核心功能无法使用

杠杆base计算错误 (高)

代码如下:

base = owedAmount / vp.maxTimesLeverage;

使用了借款金额,来计算base,可能会导致超过初始抵押品的,导致用户被清算。

计算 base 时 未考虑初始抵押品 initCollateralValue,导致 base 可能超出合理范围。
这导致用户在 实际抵押品仍然充足的情况下被提前清算。
例子如下:
假设:

  • 用户存入 1000 USDT 作为抵押
  • 借入 1000 USDT,总仓位变成 2000 USDT
  • vp.maxTimesLeverage = 2(最大杠杆 2 倍)
  • 最小抵押比率 vp.minCollateralPct = 10%
    状态 1(初始情况,正常):
  • base = owedAmount / maxTimesLeverage = 1000 / 2 = 500
  • 2000 - 500 = 1500(剩余抵押)
  • vp.minCollateralPct * owedAmount = 0.1 * 1000 = 100
  • 1500 > 100 ✅ 不会被清算
  • 状态 2(借款增长,触发清算):
  • borrow 增长到 1100 USDT
  • base = 1100 / 1 = 1100
  • 2000 - 1100 = 900
  • vp.minCollateralPct * owedAmount = 0.1 * 1100 = 110
  • 问题:900 < 110,导致被错误清算 ❌

杠杆原理:

杠杆交易是一种 利用借贷放大交易规模 的方式,它允许交易者用 较少的资金 控制 更大规模的资产,从而放大 盈利或亏损
杠杆(Leverage)
杠杆表示交易者 放大资金规模的倍数

例如:
2x(2倍杠杆):用 1000 USDT,可以开 2000 USDT 的仓位(借 1000 USDT)
5x(5倍杠杆):用 1000 USDT,可以开 5000 USDT 的仓位(借 4000 USDT)
杠杆可以让 收益放大,但同样 亏损也会放大,甚至 导致爆仓(亏损达到一定比例,被强制平仓)。
开仓(Open Position)
开仓 就是 建立一个杠杆交易,可以分为:

  • 多头(Long,做多):借入资金买入资产,预期价格上涨后卖出获利。
  • 空头(Short,做空):借入资产卖出,预期价格下跌后买回获利。
    保证金(Margin
    保证金是 用户提供的初始资金,相当于交易的 押金,用于 抵御市场波动。

初始保证金(Initial Margin):开仓时投入的本金。
维持保证金(Maintenance Margin):持仓时 账户最低可承受亏损的资金,如果低于维持保证金,就会被 强制平仓(爆仓)。
例如:

你用 1000 USDT 开 5 倍杠杆,持有 5000 USDT 资产。
如果 亏损 20%(即 1000 USDT),你的 保证金归零,系统会 强制平仓。
强平(强制平仓)
当亏损 超过保证金的承受范围,交易所会 自动卖出 资产,以防止更大的亏损。如果亏损 接近保证金,系统会 自动平仓,导致 本金全部损失。

零抵押开仓,过度杠杆化 (高)

未验证用户初始抵押,导致零抵押杠杆仓位
代码如下:

function openLeveragedPosition(LeveragedPosition memory lp) external {
require(vaultParams[lp.vault].leverageEnabled, "Leverage not enabled");

uint256 delta0 = lp.vault0In - lp.amount0In; // 计算闪电贷借入金额
uint256 delta1 = lp.vault1In - lp.amount1In;

// **漏洞:这里没有检查 lp.amount0In 和 lp.amount1In 是否大于零**
// 如果用户提供的初始抵押为 0,整个杠杆仓位就完全由借来的资金组成

// 调用借贷池借入 delta0 和 delta1
flashloan(delta0, delta1);

// 存入资金,创建杠杆仓位
IVault(lp.vault).deposit(lp.vault0In, lp.vault1In);

// 记录仓位
positions[msg.sender] = lp;
}

lp.amount0In 和 lp.amount1In 未强制要求大于零。
攻击者可以提供零抵押(amount0In = 0, amount1In = 0),仅使用 借来的资金(flashloan) 开启杠杆仓位。

杠杆上限被限制为 2x,计算错误 (高)

代码如下:

uint256 positionLeverage = (up.initCollateralValue + up.borrowedAmount) * 1e18 / up.initCollateralValue;
require(initCollateralValue >= borrowedAmount, "Liquidatable Position");

计算杠杆的仓位,但是又要求initCollateralValue >= borrowedAmount,这就导致计算positionLeverage只能限制2x,不符合协议的要求

杠杆机制注意清算问题,计算的时候,会不会有什么计算的错误,使用了错误的杠杆倍数,检查不清楚条件

不更新变量 (中)

合约中的 _getTokenIn() 方法在解析 多跳交换(Multi-hop Swap) 时,路径(path)变量未更新,导致无限循环。
代码如下:

function _getTokenIn(bytes memory path) internal pure returns (address) {  
while (path.hasMultiplePools()) {
path.skipToken();
}

(, address tokenIn,) = path.decodeFirstPool();
return tokenIn;
}

path.skipToken(); 不会修改 path 变量,而只是跳过了一个代币,但 path 本身并没有更新。
由于 path 没有改变,path.hasMultiplePools() 永远为真,导致无限循环。

还款方式计算错误 (中)

代码如下:

else if (borrowed == up.token1) {  
uint256 repayFromWithdraw = amountOut1 < owedAmount ? amountOut0 : owedAmount;
owedAmount -= repayFromWithdraw;
amountOut1 -= repayFromWithdraw;
}

borrowed == up.token1 时,应该用 amountOut1 来偿还借款,而这里却在 repayFromWithdraw 计算中使用了 amountOut0。
这意味着:
如果 amountOut1 < owedAmount(即用户提取的 token1 不够偿还借款),代码应该使用 amountOut1,但错误地使用了 amountOut0(这是 token0 的输出)。
由于 amountOut0 可能与 amountOut1 无关,这可能会导致:
未能正确偿还借款(Underpayment),导致用户的仓位可能变得 低于抵押要求(under-collateralized),可能触发 清算(liquidation)。
过度偿还(Overpayment),如果 amountOut0 远大于 owedAmount,用户可能 额外支付不必要的资金。