Rest engaged Guardian to review the security of its LST vault, allowing users to deposit and withdraw a variety of LST’s with Eigenlayer. From the 15th of January to the 22nd of January, a team of 5 auditors reviewed the source code in scope.
- Published
- Review window
- January 15 to 22, 2024
- Language
- Solidity
- Chains
- Ethereum
- Sector
- Staking
- 0 Critical
- 9 High
- 13 Medium
- 22 Low
- 0 Informational
Scope
Overview
Rest engaged Guardian to review the security of its LST vault, allowing users to deposit and withdraw a variety of LST’s with Eigenlayer. From the 15th of January to the 22nd of January, a team of 5 auditors reviewed the source code in scope.
Findings 44
-
EWT-1 High All Attempted Withdrawals Will Fail in EigenLayer M1 Logical Error Resolved
Description
Proof of concept: PoC
When a staker wants to withdraw their eigen shares through function
withdrawUsingEigenShares, the function_queueWithdrawis called which queues a withdrawal through the delegation manager:delegationManager.queueWithdrawals(arrayify(withdraw));However, the currently deployed M1
DelegationManagerdoes not support functionqueueWithdrawalsand all calls to it will revert. Consequently, users are entirely unable to withdraw their restaked assets.Recommendation
Consider making the contracts upgradeable such that when EigenLayer upgrades to the M2 contracts, the Rest Vault’s functionality can be updated.
Furthermore, add functions for user to withdraw their eigen shares through the current M1 StrategyManager with function
queueWithdrawalandcompleteQueuedWithdrawal.Resolution
Rest Team: Resolved.
-
GLOBAL-1 High Issuance Withdrawals Can Be Trapped Logical Error Resolved
Description
Proof of concept: PoC
Anyone may complete a withdrawal that was queued through the Issuance contract by calling the
completeWithdrawfunction on the Vault contract directly.Because the
owneris the Issuance address for withdrawals done through the Issuance contract, a user can complete a withdrawal for another user and their funds will be sent to the Issuance contract and stuck. Furthermore, thependingWithdrawsfor the true owner will not be updated in the Issuance contract.Recommendation
Refactor the integration between the Issuance contract and the Vault contract such that the
pendingWithdrawsin the Issuance contract cannot be circumvented.Resolution
Rest Team: The issue was resolved in commit d93fb8e.
-
GLOBAL-2 High stETH Rebase Frontrunning Frontrunning Acknowledged
Description
Proof of concept: PoC
stETH rebases once a day to reflect the yield earned from staking ETH. This creates issues in the Vault and Issuance contracts.
Firstly, in the Vault contract a malicious actor may deposit a different LST, say rETH, before the stETH rebase and withdraw more rETH directly after the stETH rebase -- as the value of the vault has increased by the stETH yield.
Secondly, a malicious actor may frontrun the stEth rebase and use the
completeWithdrawEarlyfunction in the Issuance contract to buy out a user's stETH pending withdrawal before the rebase is recorded, therefore obtaining the assets at a discount.Recommendation
To address the Vault issue, ensure that the withdrawal fee is significant enough to deter make any extraction of the stEth rebase unprofitable.
To address the Issuance issue, consider implementing a protocol fee for the buyer of stEth withdrawals such that any value gained from the stEth rebase is overshadowed by the amount paid to the protocol.
Resolution
Rest Team: Acknowledged.
-
ISU-1 High Tokens Stolen When Completing Withdraw Logical Error Resolved
Description
Proof of concept: PoC
The Issuance contract was created to facilitate early buyouts of pending withdraws. The function
completeWithdrawmust be called after a withdraw has matured from a queued state.This function incorrectly sends the funds to the caller of the function, rather than the owner of the pending withdraw. This makes it possible for anyone to call this function, with any non-pending withdraw and steal the funds.
Recommendation
Validate that the owner of the withdrawal is the
msg.sender.Resolution
Rest Team: The issue was resolved in commit 90dc6f3.
-
ISU-2 High All Early Withdrawals Fail Logical Error Resolved
Description
Proof of concept: PoC
The
completeWithdrawalEarlyfunction from theIssuancecontract allows users to "buy out” the withdrawal of someone else in return for the withdrawal's equivalent in their desired LST/ETH.It checks the opposite of what it should. Only withdrawals that have a set root should continue being executed, when the check is doing quite the opposite. This will completely DoS the function from being used as intended and will further introduce unexpected behavior.
Recommendation
if (pendingWithdraws.owner(root) == address(0)) revert UnknownRoot()Resolution
Rest Team: The issue was resolved in commit b5343d8.
-
ISU-3 High Unbounded Call Leads To Gas Griefing and DoS Griefing Partially resolved
Description
Proof of concept: PoC
Issuance.completeWithdrawalEarly()is used to “buy out” a withdrawal from someone in return for the equivalent the withdrawal's value in the LST of the withdrawal or in ETH if the withdrawal was queued withwantsEth = true.That equivalent value gets sent to the
oldOwnerof the withdrawal either through andERC20.transferor through anaddress.callin the case of ETH.Calling an address through
address.call()without a gas stipend allows the call recipient to use as much as 63/64 of the gas left for execution.This allows the recipient to do two things:
- Maliciously expend gas in order to make the transaction be very expensive for the caller.
- Trigger a revert, which will make the transaction fail and disallow it from being completed, thus
causing DoS.
Recommendation
Implement the classical pull pattern, instead of the current push implementation, with regards to how the original withdrawal owner gets the payment for his withdrawal.
Resolution
Rest Team: The issue was partially resolved in commit edeb661.
Guardian: It is still possible for a user to block anyone from early withdrawing their shares by depositing in the Issuance contract through a contract that does not have a receiver/fallback. Consider implementing the pull pattern instead. 18
-
STEA-1 High stETH Price Hardcoded To 1 ETH Logical Error Partially resolved
Description
Proof of concept: PoC
stETH is a rebasing token. It distributes staking rewards through increasing the balances of users with their accrued APR daily. Since the total supply of stETH represents the total staked ETH plus the staking rewards it has a price that is usually very close to that of ETH.
The issue arises due to the price of stETH being hardcoded to
1e18. Even though the token is loosely pegged to ETH, 1 stETH ≠ 1 ETH.- When price of stETH < the price of ETH: A. Deposits in stETH will mint more to the user than supposed to, devaluing the shares. B. Withdrawing in stETH will make users receive less than intended, thus losing them funds.
- When price of stETH > ETH: A. Depositing stETH will output less shares than should, thus damaging the depositor. B. Withdrawing stETH will be more profitable than should be, thus devaluing the shares.
Recommendation
To mitigate the issue change the stETH adapter's value() function to use the stETH/ETH Chainlink Price Feed.
Resolution
Rest Team: The issue was partially resolved in commit cdbb74a.
-
VLT-1 High restETH Becomes Cheaper Upon Withdrawals Logical Error Resolved
Description
In the
completeWithdrawfunction the restETH shares are only burned upon completion of the withdrawal. However the shares attributed to the Vault contract in the EigenlayerStrategyManagercontract are reduced upon the queuing of a withdrawal.The strategy contract will often refer to the
StrategyManager.stakerStrategySharesto produce the shares result (see StrategyBase). Therefore thetotalAssetsvalue is reduced immediately upon calling the withdrawUsingEiganShares function, while the corresponding decrease in the restETH supply only occurs when the withdrawal is completed in thecompleteWithdrawfunction.As a result, the price of restETH will errantly drop when users initiate a withdrawal with the withdrawUsingEiganShares function.
Recommendation
Reduce the shares of
restETHimmediately in thewithdrawUsingEiganSharesfunction, as this is when the corresponding reduction intotalAssetsoccurs.Resolution
Rest Team: The issue was resolved in commit 2f68a92.
-
VLT-2 High Vault Can Be Drained Through Overlooked Mint Function Logical Error Resolved
Description
Proof of concept: PoC
The
RestEthVaultcontract Vault has an adapter for each LST it supports, including for the flagship asset, that will be set as the ERC4626 vault asset. The contract mistakenly does not overwrite theERC4626.mint(uint256,address)function, meaning that when receiving shares through that function, the required asset LST asset amount will be mapped as a parity of 1:1 with ETH.If the flagship LST has a moment when it is valued higher than ETH, by depositing through this function an attacker could then instantly resell the shares, using the
withdrawUsingAssetsfunction for a profit, after fees.The attack would require that funds be existing in the vault, thus back-running any deposit call and would require the difference in price in the flagship LST and ETH so that it is profitable after withdraw fees.
Recommendation
Override the
ERC4626.mint(uint256,address)function and use the adapter provided price.Resolution
Rest Team: The issue was resolved in commit dcbb07b.
-
GLOBAL-3 Medium Eigen Airdrop Cannot Be Claimed Logical Error Resolved
Description
Stakers which have their LST’s staked in EigenLayer will be eligible for an airdrop. However, there is currently no way for users of the Rest Vault to claim these funds.
Recommendation
Consider adding the functionality to withdraw airdropped tokens and/or make the contracts upgradeable.
Resolution
Rest Team: Resolved.
-
GLOBAL-4 Medium Lack Of Migration Or Extension Mechanisms Upgradability Resolved
Description
The current implementation of the
RestEthVaultcontract lacks a significant part of protocol target functionality.Besides protocol specific functionality it also lacks a means to migrate funds to a new contract or a means to delegate to an operator in the EigenLayer ecosystem. The second aspect may be relevant for any future airdrop or yield increase.
Recommendation
Until the protocol reaches maturity, in order to support incremental feature development, use an upgradable pattern.
Resolution
Rest Team: Resolved.
-
GLOBAL-5 Medium Anyone Can Remove All LST Deposits From A Strategy Logical Error Acknowledged
Description
Anyone can force the system to withdraw the entirety of any specific asset from Eigenlayer by depositing a different asset and then using the
withdrawUsingEigenSharesfunction to queue a withdrawal for the vaults entire strategy shares.This way the Rest system would be unable to earn yield and may not be eligible for an airdrop. A competing LST protocol may do this to gain their own position in a strategy that has maximum TVL limits.
Recommendation
Consider implementing a mechanism to prevent malicious actors from removing the Vault's allocation in strategies.
Resolution
Rest Team: Acknowledged.
-
ISU-4 Medium Potential For Trapped Ether Trapped Ether Resolved
Description
In the
wantsEth[root] == falsecase, it is possible for Ether to become trapped in this contract since themsg.valueis not validated to be 0 nor refunded to the user.Recommendation
Either validate that the
msg.valueis 0 whenwantsEth[root] == falseand theelsecase is entered, or refund this ETH to the caller.Resolution
Rest Team: The issue was resolved in commit fe74bf2.
-
ISU-5 Medium Incorrect Withdrawal Maturity Check Logical Error Resolved
Description
Issuancechecks whether a withdrawal has matured in order to allow callingcompleteWithdrawEarlyonly on non-matured withdrawals. EigenLayer makes withdrawal requests wait roughly a week after they got queued in order to be able to react and punish in cases where the staker/operator of the staker acted maliciously in any sort of way.On the contrary the logic that enforces this in the
Issuancecontract implements the following access control:block.number > withdraw.startBlock + vault.delegationManager().withdrawalDelayBlocks()It explicitly requires that the current block is greater than the queue start plus the delay period, thus allowing early withdrawals even when the EigenLayer withdrawal has already matured. This allows matured loans to be "bought out” in a block they are already completable in, thus introducing unexpected behavior and possible loss of funds for the
oldOwnerthrough MEV.Recommendation
block.number >= withdraw.startBlock + vault.delegationManager().withdrawalDelayBlocks()
Resolution
Rest Team: The issue was resolved in commit 373eed6.
-
ISU-6 Medium Users Can Avoid Withdrawal Fee Logical Error Acknowledged
Description
When a user submits a withdrawal, either through direct assets or through EigenLayer, a fee is deducted. When using the Issuance contract, users have the possibility to complete other's withdraws by paying the due value and changing ownership of the pending withdrawal.
This mechanism, however, does not deduct any fees from the new owner, this results in:
- Disincentivizing anyone from initiating a direct withdrawal from EigenLayer and simply waiting for others to initiate the withdraw and changing it, so that they may not pay the fee
- The protocol does not receive fees for withdrawal done in this manner
Recommendation
Deduct the withdraw fee also on the
Issuancecontract when doing ancompleteWithdrawEarlyfunction call.Resolution
Rest Team: Acknowledged.
-
ISU-7 Medium Pending Withdrawals Can Be Bought When Eigen Is Paused Logical Error Resolved
Description
Users that queue their withdraw on EigenLayer using the
Issuancecontract can have their withdrawals taken over by others as long as they are paid the equivalent withdraw amount in exchange.This mechanism leaves a potential abuse situation when EigenLayer has withdrawal completion paused (
PAUSED_EXIT_WITHDRAWAL_QUEUE) so that nobody would be able to callcompleteQueuedWithdrawalat that time.During this time, users of the
Issuancecontract can take ownership of withdrawals that, unbeknown to them, can't be finalized at that time, basically buying into a blocked position.Recommendation
Do not allow changing the owner of pending withdrawals if EigenLayer has withdrawal completion paused. Checking that withdrawal completion is paused can be done by calling the
Pausable.paused(uint8)method with thePAUSED_EXIT_WITHDRAWAL_QUEUE(2)value.Resolution
Rest Team: Resolved.
-
PENW-1 Medium DoS pendingWithdrawals DoS Acknowledged
Description
There is no limit on the amount of pending withdrawals a user can have, nor is there a minimum amount of shares that ought to be redeemed per withdrawal. Therefore it can be economically viable to submit many withdrawals each with a single wei in order to expand the
pendingWithdrawslist for a malicious user.As a result third party contracts interacting with the
PendingWithdraws.withdrawsfunction can be DoS’d simply because the amount of gas required to load thependingWithdrawslist into memory is greater than the block gas limit.Recommendation
Consider adding either a limit on the amount of pending withdrawals a single user can have, or a minimum on the amount of shares necessary for a withdrawal to be queued or both.
Resolution
Rest Team: Acknowledged.
-
VLT-3 Medium Deposits Can Be Griefed Griefing Acknowledged
Description
In the
depositIntoEiganfunction, a user can to deposit an arbitrary amount of vault assets into EigenLayer. This being permissionless, allows the protocol to gain yield more efficiently because it will not have to wait for a trusted party to move these funds.The issue, however, is that the amount that is being deposited is not guaranteed to be available. If another user wanted to grief the protocol, they could do so by frontrunning a call to the
depositIntoEiganfunction by calling thewithdrawUsingAssetsfunction. They could then withdraw just enough from the vault so that the other user's deposit call will fail.Long term, this can pose a problem for the protocol because the way for users to gain yield through Rest is to have their funds deposited into EigenLayer. If a user can delay and reduce the efficiency with which these funds move to EigenLayer, the less yield the Rest users will receive.
Recommendation
Consider having a
depositIntoEiganfunction which deposits thebalanceOf(address(this))instead of a specified amount. This will prevent any griefing of deposits.Resolution
Rest Team: Acknowledged.
-
VLT-4 Medium Invalid Flagship LST Amount Transferred In Logical Error Resolved
Description
In the
deposit(uint256 assets, address _receiver)function the assets amount passed to thesuper.depositfunction is an ether value of the flagship asset amount. However the ERC4626depositfunction will transfer in the ether value amount of the flagship asset rather than the flagship asset amount.The value of the amount transferred in is correctly converted to shares as the
previewDepositfunction is correctly overridden in the Vault contract, however the user still transfers in an unexpected amount of the flagship asset.Consider the following example:
- Flagship asset price is 0.8 ether
- Bob calls
deposit(1 * 1e18, address(bob)) - The vault transfers 0.8 * 1e18 of the flagship asset from Bob
This is unexpected for Bob as he specified 1 * 1e18 of the flagship asset to be transferred in.
Recommendation
Do not convert the specified assets amount to an ether amount in the
deposit(uint256 assets,address receiver)function, as this conversion is already accounted for in thepreviewDepositfunction.Resolution
Rest Team: The issue was resolved in commit dcbb07b.
-
VLT-5 Medium Vault is not ERC4626 Compliant Compliance Partially resolved
Description
The
Vaultdoes not respect several requirements needed to beERC4626compliant, although the internal documentation states that it is compliant: /// @notice While this contract is fully ERC4626 compliant, it also has additional functionality The non-compliance issues are:withdrawandredeemalways revert; does not respect any of EIP requirements- if the
flagshipTokenis removed from the vault viaremoveLstthendepositreverts. At this pointmaxDepositmust return 0, but it does not. - ○ MUST factor in both global and user-specific limits, like if deposits are entirely disabled (even temporarily) it MUST return 0.
convertToSharesalso reverts when this is not allowed- ○ MUST NOT revert unless due to integer overflow caused by an unreasonably large input.
previewRedeemandpreviewWithdrawdo not take into consideration vault fees- ○ MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees.
Recommendation
Modify the above issues to match the standard. Also do not allow the flagship asset to be removed from the allowed LST list.
Resolution
Rest Team: Partially Resolved.
-
VLT-6 Medium Attacker can Front-Run Removal of Asset Frontrunning Acknowledged
Description
When an LST is removed with the
removeLstfunction, the value of that LST in the system will no longer be attributed to the totalAssets.Therefore a malicious actor may frontrun the removal of an LST and deposit that exact LST into the system before withdrawing that value in a different LST token, as a result the value of restEth will drop and holders will immediately lose the value of the removed LST tokens in the system.
If the protocol were to add the removed LST back it would create a positive stepwise jump in the value of restEth. This way an attacker could game the stepwise jump by depositing before the LST is added back and withdrawing afterwards for an immediate profit.
Recommendation
Introduce a mechanism for deposits of a certain LST to be paused, or deposits as a whole to be paused. Additionally, consider implementing validation that an LST cannot be removed unless the vault holds no value in the corresponding strategy and no balance of that LST directly in the vault contract. This prevents unlisted tokens from being lost to depositors.
Resolution
Rest Team: Acknowledged.
-
VLT-7 Medium Yield In Strategies Can Be Stolen Gaming Acknowledged
Description
In the event that yield is distributed to a strategy in a single transaction, that yield amount can be vampire attacked by a malicious actor. The actor may deposit into the vault right before the reward is distributed, and then withdraw the gained funds with their Rest shares. These funds end up siphoned from the veritable vault depositors.
Recommendation
Ensure the vault withdrawal fee is large enough to deter a vampire attack such as this. Additionally be sure to implement a fee for the
completeWithdrawEarlyfunction so that the attacker cannot avoid the fee by using the Issuance contract.Resolution
Rest Team: Acknowledged.
-
CONST-1 Low Unresolved TODO Best Practices Acknowledged
Description
In the Constants folder there is an unresolved TODO, which serves to warn to resolve the issue of the owner, fee receiver and starting withdrawal fee being 0.
Recommendation
Resolve the indicating TODO.
Resolution
Rest Team: Acknowledged.
-
GLOBAL-6 Low No Incentive For Unsupported Eigen Assets To Use Rest Incentives Acknowledged
Description
Currently Rest supports 6 LSTs for deposit, cbETH (Coinbase), stETH (Lido), rETH (Rocket Pool), sfrxETH (Frax), LsETH (Liquid) and mETH. Out of these, the last 3 are currently not supported by EigenLayer.
At this point in time, there is no incentive for users of the Rest protocol to deposit any tokens that do not have a backing EigenLayer strategy. Also, users lose funds on withdraw due to the fees, while not gaining any yield on their deposit.
Recommendation
Clearly document this and do not add LSTs without EigenLayer strategies to the Vault.
Resolution
Rest Team: Acknowledged.
Guardian: If mETH is to be supported, the current constant points to the staking contract rather than mETH itself. As a result, the mETH token will not be able to be transferred to or from the vault, preventing it from being used in Rest entirely.
-
GLOBAL-7 Low Use SafeERC20 For Token Operations Best Practices Resolved
Description
In the
EiganWithdrawlTracker._completeWithdrawandIssuance.completeWithdrawfunctions, upon sending funds to the user, the transfer function is used.Though often the token used will not have a transfer implementation that returns false instead of reverting, out of an abundance of caution safeTransfer should be used.
Recommendation
Consider using
safeTransferinstead oftransfer.Resolution
Rest Team: Unresolved.
-
GLOBAL-8 Low Possible Trapped Funds When Using LST With Blacklist Best Practices Acknowledged
Description
Some LSTs such as
cbEthhave a blacklist functionality, however the Rest system does not confirm that a withdrawer is not blacklisted upon queuing a withdrawal from Eigenlayer with the queueWithdrawals function.Therefore it is possible that withdrawals are queued which will attempt to send the LST tokens to blacklisted accounts upon completion of the withdrawal. These withdrawals will not be completable and the funds will be stuck.
Recommendation
Consider validating that the
msg.senderis not blacklisted for the_assetin thewithdrawUsingEigenSharesfunction.Resolution
Rest Team: Acknowledged.
-
GLOBAL-9 Low Redundant Code Superfluous Code Partially resolved
Description
There are several instances of duplicate, superfluous or redundant code in the project.
RestEthVault.addLstuses theisValidAssetmodifier instead of checking directlyRestEthVault.removeLsthas both theisValidAssetmodifier and checks again redundantly. Remove the direct checkRestEthVault.withdrawUsingEigenShareshas bothhasStrategymodifier and checks again redundantly. Remove the direct check
Recommendation
Implement the above mentioned changes.
Resolution
Rest Team: Partially Resolved.
.
-
GLOBAL-10 Low Missing Input Validations Validation Acknowledged
Description
There are several locations throughout the codebase where validation for input is not done.
RestEthVault.constructor_strategyManagerand_delegationManagerare not checked for zero address or for actually being the EigenLayer contracts.flagshipAssettoken address not checked foraddress(0)RestEthVault.addLstorRestEthVault._addLst_assetis not checked foraddress(0)RestEthVault.previewMint(address,uint256)there is noisValidAssetmodifier, although theadapter[_asset].price()execution reverts, it should be added for a clearer message
Recommendation
Add the above indicated validations.
Resolution
Rest Team: Acknowledged.
-
GLOBAL-11 Low Lack of CEI Reentrancy Resolved
Description
In the
Vault.withdrawUsingAssetsfunction the assets are transferred before the restate amount is burned from themsg.sender. This allows the tx execution to be passed to an arbitrary address with an invalid state in the case of tokens with callbacks.In the
EiganWithdrawlTracker._completeWithdrawfunction the transfer call occurs before theremovePendingfunction is invoked. Thought there is no obvious path to exploitation, the pending withdrawal ought to be removed from storage before the transfer is made.This avoids potentially passing tx execution to an arbitrary address with an invalid state in the case of tokens with callbacks.
Recommendation
- Out of an abundance of caution, perform the
safeTransferat the end of thewithdrawUsingAssets
function.
- Perform the transfer at the end of the
_completeWithdrawfunction.
Resolution
Rest Team: Resolved.
- Out of an abundance of caution, perform the
-
ISU-8 Low Pending Withdraws Not Cleared Best Practices Resolved
Description
In the
completeWithdrawfunction thependingWithdrawsare not cleared. There is no immediate risk, however thependingWithdrawought to be cleared for consistent accounting.Recommendation
Clear the
pendingWithdrawin thecompleteWithdrawfunction.Resolution
Rest Team: Unresolved.
-
ISU-9 Low New Owner Cannot Decide wantsETH Value Logical Error Acknowledged
Description
In the
completeWithdrawEarlyfunction the new owner does not get to change the value ofwantsEthfor the root, therefore if the new owner wishes to accept the underlying token as opposed to ether for an early withdrawal they do not have this optionality.Recommendation
Consider allowing the new owner to change the
wantsEthvalue in the event they would prefer a different option for an early withdrawal.Resolution
Rest Team: Acknowledged.
-
ISU-10 Low Potential Duplicate Of Withdrawal Funds Logical Error Resolved
Description
In the
completeWithdrawfunction the underlying token is sent from the Issuance contract regardless of if the withdrawal was queued through the Issuance contract.Therefore a user may queue a withdrawal by directly calling the
withdrawUsingEigenSharesfunction and complete it by calling thecompleteWithdrawfunction on the Issuance contract and receive tokens directly from the vault as well as from the Issuance contract, as long as the Issuance contract is holding enough balance of that token.Recommendation
Only allow withdrawals that were queued through the Issuance contract to be completed through the Issuance contract.
Resolution
Rest Team: Resolved.
-
VLT-8 Low Lack of Fee Basis Points Validation Validation Resolved
Description
The fee basis points has no validation when set, neither in the constructor nor in its dedicated setter. If set above 10,000 it will block all withdrawals because the subtraction when taking the fee out of the full amount would underflow
Recommendation
Validate when setting the fee basis points that it does not surpass 10,000.
Resolution
Rest Team: The issue was resolved in commit 23bfab1.
-
VLT-9 Low Zero Address Fee Receiver Bricks Withdrawals Logical Error Resolved
Description
If the
feeReceiverfor theVaultcontract is set toaddress(0), withdrawals will revert because theERC20._mintfunction reverts when minting toaddress(0).Recommendation
Do not allow setting a fee receiver as the zero address, both in the constructor or in the dedicated
setFeeReceiverfunction.Resolution
Rest Team: The issue was resolved in commit a23cfd4.
-
VLT-10 Low Missing Events On Key State Changes Events Acknowledged
Description
Throughout the Vault contract there are instances where important contract changes were made but no event was emitted.
- Setting the fee receiver via
Vault.setFeeReceiveror in the constructor - Setting the fee BPS via
Vault.setFeeBasisPointsor in the constructor - Adding or removing a LST from the vault
- Minting shares via the function
Vault.mintdoes not emit a Deposit event specific to this behavior
Recommendation
Emit events in all the mentioned locations.
Resolution
Rest Team: Acknowledged.
- Setting the fee receiver via
-
VLT-11 Low Added LST Lacks Adapter Validation Validation Partially resolved
Description
When a LST token is added to the
Vaultcontract, either via the constructor or theaddLstfunction, an adapter must always exist and be associated with the corresponding LST token.Currently there is no such check and mistakenly adding a token without an adaptor, or with an incorrect adaptor that would cause severe issues such as token mispricing. Furthermore, the strategy is written without validating that it is indeed compatible with that asset.
Recommendation
In the
_addLstfunction, validate that the_adapateris notaddress(0)and that theIValueAdapter.assetequals the asset it will be mapped to. Also validate that the adapter and strategy provided are indeed compatible with the asset in the_addLstfunction.Resolution
Rest Team: The issue was partially resolved in commit 7c3fe88.
-
VLT-12 Low Risk Of Too Many LSTs Validation Acknowledged
Description
In the EigenLayer StrategyManager contract there is a MAX_STAKER_STRATEGY_LIST_LENGTH of 32.
However in the
_addLstfunction there is no validation that the amount of supported assets is less than the MAX_STAKER_STRATEGY_LIST_LENGTH.Recommendation
Consider implementing a maximum of the MAX_STAKER_STRATEGY_LIST_LENGTH for the amount of LST tokens that can be added.
Resolution
Rest Team: Acknowledged.
-
VLT-13 Low Single Step Ownership Transfer Centralization Risk Acknowledged
Description
The Vault uses a single-step ownership transfer, which poses a potential risk if ownership was transferred to the wrong address.
Recommendation
Consider using OpenZeppelin’s
Ownable2Stepcontract.Resolution
Rest Team: Acknowledged.
-
VLT-14 Low Typo Typo Resolved
Description
In the
depositfunction, thereceiverparameter is misspelled asreciver.Recommendation
Replace
reciverwithreceiver.Resolution
Rest Team: Unresolved.
-
VLT-15 Low Withdraw Using Assets Can Always Be Blocked Logical Error Acknowledged
Description
Users of the Rest vault can opt to withdraw, not through EigenLayer but through funds directly available in the Vault. This option is provided with the
withdrawUsingAssetsfunction depending on token availability.This option can be completely blocked by a user continuously spamming the
depositIntoEiganfunction whenever funds are available in the vault, making the functionality potentially redundant.Recommendation
Consider adding a minimum cooldown wait period between subsequent calls to
depositIntoEigan, to allow users the chance to be able to withdraw the assets instantly. Otherwise, clearly document this behavior to users.Resolution
Rest Team: Acknowledged.
-
VLT-16 Low Missing Zero Fee Amount Check Validation Acknowledged
Description
In the
withdrawUsingEigenSharesfunction, thefeeamount is minted to thefeeReceiver. However, with a small withdrawal amount, it is possible for the fee to be 0. In this case, the protocol will attempt to mint 0 tokens to thefeeReceiver.This is inconsistent with the protocol's design, as seen in the
withdrawUsingAssetsfunction, thefeeamount is only minted iffeeis greater than 0.Recommendation
Implement the same check in the
withdrawUsingEigenSharesfunction that is in thewithdrawUsingAssetsfunction.Resolution
Rest Team: Acknowledged.
-
VLT-17 Low Fee Receiver Cannot Withdraw 100% of Amount Logical Error Resolved
Description
Whenever a withdrawal occurs, a percentage of the
restEthAmtpassed in by the caller will be designated as afeefor thefeeReceiver. When thefeeReceiverattempts to make their own withdraw using the collected fees, they will also have to pay the samefeeto themselves. This leads to a situation where 100% of the lst cannot easily be withdrawn.Recommendation
Consider not charging a fee if the
feeReceiveris the one withdrawing.Resolution
Rest Team: The issue was resolved in commit d93fb8e.
-
VLT-18 Low User Can Withdraw Before Slashing Future Changes Acknowledged
Description
In the future when slashing logic is implemented in Eigenlayer and Rest Finance adopts delegation logic, a malicious user may observe that a particular delegated allocation is about to be slashed and frontrun the slashing transaction to withdraw from a separate strategy in order to avoid losing funds due to slashing.
Recommendation
Consider making withdrawals a two step action which requires time to pass, therefore no user may trivially frontrun a slashing transaction to protect themselves. Otherwise consider implementing some other solution in the future that would prevent users from avoiding the downsides of delegated stakes being slashed.
Resolution
Rest Team: Acknowledged.
-
VLT-19 Low safeTransferFrom After External Call Reentrancy Acknowledged
Description
In the
deposit(address _asset, uint256 assets, address receiver)andmint(address _asset, uint256assets, address receiver)functions thesafeTransferFromcall should take place before the restEth shares are minted, therefore it would not be possible to hand over tx execution to an arbitrary address in the event of a token with callbacks.Recommendation
Perform the
safeTransferFromafter the shares/assets are computed and before the restEth is minted.Resolution
Rest Team: Acknowledged.
No findings match.
Invariants 16
The review's fuzzing suite asserted 16 invariants. 13 held and 3 did not.
Every invariant tested
| ID | Invariant | Result |
|---|---|---|
VLT-01 | Depositing Into Rest Increases User Share Balance | Held |
VLT-02 | Depositing Into Rest Increases Total Supply | Held |
VLT-03 | Depositing Specified LST Into Rest Decreased User Balance By Passed Amount | Broken |
VLT-04 | Depositing Into Eigen Does Not Change Vault Supply | Held |
VLT-05 | Depositing Into Eigen Increases Staker Strategy Shares | Held |
VLT-06 | Depositing Into Eigen Increases LST Balance of Strategy By Deposited Amount | Broken |
VLT-07 | Withdrawing With Assets Decreases Vault Supply By Share Amount Minus Fee | Held |
VLT-08 | Withdrawing With Assets Increases User LST Balance By Previewed Amount | Broken |
VLT-09 | Withdrawing Eigen Shares Decreases Staker Strategy Shares | Held |
VLT-10 | Withdrawing Eigen Shares Decreases User Vault Share Balance By Passed Amount | Held |
VLT-11 | Withdrawing Eigen Shares Decreases Vault's Total Assets | Held |
VLT-12 | Completing a Withdrawal Does Not Modify The Vault Supply | Held |
VLT-13 | Completing a Withdrawal Does Not Decrease User LST Balance | Held |
VLT-14 | Vault Supply = Sum of Actor Share Balances | Held |
VLT-15 | Total Assets Is Non-Zero If Vault Has LST Balance | Held |
VLT-16 | Total Assets Is Non-Zero If Vault Has Strategy Balance | Held |
Put your code through the same review.
This review started with a conversation about scope. Tell us what you are building and we will plan yours with you.