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: 117/122
Findings: 1
Award: $0.00
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: Sathish9098
Also found by: 0x73696d616f, 0xCiphky, 0xmystery, ABAIKUNANBAEV, Bauchibred, BiasedMerc, Fassi_Security, FastChecker, GalloDaSballo, GoatedAudits, K42, KupiaSec, LessDupes, Limbooo, ReadyPlayer2, Rhaydden, SBSecurity, Sabit, Sparrow, WildSniper, ZanyBonzy, adam-idarrha, adeolu, araj, aslanbek, atoko, b0g0, carlitox477, crypticdefense, fyamf, gesha17, gjaldon, grearlake, gumgumzum, hihen, honey-k12, hunter_w3b, inzinko, jesjupyter, jokr, kennedy1030, kind0dev, kinda_very_good, ladboy233, lanrebayode77, oakcobalt, oualidpro, pauliax, rbserver, t0x1c, tapir, underdog, xg, zzykxx
0 USDC - $0.00
https://github.com/code-423n4/2024-04-renzo/blob/519e518f2d8dec9acf6482b84a181e403070d22d/contracts/Deposits/DepositQueue.sol#L193 https://github.com/code-423n4/2024-04-renzo/blob/519e518f2d8dec9acf6482b84a181e403070d22d/contracts/Deposits/DepositQueue.sol#L284 https://github.com/code-423n4/2024-04-renzo/blob/519e518f2d8dec9acf6482b84a181e403070d22d/contracts/Deposits/DepositQueue.sol#L217
The contract would refund more Ether to the external caller (admin) than they actually spent on gas. This overpayment of gas refunds would effectively drain the contract's balance unnecessarily.
The stakeEthFromQueue function in the provided code has a bug in the way it calculates the gas used by the admin caller (msg.sender) for the purpose of refunding gas. The current implementation does not accurately measure the gas used by the admin caller and instead refunds gas based on the contract's internal gas consumption.
function stakeEthFromQueue( IOperatorDelegator operatorDelegator, bytes calldata pubkey, bytes calldata signature, bytes32 depositDataRoot ) external onlyNativeEthRestakeAdmin { uint256 gasBefore = gasleft(); // Send the ETH and the params through to the restake manager restakeManager.stakeEthInOperatorDelegator{ value: 32 ether }( operatorDelegator, pubkey, signature, depositDataRoot ); emit ETHStakedFromQueue(operatorDelegator, pubkey, 32 ether, address(this).balance); // Refund the gas to the Admin address if enough ETH available _refundGas(gasBefore); } function _refundGas(uint256 initialGas) internal { uint256 gasUsed = (initialGas - gasleft()) * tx.gasprice; uint256 gasRefund = address(this).balance >= gasUsed ? gasUsed : address(this).balance; (bool success, ) = payable(msg.sender).call{ value: gasRefund }(""); if (!success) revert TransferFailed(); emit GasRefunded(msg.sender, gasRefund); }
The issue lies in the way the gas used by the admin caller (msg.sender) is calculated for the refund. The current implementation assigns gasBefore before the call to restakeManager.stakeEthInOperatorDelegator, and then passes this value to the _refundGas function. Inside _refundGas, the gas used is calculated as initialGas - gasleft().
However, this calculation does not accurately reflect the gas used by the admin caller (msg.sender). Instead, it calculates the gas used by the contract itself when executing the internal _refundGas function, which is not the intended behavior.
Here's a practical scenerio:
The external caller (admin) provides an initial gas allowance when calling the stakeEthFromQueue function.
This gas allowance is used to execute the external function call up until the point where the contract's internal operations begin (e.g., the call to restakeManager.stakeEthInOperatorDelegator).
After that point, any gas consumed by the contract's internal operations and function calls (e.g., _refundGas) is paid for by the contract itself, using its own balance.
The gas refund is intended to compensate the external caller (admin) for the gas used to initiate the external function call, not for the gas consumed by the contract's internal operations.
Similarly issue is in stakeEthFromQueueMulti function.
Manual review
To accurately calculate the gas used by the admin caller (msg.sender) and refund the correct amount, the following changes should be made:
Move the assignment of gasBefore to the beginning of the stakeEthFromQueue function, before any other operations.
Capture the value of gasleft() immediately after the event emission (emit ETHStakedFromQueue) and store it in a new variable, let's call it gasAfter. Calculate the gas used by the admin caller (msg.sender) as gasBefore - gasAfter. Refund the calculated gas amount to msg.sender by sending (gasBefore - gasAfter) * tx.gasprice Ether from the contract's balance.
Here's the corrected code:
function stakeEthFromQueue( IOperatorDelegator operatorDelegator, bytes calldata pubkey, bytes calldata signature, bytes32 depositDataRoot ) external onlyNativeEthRestakeAdmin { uint256 gasBefore = gasleft(); // Send the ETH and the params through to the restake manager restakeManager.stakeEthInOperatorDelegator{ value: 32 ether }( operatorDelegator, pubkey, signature, depositDataRoot ); uint256 gasAfter = gasleft(); emit ETHStakedFromQueue(operatorDelegator, pubkey, 32 ether, address(this).balance); // Refund the gas to the Admin address if enough ETH available uint256 gasUsedByAdmin = gasBefore - gasAfter * tx.gasprice; (bool success, ) = payable(msg.sender).call{ value: gasUsedByAdmin }(""); if (!success) revert TransferFailed(); emit GasRefunded(msg.sender, gasUsedByAdmin); }
Context
#0 - C4-Staff
2024-05-15T14:19:53Z
CloudEllie marked the issue as duplicate of #504
#1 - c4-judge
2024-05-20T02:31:15Z
alcueca marked the issue as not a duplicate
#2 - c4-judge
2024-05-20T02:35:13Z
alcueca changed the severity to QA (Quality Assurance)
#3 - c4-judge
2024-05-24T09:03:53Z
alcueca marked the issue as grade-a
#4 - c4-judge
2024-05-24T09:20:20Z
alcueca marked the issue as grade-b