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: 90/122
Findings: 1
Award: $0.41
π 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/Withdraw/WithdrawQueue.sol#L206
Users withdrawing in ezETH do no earn any rewards when withdrawing. Yield should accumulated while in the withdraw waiting queue. It is fair to assume that withdrawing users have a proportional claim to the yield.
According to the documentation:
ezETH is the liquid restaking token representing a userβs restaked position at Renzo
The underlying restaking positions earn rewards which are reflected in the price of ezETH
The value ezETH increase relative to the underlying LSTs as it earns more rewards in AVS tokens
Therefore, the price of ezETH increases over time, and the value of the rewards is reflected in the value of ezETH.
When extracting rewards, Renzo handles it as follows:
/** * @notice Creates a withdraw request for user * @param _amount amount of ezETH to withdraw * @param _assetOut output token to receive on claim */ function withdraw(uint256 _amount, address _assetOut) external nonReentrant { // check for 0 values if (_amount == 0 || _assetOut == address(0)) revert InvalidZeroInput(); // check if provided assetOut is supported if (withdrawalBufferTarget[_assetOut] == 0) revert UnsupportedWithdrawAsset(); // transfer ezETH tokens to this address IERC20(address(ezETH)).safeTransferFrom(msg.sender, address(this), _amount); // calculate totalTVL (, , uint256 totalTVL) = restakeManager.calculateTVLs(); // Calculate amount to Redeem in ETH uint256 amountToRedeem = renzoOracle.calculateRedeemAmount( _amount, ezETH.totalSupply(), totalTVL ); ....... }
The calculateRedeemAmount function calculates the reward amount.
// Given the amount of ezETH to burn, the supply of ezETH, and the total value in the protocol, determine amount of value to return to user function calculateRedeemAmount( uint256 _ezETHBeingBurned, uint256 _existingEzETHSupply, uint256 _currentValueInProtocol ) external pure returns (uint256) { // This is just returning the percentage of TVL that matches the percentage of ezETH being burned uint256 redeemAmount = (_currentValueInProtocol * _ezETHBeingBurned) / _existingEzETHSupply; // Sanity check if (redeemAmount == 0) revert InvalidTokenAmount(); return redeemAmount; }
Finally, after the coolDownPeriod users can claim their rewards.
function claim(uint256 withdrawRequestIndex) external nonReentrant { // check if provided withdrawRequest Index is valid if (withdrawRequestIndex >= withdrawRequests[msg.sender].length) revert InvalidWithdrawIndex(); WithdrawRequest memory _withdrawRequest = withdrawRequests[msg.sender][ withdrawRequestIndex ]; if (block.timestamp - _withdrawRequest.createdAt < coolDownPeriod) revert EarlyClaim(); // subtract value from claim reserve for claim asset claimReserve[_withdrawRequest.collateralToken] -= _withdrawRequest.amountToRedeem; // delete the withdraw request withdrawRequests[msg.sender][withdrawRequestIndex] = withdrawRequests[msg.sender][ withdrawRequests[msg.sender].length - 1 ]; withdrawRequests[msg.sender].pop(); // burn ezETH locked for withdraw request ezETH.burn(address(this), _withdrawRequest.ezETHLocked); // send selected redeem asset to user if (_withdrawRequest.collateralToken == IS_NATIVE) { payable(msg.sender).transfer(_withdrawRequest.amountToRedeem); } else { IERC20(_withdrawRequest.collateralToken).transfer( msg.sender, _withdrawRequest.amountToRedeem ); } // emit the event emit WithdrawRequestClaimed(_withdrawRequest); } }
Here, there is a problem. The reward amount calculates the current value of the withdrawn ezETH, but according to the documentation, there is a waiting period of at least seven days before rewards can be extracted. The calculation method above does not consider the waiting time, which results in users receiving rewards based on the value of ezETH that is seven days ago, leading to unnecessary losses for users.
vscode
Account for the accumulate rewards during the withdrawal period that belongs to the deposit pool
Other
#0 - C4-Staff
2024-05-15T14:33:23Z
CloudEllie marked the issue as duplicate of #259
#1 - c4-judge
2024-05-17T12:28:21Z
alcueca marked the issue as satisfactory
#2 - c4-judge
2024-05-17T12:35:51Z
alcueca marked the issue as duplicate of #326