# Vlad Audit Report # 1. Critical. EthStrategy can't claim withdrawn ETH from Lido ## Description In function `claimLidoWithdrawals()` EthStrategy expects to receive withdrawn ETH from Lido, however it doesn't have a function to receive ETH: there is no `receive() payable` or `fallback() payable`. As a result, ETH can't be withdrawn, and withdrawal NFT can't be transferred either. ## Recommendation Add function `receive() payable`. Also add end-to-end tests to ensure protocol works as expected # 2. High. Funds withdrawn via `EthStrategy.ownerWithdraw()` will stuck in StakingManager ## Description Owner can withdraw ETH to StakingManager.sol via this function. However funds can't be retrieved from StakingManager contract, there is no such a method. ```solidity function ownerWithdraw(uint256 amount) external onlyOwner returns (uint256 withdrawnAmount) { (bool success, bytes memory data) = stakingManager.call{ value: amount }(""); if (!success) revert TransferFailed(data); emit EthWithdrawn(amount); return amount; } ``` ## Recommendation Remove function `ownerWithdraw()`. ETH balance in `EthStrategy` belongs to WrappedEth holders, not to owner. # 3. Medium. Incorrect role management of `address staker` and `address depositor` ## Description Addresses `staker` and `depositor` are meant to be different: `depositor` is EdgelessDeposit, staker is EOA However functions which are called by EdgelessDeposit have `onlyStaker` modifier, hence can't be called by EdgelessDeposit: ```solidity function stake(address asset, uint256 amount) external payable onlyStaker { _stakeEth(msg.value); emit Stake(asset, amount); } function withdraw(uint256 amount) external onlyStaker { _withdrawEth(amount); } ``` ## Recommendation Refactor access control # 4. Medium. `mintEthBasedOnStakedAmount()` can mint less than expected ## Description According to Natspec: ```solidity /** * @notice Mint wrapped tokens based on the amount of Eth staked @> * @dev The owner can only mint up to the amount of Eth deposited + Eth staking rewards from Lido * @param to Address to mint wrapped tokens to * @param amount Amount of wrapped tokens to mint */ function mintEthBasedOnStakedAmount(address to, uint256 amount) external onlyOwner { uint256 maxMint = stakingManager.getAssetTotal(stakingManager.ETH_ADDRESS()) - wrappedEth.totalSupply(); if (maxMint < amount) revert MaxMintExceeded(); wrappedEth.mint(to, amount); emit MintWrappedEth(to, amount); } ``` Let's take a look on how `underlyingAssetAmount()` is calculated. It doesn't take into consideration pending withdrawals requested via `requestLidoWithdrawal()`. ```solidity function underlyingAssetAmount() external view returns (uint256) { return address(this).balance + LIDO.balanceOf(address(this)); } ``` ## Recommendation According to Lido documentation, withdrawn ETH amount can't be greater than requested stETH amount, however can be slightly less. Keep # 5. Low. Bridged funds can be lost if `l2Eth = address(0)` ## Description Soon after deployment user can call `depositEth()` when all values are set except `l2Eth`. And if `autoBridge = true`, bridged funds will be lost: ```solidity function _bridgeToL2(WrappedToken wrappedToken, address l2WrappedToken, address to, uint256 amount) internal { if (autoBridge) { wrappedToken.approve(address(l1standardBridge), amount); l1standardBridge.depositERC20To(address(wrappedToken), l2WrappedToken, to, amount, 0, ""); emit BridgeToL2(address(wrappedToken), l2WrappedToken, to, amount); } } ``` ## Recommendation Add check to ensure that values are correctly set: ```diff function _bridgeToL2(WrappedToken wrappedToken, address l2WrappedToken, address to, uint256 amount) internal { if (autoBridge) { + require(l2Eth != address(0)); wrappedToken.approve(address(l1standardBridge), amount); l1standardBridge.depositERC20To(address(wrappedToken), l2WrappedToken, to, amount, 0, ""); emit BridgeToL2(address(wrappedToken), l2WrappedToken, to, amount); } } ``` # 6. Low. `underlyingAssetAmount()` treats stETH is equal to ETH ## Description Here ETH balance is mixed with stETH balance: ```solidity function underlyingAssetAmount() external view returns (uint256) { return address(this).balance + LIDO.balanceOf(address(this)); } ``` However historically stETH had deviations from ETH: https://dune.com/LidoAnalytical/Curve-ETHstETH ## Recommendation Consider using Oracle to convert stETH into ETH # 7. Info. Make sure that array `requestIds` in function `claimLidoWithdrawals` is sorted Otherwise some `requestIds` won't be processed accroding to Lido docs: ```solidity /// @notice Finds the list of hints for the given `_requestIds` searching among the checkpoints with indices /// in the range `[_firstIndex, _lastIndex]`. @> /// NB! Array of request ids should be sorted ... function findCheckpointHints(uint256[] calldata _requestIds, uint256 _firstIndex, uint256 _lastIndex) ```