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

Stuck Funds Due to Unsafe wToken Burn in _redeemWTokenPrivate()

Summary

A vulnerability was identified in the _redeemWToken, _redeemPositionToken, and _removeLiquidity functions. These functions rely on the _redeemWTokenPrivate function, which demonstrates unsafe behavior when handling failed token transfers. This flaw can result in the permanent loss of wrapped tokens (wTokens) without successfully receiving the equivalent underlying tokens, particularly in scenarios involving blacklisted USDC accounts, problematic ERC-20 tokens with non-standard transfer behaviors, or if the reserve is paused.

If the transfer of the underlying tokens fails after burning wTokens, or the reserve is paused, the funds are effectively lost, creating severe implications for users.

Vulnerability Details

The root cause of the vulnerability lies in the _redeemWTokenPrivate function, where the burn operation for wTokens occurs before the withdrawal of the underlying tokens. If the token transfer fails, the wTokens are already burned, but no tokens are returned to the user. This creates a situation where user funds are effectively stuck or permanently lost.

function _redeemWTokenPrivate(
address _wToken,
uint256 _wTokenAmount,
address _recipient,
address _burnFrom
) private returns (uint256) {
// 1. Burns wTokens first
IWToken(_wToken).burn(_burnFrom, _wTokenAmount);
// 2. Then withdraws from Aave
uint256 _amountReturned = IAave(_aaveV3Pool).withdraw(
_collateralToken,
_wTokenAmount,
_recipient
);
}

Impact

This can lead to the permanent loss of user funds.

Tools Used

Manual Code Review

Recommendations

To mitigate this issue and prevent the loss of user funds, the following changes are recommended:

  1. Reorder Operations in _redeemWTokenPrivate: Perform the withdrawal operation before burning the wTokens. This ensures that the wrapped tokens are only burned after confirming the successful transfer of the underlying tokens.

function _redeemWTokenPrivate(
address _wToken,
uint256 _wTokenAmount,
address _recipient,
address _burnFrom
) private returns (uint256) {
// 1. Withdraw from Aave first
uint256 _amountReturned = IAave(_aaveV3Pool).withdraw(
_collateralToken,
_wTokenAmount,
_recipient
);
require(_amountReturned > 0, "Withdrawal failed"); // Ensure successful withdrawal
// 2. Burn wTokens only after successful withdrawal
IWToken(_wToken).burn(_burnFrom, _wTokenAmount);
return _amountReturned;
}

2. Add a check to handle paused reserves in Aave or other protocols. If a reserve is paused, revert the transaction gracefully before burning the wTokens. Use the getReserveData function from Aave to determine the reserve's status.

function _checkReserveStatus(address _reserve) private view {
(bool isPaused, , , , , ) = IAave(_aaveV3Pool).getReserveData(_reserve);
require(!isPaused, "Reserve is paused, withdrawals are disabled");
}
Updates

Lead Judging Commences

bube Lead Judge 6 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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