HardhatDeFi
15,000 USDC
View results
Submission Details
Severity: high
Invalid

Wrong Collateral Withdrawal During Redemption

Summary

The _redeemWTokenPrivate function incorrectly assumes that _wTokenAmount directly corresponds to the withdrawable collateral in Aave. However, this assumption breaks in cases where:

  • Aave has insufficient liquidity (e.g., due to withdrawal restrictions or market conditions).

  • Rounding errors exist between aToken balances and wToken supply, leading to transaction failures.

This can cause funds to become stuck, preventing users from withdrawing collateral even if they hold wTokens.


Technical Analysis

Vulnerable Code

function _redeemWTokenPrivate(...) private returns (uint256) {
IWToken(_wToken).burn(...);
IAave(_aaveV3Pool).withdraw(_collateralToken, _wTokenAmount, ...);
}

Root Cause

  1. Aave Liquidity Constraints

    • Aave does not guarantee immediate withdrawals if liquidity is low.

    • If insufficient liquidity exists, withdraw() fails and reverts, preventing users from redeeming wTokens.

  2. Mismatch Between wTokens and aTokens

    • The contract assumes 1 wToken = 1 aToken worth of collateral.

    • However, due to interest accrual and rounding discrepancies, the actual redeemable amount can be lower.

    • This causes _withdraw() to revert if the requested amount exceeds available collateral.


Attack Scenario: Withdrawal Failure & Funds Stuck

Scenario 1: Aave Liquidity Freeze

  1. A user holds wTokens and wants to redeem them for collateral.

  2. They call _redeemWTokenPrivate(), which:

    • Burns wTokens.

    • Calls withdraw() on Aave.

  3. Issue: If Aave has insufficient liquidity, the transaction fails and reverts.

  4. Result:

    • User loses wTokens (already burned).

    • Collateral remains locked in Aave, making it impossible to withdraw.

Outcome

Users experience stuck funds, leading to financial losses.
Protocol trust is damaged as users cannot withdraw expected funds.


Scenario 2: Rounding Errors Causing Unexpected Reverts

  1. A user accumulates yield over time, increasing their expected withdrawal amount.

  2. Due to rounding discrepancies, the withdraw() function requests slightly more than available.

  3. Aave reverts the transaction, even though the user should be able to withdraw.

  4. Result:

    • User's withdrawal fails even though they have enough funds.

    • Funds appear stuck until a minor deposit or withdrawal updates the balance.


Proof of Concept (PoC)

Step 1: Simulate User Redemption

uint256 wTokenBalance = IERC20(wToken).balanceOf(userAddress);
protocol.redeemWToken(wToken, wTokenBalance, userAddress);

User expects full withdrawal.

Step 2: Aave Liquidity Constraint

uint256 aTokenBalance = IERC20(aToken).balanceOf(address(protocol));
uint256 availableLiquidity = IAave(aavePool).getAvailableLiquidity(aToken);
require(aTokenBalance > availableLiquidity, "Not enough liquidity to withdraw");

If liquidity is low, _withdraw() will fail.

Step 3: Attempt Redemption

protocol.redeemWToken(wToken, wTokenBalance, userAddress);

Transaction reverts due to insufficient Aave liquidity.
User loses ability to withdraw collateral.


**Impact for DIVA **

🔴 Severity: Medium/High

  • Likelihood:High (Aave liquidity can fluctuate unexpectedly).

  • Impact:High (Users may be unable to withdraw funds).

Financial Consequences

Users unable to access their funds if liquidity is low.
Rounding errors cause unnecessary transaction failures.
Protocol becomes unreliable, leading to loss of trust.


**Mitigation **

Fix 1: Use scaledBalanceOf and Liquidity Index for Accurate Withdrawals

Instead of assuming 1 wToken = 1 aToken, use Aave’s liquidity index to calculate the exact redeemable amount.

Updated Code

function _redeemWTokenPrivate(address _wToken, uint256 _wTokenAmount, address _recipient) private returns (uint256) {
// Retrieve collateral token and Aave reserve data
address _collateralToken = _wTokenToCollateralToken[_wToken];
IAave.ReserveData memory reserveData = IAave(_aaveV3Pool).getReserveData(_collateralToken);
// Compute available liquidity in Aave
uint256 availableLiquidity = reserveData.availableLiquidity;
uint256 scaledBalance = IERC20Metadata(reserveData.aTokenAddress).balanceOf(address(this));
uint256 liquidityIndex = reserveData.liquidityIndex;
uint256 maxWithdrawable = (scaledBalance * liquidityIndex) / 1e27;
// Ensure withdrawal does not exceed available liquidity
uint256 withdrawAmount = _wTokenAmount > maxWithdrawable ? maxWithdrawable : _wTokenAmount;
require(withdrawAmount <= availableLiquidity, "Aave liquidity too low for full withdrawal");
// Burn wTokens and withdraw collateral
IWToken(_wToken).burn(msg.sender, withdrawAmount);
IAave(_aaveV3Pool).withdraw(_collateralToken, withdrawAmount, _recipient);
return withdrawAmount;
}

Ensures withdrawal does not exceed available liquidity.
Prevents wToken burns if Aave withdrawal fails.
Avoids rounding errors by using liquidity index calculations.

Updates

Lead Judging Commences

bube Lead Judge 9 months ago
Submission Judgement Published
Invalidated
Reason: Known issue

Support

FAQs

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