Stratax Contracts

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

Unwind Calculation Uses Liquidation Threshold Instead of LTV

Author Revealed upon completion

Root + Impact

Description

  • Aave V3 defines two distinct collateral parameters: LTV (Loan-to-Value) and Liquidation Threshold. LTV determines the maximum amount a user can borrow against collateral. Liquidation Threshold (always higher than LTV) determines when the position becomes liquidatable. For example, WETH has LTV=82.5% and Liquidation Threshold=86%.

  • In calculateOpenParams, the LTV is correctly used to calculate how much can be borrowed. However, in _executeUnwindOperation, the Liquidation Threshold is used instead of LTV to calculate how much collateral to withdraw. Since Liquidation Threshold > LTV, the unwind operation calculates that it can withdraw more collateral per unit of debt than was actually borrowed per unit of collateral during the open operation.

// In calculateOpenParams: correctly uses LTV
function calculateOpenParams(TradeDetails memory details) public view returns (...) {
@> (, uint256 ltv,,,,,,,,) = aaveDataProvider.getReserveConfigurationData(details.collateralToken);
// ...
@> uint256 borrowValueUSD = (totalCollateralValueUSD * ltv * BORROW_SAFETY_MARGIN) / (LTV_PRECISION * 10000);
}
// In _executeUnwindOperation: incorrectly uses liquidationThreshold
function _executeUnwindOperation(...) internal returns (bool) {
@> (,, uint256 liqThreshold,,,,,,,) =
@> aaveDataProvider.getReserveConfigurationData(unwindParams.collateralToken);
// ...
@> uint256 collateralToWithdraw = (
@> _amount * debtTokenPrice * (10 ** IERC20(unwindParams.collateralToken).decimals()) * LTV_PRECISION
@> ) / (collateralTokenPrice * (10 ** IERC20(_asset).decimals()) * liqThreshold);
}

Risk

Likelihood:

  • Every call to unwindPosition executes _executeUnwindOperation which uses liqThreshold for collateral calculation. This is a systematic error that affects all unwind operations for all assets, not an edge case.

  • The gap between LTV and Liquidation Threshold exists for every Aave asset (typically 2-5% difference).

Impact:

  • The unwind calculates a lower collateral withdrawal than expected because liqThreshold is in the denominator and liqThreshold > LTV. This means less collateral is withdrawn per unit of debt, potentially leaving residual collateral locked in Aave.

  • Conversely, using calculateUnwindParams (which uses neither -- it calculates based on raw price ratios) creates an inconsistency between what the view function suggests and what the actual _executeUnwindOperation computes.

Proof of Concept

The arithmetic below demonstrates the mismatch using WETH's real Aave V3 parameters (LTV = 8250, liquidationThreshold = 8600). The open operation borrows at 82.5% of collateral value, but the unwind divides by 86% instead, systematically under-withdrawing collateral by approximately 4% of position value.

// Example with WETH (LTV = 8250, liqThreshold = 8600):
//
// Open: borrowValueUSD = collateralValueUSD * 8250 / 10000
// Unwind: collateralToWithdraw = debtValue * 10000 / 8600
//
// The open allows borrowing 82.5% of collateral value
// The unwind withdraws collateral as if only 86% was used
//
// For a position opened with 1 WETH at $2500:
// Open: borrowValueUSD = $2500 * 0.825 = $2062.50
// Unwind: collateralToWithdraw = $2062.50 / 0.86 = $2398.26
// vs expected: $2062.50 / 0.825 = $2500.00
//
// Result: ~$101.74 worth of collateral is NOT withdrawn during unwind

Recommended Mitigation

Replace the liqThreshold lookup with ltv so that the unwind calculation mirrors the same collateralization ratio used during position opening. This ensures the full collateral backing the debt is withdrawn.

function _executeUnwindOperation(...) internal returns (bool) {
// ...
- (,, uint256 liqThreshold,,,,,,,) =
+ (, 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!