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: 101/122
Findings: 1
Award: $0.04
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: 0xCiphky
Also found by: 0rpse, 0x007, 0xAadi, 14si2o_Flint, ADM, Aamir, Aymen0909, BiasedMerc, DanielArmstrong, Fassi_Security, FastChecker, KupiaSec, LessDupes, MaslarovK, Neon2835, RamenPeople, SBSecurity, Shaheen, Tendency, ZanyBonzy, adam-idarrha, araj, b0g0, baz1ka, bigtone, bill, blutorque, carrotsmuggler, cu5t0mpeo, fyamf, gesha17, gumgumzum, hunter_w3b, inzinko, jokr, josephdara, kennedy1030, kinda_very_good, lanrebayode77, m_Rassska, mt030d, mussucal, tapir, underdog, xg, zzykxx
0.0402 USDC - $0.04
In the RestakeManager::deposit
function the contract checks the buffer deficit for the withdrawal queue first. Then it
uses the deposited assets to cover the deficit as much as possible by filling the buffer, and if there are any token left
it invests them. The problem is that if there are no tokens left it will revert since it will try to deposit zero.
That means that if there's a buffer deficit the next transaction needs to deposit enough funds to fill it fully in one single transaction, otherwise it will revert. Partial filling of the buffer is not allowed since there are no funds left and it reverts. Depending on the needed amount the deposited could be out of service for a long time till someone deposits enough funds.
function test_deposit_ERC20_simple(uint256 amount) public { vm.assume(amount <= 1_000 ether); vm.prank(alice); // @audit if deposit amount <= buffer deficit // in the first transacion buffer-deficit = buffer // this if (amount <= buffer) { // InvalidZeroInput error in delegator vm.expectRevert(abi.encodeWithSignature("InvalidZeroInput()")); restake.deposit(IERC20(ST_ETH_MAINNET), amount); return; } restake.deposit(IERC20(ST_ETH_MAINNET), amount); }
Manual review
Do nothing after spending the whole deposit amount in filling the buffer so the transaction doesn't revert:
function deposit(IERC20 _collateralToken, uint256 _amount, uint256 _referralId) public nonReentrant notPaused { // Verify collateral token is in the list - call will revert if not found uint256 tokenIndex = getCollateralTokenIndex(_collateralToken); // Get the TVLs for each operator delegator and the total TVL (uint256[][] memory operatorDelegatorTokenTVLs, uint256[] memory operatorDelegatorTVLs, uint256 totalTVL) = calculateTVLs(); // Get the value of the collateral token being deposited uint256 collateralTokenValue = renzoOracle.lookupTokenValue(_collateralToken, _amount); // Enforce TVL limit if set, 0 means the check is not enabled if (maxDepositTVL != 0 && totalTVL + collateralTokenValue > maxDepositTVL) { revert MaxTVLReached(); } // Enforce individual token TVL limit if set, 0 means the check is not enabled if (collateralTokenTvlLimits[_collateralToken] != 0) { // Track the current token's TVL uint256 currentTokenTVL = 0; // For each OD, add up the token TVLs uint256 odLength = operatorDelegatorTokenTVLs.length; for (uint256 i = 0; i < odLength;) { // esto sera 0 currentTokenTVL += operatorDelegatorTokenTVLs[i][tokenIndex]; unchecked { ++i; } } // Check if it is over the limit if (currentTokenTVL + collateralTokenValue > collateralTokenTvlLimits[_collateralToken]) { revert MaxTokenTVLReached(); } } // Determine which operator delegator to use IOperatorDelegator operatorDelegator = chooseOperatorDelegatorForDeposit(operatorDelegatorTVLs, totalTVL); // Transfer the collateral token to this address _collateralToken.safeTransferFrom(msg.sender, address(this), _amount); // Check the withdraw buffer and fill if below buffer target uint256 bufferToFill = depositQueue.withdrawQueue().getBufferDeficit(address(_collateralToken)); // fill buffer if (bufferToFill > 0) { // buffer to fill = min(amount, bufferToFill) bufferToFill = (_amount <= bufferToFill) ? _amount : bufferToFill; _amount -= bufferToFill; // safe Approve for depositQueue _collateralToken.safeApprove(address(depositQueue), bufferToFill); // fill Withdraw Buffer via depositQueue depositQueue.fillERC20withdrawBuffer(address(_collateralToken), bufferToFill); } // IF HERE AMOUNT IS NOT 0: if(_amount !=0){ // Approve the tokens to the operator delegator _collateralToken.safeApprove(address(operatorDelegator), _amount); // Call deposit on the operator delegator /// no revert operatorDelegator.deposit(_collateralToken, _amount); } // Calculate how much ezETH to mint uint256 ezETHToMint = renzoOracle.calculateMintAmount(totalTVL, collateralTokenValue, ezETH.totalSupply()); // Mint the ezETH ezETH.mint(msg.sender, ezETHToMint); // Emit the deposit event emit Deposit(msg.sender, _collateralToken, _amount, ezETHToMint, _referralId); }
Other
#0 - c4-judge
2024-05-20T05:01:36Z
alcueca marked the issue as satisfactory
#1 - c4-judge
2024-05-24T10:26:23Z
alcueca changed the severity to 2 (Med Risk)