ctf_sec
high
Lido unwrap fails because the approve is missing
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.
The LIDO unwrap is considered as the final step for redeem, if this revert, the transaction revert for Pendle, Sense and Apwine.
https://github.com/sherlock-audit/2022-10-illuminate/blob/main/src/Converter.sol#L42-L50
Manual Review
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);
}