sherlock-audit/2023-05-Index-judging

0x52 - _calculateMaxBorrowCollateral will return repayment values that are not serviceable at higher LTVs

Closed this issue · 2 comments

0x52

high

_calculateMaxBorrowCollateral will return repayment values that are not serviceable at higher LTVs

Summary

The methodology for repaying debt in conjunction with how _calculateMaxBorrowCollateral can result in repayment values that are not serviceable leading to repayment attempts reverting and eventually leading to set token liquidation.

Vulnerability Detail

AaveV3LeverageModule.sol#L338-L346

    _withdraw(deleverInfo.setToken, deleverInfo.lendingPool, _collateralAsset, deleverInfo.notionalSendQuantity);

    uint256 postTradeReceiveQuantity = _executeTrade(deleverInfo, _collateralAsset, _repayAsset, _tradeData);

    uint256 protocolFee = _accrueProtocolFee(_setToken, _repayAsset, postTradeReceiveQuantity);

    uint256 repayQuantity = postTradeReceiveQuantity.sub(protocolFee);

    _repayBorrow(deleverInfo.setToken, deleverInfo.lendingPool, _repayAsset, repayQuantity);

When a set token is deleveraging, it takes the following steps:

withdraw > swap > repay

The fundamental issue is that in order to withdraw it must have enough collateral to cover it's current debt or else the withdrawal will revert. This directly conflicts with the methodology used to rebalance the leverage.

AaveLeverageStrategyExtension.sol#L1111-L1113

        uint256 netRepayLimit = _actionInfo.collateralValue
            .preciseMul(liquidationThresholdRaw.mul(10 ** 14))
            .preciseMul(PreciseUnitMath.preciseUnit().sub(execution.unutilizedLeveragePercentage));

When calculating the repay it uses the liquidation threshold rather than the LTV. This causes it to potentially attempt rebalances that are completely unserviceable. Let's take LINK as an example. It has the following risk parameters:

Max LTV - 50%
Liquidation Threshold - 65%

Assume that the strategy is a leveraged long on LINK borrowing USDT with a target LTV of 40% and 10% unutilizedLeverage. This would allows the contract to return a rebalance up to 58.5% (0.9 * 65%), which could be problematic in the following scenario: The current LTV is 45% and a sudden price movement pushes the LTV to 48%. A rebalance is triggered attempting to push the LTV back to 45%, rebalancing 3% of the LINK. When attempting to withdraw the call will revert because after the withdrawal, the LTV would be 51%, higher than the max of 50%.

This creates a trigger point after which it is impossible for the set token to recover even though it has plenty of assets to fully recover itself.

Impact

Error in max rebalance calculation causes extension locking that leads to set token liquidation

Code Snippet

AaveV3LeverageModule.sol#L338-L346

Tool used

Manual Review

Recommendation

I would recommend repaying via a flashloan rather than the current method:

flashloan > swap > repay > withdraw > repay flashloan

It seems that the above issue is based on the assumption that one cannot withdraw from aave if the withdrawal would cause the LTV to exceed the max LTV.
However based on the documentation: _"the max amount available to withdraw is the amount that will not leave user health factor < 1 after withdrawal".

Based on the definition of the health factor, it follows that using the liquidationThreshold is in fact correct, rendering this issue invalid.

Based on Aaves docs: If user has any existing debt backed by the underlying token, then the max amount available to withdraw is the amount that will not leave user health factor < 1 after withdrawal, I agree with sponsor that aave takes care to save-guard users health factor. Invalid