Skip to content
This repository has been archived by the owner on May 26, 2023. It is now read-only.

Latest commit

 

History

History
137 lines (101 loc) · 3.67 KB

136.md

File metadata and controls

137 lines (101 loc) · 3.67 KB

ctf_sec

high

Lido unwrap fails because the approve is missing before unwrap in Converter.sol

Summary

Lido unwrap fails because the approve is missing

Vulnerability Detail

Converter is used to in Redeemer.sol

Redeem for Sense

// Get the compounding token that is redeemed by Sense
address compounding = ISenseAdapter(a).target();

// Redeem the compounding token back to the underlying
IConverter(converter).convert(
    compounding,
    u,
    IERC20(compounding).balanceOf(address(this))
);

Redeem for Apwine

// Convert the interest bearing token to underlying
IConverter(converter).convert(
    IAPWineFutureVault(futureVault).getIBTAddress(),
    u,
    IERC20(ibt).balanceOf(address(this))
);

Redeem for Pendle

// Redeem the tokens from the Pendle contract
IPendle(pendleAddr).redeemAfterExpiry(forgeId, u, maturity);

// Get the compounding asset for this market
address compounding = IPendleToken(principal)
.underlyingYieldToken();

// Redeem the compounding to token to the underlying
IConverter(converter).convert(
compounding,
u,
IERC20(compounding).balanceOf(address(this))

the third and final attempt to redeem the fund is from LIDO.

catch {
      // get the current balance of wstETH
      uint256 balance = IERC20(c).balanceOf(address(this));
      // unwrap wrapped staked eth
      uint256 unwrapped = ILido(c).unwrap(balance);
      // Send the unwrapped staked ETH to the caller
      Safe.transfer(IERC20(u), msg.sender, unwrapped);
 }

however, the unwrap simple revert and revert the redeem for Pendle, Apwine and Sense because the lack of approve.

Before the AAVE withdraw, we approve AAVE's lending pool

// Allow the pool to spend the funds
Safe.approve(IERC20(u), pool, a);
// withdraw from Aave
IAaveLendingPool(pool).withdraw(u, a, msg.sender);

I believe the same approval before unwrap is needed for Lido, let us look into the implementation of unwrap.

    /**
     * @notice Exchanges wstETH to stETH
     * @param _wstETHAmount amount of wstETH to uwrap in exchange for stETH
     * @dev Requirements:
     *  - `_wstETHAmount` must be non-zero
     *  - msg.sender must have at least `_wstETHAmount` wstETH.
     * @return Amount of stETH user receives after unwrap
     */
    function unwrap(uint256 _wstETHAmount) external returns (uint256) {
        require(_wstETHAmount > 0, "wstETH: zero amount unwrap not allowed");
        uint256 stETHAmount = stETH.getPooledEthByShares(_wstETHAmount);
        _burn(msg.sender, _wstETHAmount);
        stETH.transfer(msg.sender, stETHAmount);
        return stETHAmount;
    }

clearly we see before stETH is transferred, we burn the _wstETH amount from msg.sender

In this case, msg.sender is the Converter, without the approval, unwrap revert in burn.

The deployed address for wstETH is here

https://etherscan.io/address/0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0#code

The unwrap used the same implementation in the github.

Impact

The LIDO unwrap is considered as the final step for redeem, if this revert, the transaction revert for Pendle, Sense and Apwine.

Code Snippet

https://github.com/sherlock-audit/2022-10-illuminate/blob/main/src/Converter.sol#L42-L50

Tool used

Manual Review

Recommendation

We recommend the project approve first before unwrap.

catch {
      // get the current balance of wstETH
      uint256 balance = IERC20(c).balanceOf(address(this));
      // unwrap wrapped staked eth
      Safe.approve(IERC20(u), c, balance); // approve first
      uint256 unwrapped = ILido(c).unwrap(balance);
      // Send the unwrapped staked ETH to the caller
      Safe.transfer(IERC20(u), msg.sender, unwrapped);
 }