In Stratax.sol, the _executeUnwindOperation() function is intended to use the Loan-to-Value (LTV) ratio from Aave to calculate how much collateral to withdraw after repaying debt. However, the code incorrectly destructures the return value of getReserveConfigurationData(), taking liquidationThreshold (index 2) instead of ltv (index 1). This causes ~3-7% less collateral to be withdrawn on every unwindPosition() call, leaving excess collateral in Aave.
The IProtocolDataProvider.getReserveConfigurationData() returns values in this order: decimals (index 0), ltv (index 1), liquidationThreshold (index 2). At line 566, _executeUnwindOperation() skips two positions (,, and takes liquidationThreshold at index 2, while the comment says "Get LTV" and the formula comment also says "ltv".
The calculateOpenParams() function at line 385 correctly uses (, uint256 ltv,,,,,,,,) taking index 1. This inconsistency within the same contract proves the developer intended to use ltv in both places.
For ETH collateral (LTV=8000, LiqThreshold=8250), the function withdraws 9.697 ETH instead of 10.0 ETH for a 24,000 USDC debt — a shortfall of 0.303 ETH ($909), approximately 3% of collateral value per unwind.
Likelihood:
This occurs on every single call to unwindPosition(). No special conditions are required — the incorrect destructuring always takes liquidationThreshold instead of ltv.
Three independent pieces of evidence confirm this is unintentional: (1) comment says "Get LTV", (2) formula comment says "ltv", (3) calculateOpenParams() correctly uses ltv.
Impact:
For ETH collateral (LTV=8000, LiqThreshold=8250), the function withdraws 9.697 ETH instead of 10.0 ETH for a 24,000 USDC debt — a loss of 0.303 ETH ($909) per unwind, approximately 3% of collateral value.
The remaining collateral stays in Aave as aTokens. Recovery is possible via recoverTokens(aTokenAddress, amount) but requires manual intervention, knowledge of aToken addresses, and only works when no other debt positions exist (since aToken transfers check Aave health factor).
The following demonstrates that _executeUnwindOperation() takes the wrong index from Aave's getReserveConfigurationData() return values. The IProtocolDataProvider interface defines ltv at index 1 and liquidationThreshold at index 2, but the destructuring pattern (,, uint256 liqThreshold,,,,,,,) skips two positions, landing on index 2 instead of index 1. A mathematical proof with real Aave parameters (ETH LTV=80%, LiqThreshold=82.5%) shows the resulting collateral shortfall.
Change the destructuring pattern in _executeUnwindOperation() to take ltv at index 1 instead of liquidationThreshold at index 2. This aligns with how calculateOpenParams() already correctly retrieves ltv, and matches the developer's intent as documented in the code comments. The fix requires changing the comma pattern from (,, to (, and updating the variable name from liqThreshold to ltv in the formula denominator.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.
The contest is complete and the rewards are being distributed.