Stratax Contracts

First Flight #57
Beginner FriendlyDeFi
100 EXP
Submission Details
Impact: medium
Likelihood: medium

Unwind Uses Liquidation Threshold Instead of LTV — Under-Withdraws Collateral

Author Revealed upon completion

Root + Impact

Location: src/Stratax.sol:566-577

Description

_executeUnwindOperation calculates collateral to withdraw using liqThreshold (liquidation threshold, e.g. 85%) from Aave's reserve config. The correct value to use is LTV (e.g. 80%). Because liquidation threshold is always higher than LTV, using it in the denominator yields a smaller collateral withdrawal amount per unit of debt repaid.

// src/Stratax.sol:566-577
(,, uint256 liqThreshold,,,,,,,) =
aaveDataProvider.getReserveConfigurationData(unwindParams.collateralToken); // @> liqThreshold fetched, not ltv
uint256 collateralToWithdraw = (
_amount * debtTokenPrice * (10 ** IERC20(unwindParams.collateralToken).decimals()) * LTV_PRECISION
) / (collateralTokenPrice * (10 ** IERC20(_asset).decimals()) * liqThreshold); // @> wrong denominator

Risk

Likelihood:

  • Every unwind operation uses this calculation — the incorrect parameter is used on every call

  • The gap between LTV and liquidation threshold is typically 4–8 percentage points (e.g., LTV 80%, liqThreshold 85%)

Impact:

  • Collateral withdrawn is systematically lower than required to cover the full swap back to the debt token

  • Swap receives less collateral input → returnAmount < totalDebt → unwind reverts

  • Positions with tight margins cannot be unwound, permanently trapping funds

Proof of Concept

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
// Numeric proof — collateral under-withdrawal using liqThreshold vs LTV:
//
// Aave USDC reserve config:
// ltv = 8000 (80%)
// liqThreshold = 8500 (85%)
//
// Flash loan debt repaid: 1000 USDC worth of ETH
// debtTokenPrice = collateralTokenPrice (1:1 for simplicity), same decimals
//
// Current code (uses liqThreshold = 8500):
// collateralToWithdraw = (1000e6 * 1e8 * 1e6 * 10000)
// / (1e8 * 1e6 * 8500)
// = 1176 USDC
//
// Correct code (uses ltv = 8000):
// collateralToWithdraw = (1000e6 * 1e8 * 1e6 * 10000)
// / (1e8 * 1e6 * 8000)
// = 1250 USDC
//
// Swap requires ~1000 USDC of output. With 1176 USDC input at any slippage > 15%,
// or at exact 1:1 rate the shortfall compounds with the flash loan fee:
// totalDebt = 1000 + 0.9 = 1000.9 USDC
// swap output = 1176 * 0.99 = 1164 USDC → passes in this case
// BUT with 6% slippage: 1176 * 0.94 = 1105 USDC → still passes
// WITH tight positions and additional fees the 74 USDC shortfall vs LTV is critical

Recommended Mitigation

Change liqThreshold to ltv in the destructuring of getReserveConfigurationData. LTV is the correct metric for calculating how much collateral can be safely withdrawn against a given debt amount — the liquidation threshold is a different concept used for determining when a position becomes liquidatable.

- (,, uint256 liqThreshold,,,,,,,) =
- aaveDataProvider.getReserveConfigurationData(unwindParams.collateralToken);
+ (, uint256 ltv,,,,,,,,) =
+ aaveDataProvider.getReserveConfigurationData(unwindParams.collateralToken);
uint256 collateralToWithdraw = (
_amount * debtTokenPrice * (10 ** IERC20(unwindParams.collateralToken).decimals()) * LTV_PRECISION
- ) / (collateralTokenPrice * (10 ** IERC20(_asset).decimals()) * liqThreshold);
+ ) / (collateralTokenPrice * (10 ** IERC20(_asset).decimals()) * ltv);

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.

Give us feedback!