Stratax Contracts

First Flight #57
Beginner FriendlyDeFi
100 EXP
View results
Submission Details
Severity: medium
Valid

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

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);
Updates

Lead Judging Commences

izuman Lead Judge 7 months ago
Submission Judgement Published
Validated
Assigned finding tags:

liqThreshold is used instead of LTV

Support

FAQs

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

Give us feedback!