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: 85/122
Findings: 1
Award: $1.48
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 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/Withdraw/WithdrawQueue.sol#L206
Users will loss assets due to the absence of slippage control.
The WithdrawQueue lacks a mechanism for usersto express their minimum acceptable output.
It is reasonable that as the users deposit(sends input tokens) to the contract, he/she should receive the rightful amount of tokens on that moment. Also, when the users withdraw his/her tokens he/she should receive the rightful amount of usdc on that moment.
If the token amount to be received is less than what he/she expects, the transaction should revert. This deficiency exposes them to potential losses of their assets due to price fluctuations.
Here is the withdraw() function that contains 0 slippage protection:
function withdraw(uint256 _amount, address _assetOut) external nonReentrant { // code // calculate totalTVL (, , uint256 totalTVL) = restakeManager.calculateTVLs(); // Calculate amount to Redeem in ETH uint256 amountToRedeem = renzoOracle.calculateRedeemAmount( _amount, ezETH.totalSupply(), totalTVL ); // update amount in claim asset, if claim asset is not ETH if (_assetOut != IS_NATIVE) { // Get ERC20 asset equivalent amount amountToRedeem = renzoOracle.lookupTokenAmountFromValue( IERC20(_assetOut), amountToRedeem ); } // code // add redeem amount to claimReserve of claim asset claimReserve[_assetOut] += amountToRedeem; }
The WithdrawQueue uses lookupTokenAmountFromValue which uses chainlink.latestRoundData to retrieve the last price:
function lookupTokenAmountFromValue( IERC20 _token, uint256 _value ) external view returns (uint256) { AggregatorV3Interface oracle = tokenOracleLookup[_token]; if (address(oracle) == address(0x0)) revert OracleNotFound(); (, int256 price, , uint256 timestamp, ) = oracle.latestRoundData(); if (timestamp < block.timestamp - MAX_TIME_WINDOW) revert OraclePriceExpired(); if (price <= 0) revert InvalidOraclePrice(); // Price is times 10**18 ensure token amount is scaled return (_value * SCALE_FACTOR) / uint256(price); }
For instance, if an users attempts to withdraw his assets from the contract and their transaction is delayed due to low gas or network congestion, any price change would result in the trade occurring at a sub-optimal price, harming the investor.
Manual review
Allow users to specify a minOut & deadline inputs to protect against slippage.
Other
#0 - C4-Staff
2024-05-15T14:23:17Z
CloudEllie marked the issue as duplicate of #484
#1 - c4-judge
2024-05-17T13:44:10Z
alcueca marked the issue as duplicate of #345
#2 - c4-judge
2024-05-17T13:45:10Z
alcueca marked the issue as satisfactory