Platform: Code4rena
Start Date: 30/04/2024
Pot Size: $112,500 USDC
Total HM: 22
Participants: 122
Period: 8 days
Judge: alcueca
Total Solo HM: 1
Id: 372
League: ETH
Rank: 72/122
Findings: 2
Award: $1.89
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: guhu95
Also found by: 0rpse, 0x007, 0x73696d616f, 0xCiphky, 0xabhay, Audinarey, Bauchibred, Fassi_Security, GalloDaSballo, GoatedAudits, KupiaSec, LessDupes, MSaptarshi, OMEN, Ocean_Sky, RamenPeople, SBSecurity, Tendency, WildSniper, aslanbek, bill, blutorque, crypticdefense, cu5t0mpeo, d3e4, gjaldon, grearlake, gumgumzum, honey-k12, ilchovski, jokr, josephdara, kennedy1030, p0wd3r, peanuts, stonejiajia, t0x1c, tapir, underdog, zzykxx
0.4071 USDC - $0.41
https://github.com/code-423n4/2024-04-renzo/blob/main/contracts/RestakeManager.sol#L491-L616 https://github.com/code-423n4/2024-04-renzo/blob/main/contracts/Withdraw/WithdrawQueue.sol#L206-L262 https://github.com/code-423n4/2024-04-renzo/blob/main/contracts/Withdraw/WithdrawQueue.sol#L279-L311
The protocol allows native ETH and different LSTs (liquid staking tokens) to be deposited as collateral in exchange of ezETH. In the current status, it accepts stETH and wBETH but it can accept other LSTs as well if configured.
As you can see below, this is a deposit function for LSTs, in line 507, it retrieved the current value of the collateral in which the minted ezETH will depend on.
File: RestakeManager.sol 491: function deposit( 492: IERC20 _collateralToken, 493: uint256 _amount, 494: uint256 _referralId 495: ) public nonReentrant notPaused { ~continue 505: 506: // Get the value of the collateral token being deposited 507: uint256 collateralTokenValue = renzoOracle.lookupTokenValue(_collateralToken, _amount); 508: ~continue 563: 564: // Calculate how much ezETH to mint 565: uint256 ezETHToMint = renzoOracle.calculateMintAmount( 566: totalTVL, 567: collateralTokenValue, 568: ezETH.totalSupply() 569: ); 570: 571: // Mint the ezETH 572: ezETH.mint(msg.sender, ezETHToMint); 573: 574: // Emit the deposit event 575: emit Deposit(msg.sender, _collateralToken, _amount, ezETHToMint, _referralId); 576: }
See below. This is a deposit function for native ETH. In line 607, it just retrieved the msg.value to get the minted ezETH.
File: RestakeManager.sol 592: function depositETH(uint256 _referralId) public payable nonReentrant notPaused { 593: // Get the total TVL 594: (, , uint256 totalTVL) = calculateTVLs(); 595: 596: // Enforce TVL limit if set 597: if (maxDepositTVL != 0 && totalTVL + msg.value > maxDepositTVL) { 598: revert MaxTVLReached(); 599: } 600: 601: // Deposit the remaining ETH into the DepositQueue 602: depositQueue.depositETHFromProtocol{ value: msg.value }(); 603: 604: // Calculate how much ezETH to mint 605: uint256 ezETHToMint = renzoOracle.calculateMintAmount( 606: totalTVL, 607: msg.value, 608: ezETH.totalSupply() 609: ); 610: 611: // Mint the ezETH 612: ezETH.mint(msg.sender, ezETHToMint); 613: 614: // Emit the deposit event 615: emit Deposit(msg.sender, IERC20(address(0x0)), msg.value, ezETHToMint, _referralId); 616: }
In the withdrawal function, the user has a choice to withdraw what type of collateral they want to withdraw. In this scenario, there could be an arbitrage opportunity in which a malicious user can deposit lower valued LST and withdraw higher valued LST. As per current market condition, not all LSTs are equal in terms of pricing, so there is a high chance this price difference can be use as arbitrage opportunity to drain the high valued assets from the protocol to the malicious user.
A good example of two LSTs that can have price difference is between wbETH and stETH. wbETH price is always higher than stETH price in terms of USD. See below for the market price links.
wbETH - https://coinmarketcap.com/currencies/wrapped-beacon-eth/ stETH - https://coinmarketcap.com/currencies/steth/#Chart
As of date of writing May 8 wbETH price is $3,145 while stETH price is $3,029
Please see below the withdraw function (line 206, parameter asset out) in which the user is free to withdraw whatever type of collateral assets.
File: WithdrawQueue.sol 201: /** 202: * @notice Creates a withdraw request for user 203: * @param _amount amount of ezETH to withdraw 204: * @param _assetOut output token to receive on claim 205: */ 206: function withdraw(uint256 _amount, address _assetOut) external nonReentrant { 207: // check for 0 values 208: if (_amount == 0 || _assetOut == address(0)) revert InvalidZeroInput(); 209: 210: // check if provided assetOut is supported 211: if (withdrawalBufferTarget[_assetOut] == 0) revert UnsupportedWithdrawAsset(); 212:
Economic losses to depositors of higher valued LST collaterals
This could be the scenario of the exploit.
Manual Review
The protocol should only allow withdrawal of collateral originally deposited by the user.
Other
#0 - c4-judge
2024-05-16T14:03:18Z
alcueca marked the issue as duplicate of #326
#1 - c4-judge
2024-05-17T12:48:31Z
alcueca marked the issue as satisfactory
🌟 Selected for report: t0x1c
Also found by: 0xCiphky, 0xDemon, Bauchibred, DanielArmstrong, FastChecker, MSaptarshi, Maroutis, NentoR, Ocean_Sky, PNS, Rhaydden, SBSecurity, Shaheen, Tigerfrake, ZanyBonzy, atoko, btk, carlitox477, crypticdefense, honey-k12, hunter_w3b, ilchovski, jokr, ladboy233, rbserver, twcctop, umarkhatab_465
1.479 USDC - $1.48
https://github.com/code-423n4/2024-04-renzo/blob/main/contracts/RestakeManager.sol#L491-#L576
The deposit function in Restake Manager contract enables a user to deposit collateral token in exchange of ezETH. The returned minted amount of ezETH depends on the retrieved current value of exchanged collateral token through lookupTokenValue. The collateral tokens are LSTs or native ETH.
/File: RestakeManager.sol 506: // Get the value of the collateral token being deposited 507: uint256 collateralTokenValue = renzoOracle.lookupTokenValue(_collateralToken, _amount); 508:
File: RenzoOracle.sol 71: function lookupTokenValue(IERC20 _token, uint256 _balance) public view returns (uint256) { 72: AggregatorV3Interface oracle = tokenOracleLookup[_token]; 73: if (address(oracle) == address(0x0)) revert OracleNotFound(); 74: 75: (, int256 price, , uint256 timestamp, ) = oracle.latestRoundData(); 76: if (timestamp < block.timestamp - MAX_TIME_WINDOW) revert OraclePriceExpired(); 77: if (price <= 0) revert InvalidOraclePrice(); 78: 79: // Price is times 10**18 ensure value amount is scaled 80: return (uint256(price) * _balance) / SCALE_FACTOR; 81: }
There is potential volatility on collateral LST tokens being exchanged for ezETH. These collateral token price may drop against the ETH. The protocol did not put any slippage protection in regards of this situation. User may be put into a disadvantage position and received unfavorable number of minted ezETH.
Can lead to unfavorable ezETH minting rates and potential economic losses to users
This could be the scenario
Manual Review
Put a parameter input in deposit function in which a user can decide the minimum output it can receive from minting so user can control the slippage.
Other
#0 - c4-judge
2024-05-17T13:28:59Z
alcueca marked the issue as satisfactory