-
Notifications
You must be signed in to change notification settings - Fork 262
[EPIC] stVaults #874
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
[EPIC] stVaults #874
Conversation
TheDZhon
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👀
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Slither found more than 20 potential problems in the proposed changes. Check the Files changed tab for more details.
Hardhat Unit Tests Coverage SummaryDiff against masterResults for commit: 5c36d33 Minimum allowed coverage is ♻️ This comment has been updated with latest results |
TheDZhon
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👀
TheDZhon
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👀
...acts/openzeppelin/5.0.2/upgradeable/access/extensions/AccessControlEnumerableUpgradeable.sol
Fixed
Show fixed
Hide fixed
...acts/openzeppelin/5.0.2/upgradeable/access/extensions/AccessControlEnumerableUpgradeable.sol
Fixed
Show fixed
Hide fixed
TheDZhon
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👀
TheDZhon
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👀 👀 👀
TheDZhon
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚛
TheDZhon
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚛 🚛 🚛
TheDZhon
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
First lap finished 👍
Unify scratch tests with mainnet params
feat: hoodi patch 4
feat: log explorer links in deploy
feat: minor updates in V3VoteScript
| function _getClBalanceAndClValidators() internal view returns (uint256, uint256) { | ||
| return CL_BALANCE_AND_CL_VALIDATORS_POSITION.getLowAndHighUint128(); | ||
| } |
Check warning
Code scanning / Slither
Unused return Medium
| function triggerValidatorWithdrawals( | ||
| bytes calldata _pubkeys, | ||
| uint64[] calldata _amountsInGwei, | ||
| address _excessRefundRecipient | ||
| ) external payable onlyOwner { | ||
| if (msg.value == 0) revert ZeroArgument("msg.value"); | ||
| if (_pubkeys.length == 0) revert ZeroArgument("_pubkeys"); | ||
| if (_pubkeys.length % PUBLIC_KEY_LENGTH != 0) revert InvalidPubkeysLength(); | ||
| if (_excessRefundRecipient == address(0)) revert ZeroArgument("_excessRefundRecipient"); | ||
|
|
||
| uint256 feePerRequest = TriggerableWithdrawals.getWithdrawalRequestFee(); | ||
| uint256 totalFee = (_pubkeys.length / PUBLIC_KEY_LENGTH) * feePerRequest; | ||
| if (msg.value < totalFee) revert InsufficientValidatorWithdrawalFee(msg.value, totalFee); | ||
|
|
||
| // If amounts array is empty, trigger full withdrawals, otherwise use amount-driven withdrawal types | ||
| if (_amountsInGwei.length == 0) { | ||
| TriggerableWithdrawals.addFullWithdrawalRequests(_pubkeys, feePerRequest); | ||
| } else { | ||
| TriggerableWithdrawals.addWithdrawalRequests(_pubkeys, _amountsInGwei, feePerRequest); | ||
| } | ||
|
|
||
| uint256 excess = msg.value - totalFee; | ||
| if (excess > 0) { | ||
| (bool success, ) = _excessRefundRecipient.call{value: excess}(""); | ||
| if (!success) revert TransferFailed(_excessRefundRecipient, excess); | ||
| } | ||
|
|
||
| emit ValidatorWithdrawalsTriggered(_pubkeys, _amountsInGwei, excess, _excessRefundRecipient); | ||
| } |
Check warning
Code scanning / Slither
Divide before multiply Medium
- totalFee = (_pubkeys.length / PUBLIC_KEY_LENGTH) * feePerRequest
| function connectVault(address _vault) external whenResumed { | ||
| _requireNotZero(_vault); | ||
|
|
||
| if (!IVaultFactory(LIDO_LOCATOR.vaultFactory()).deployedVaults(_vault)) revert VaultNotFactoryDeployed(_vault); | ||
| IStakingVault vault_ = IStakingVault(_vault); | ||
| _requireSender(vault_.owner()); | ||
| if (vault_.pendingOwner() != address(this)) revert VaultHubNotPendingOwner(_vault); | ||
| if (IPinnedBeaconProxy(address(vault_)).isOssified()) revert VaultOssified(_vault); | ||
| if (vault_.depositor() != address(_predepositGuarantee())) revert PDGNotDepositor(_vault); | ||
| // we need vault to match staged balance with pendingActivations | ||
| if (vault_.stagedBalance() != _predepositGuarantee().pendingActivations(vault_) * PDG_ACTIVATION_DEPOSIT) { | ||
| revert InsufficientStagedBalance(_vault); | ||
| } | ||
|
|
||
| ( | ||
| , // nodeOperatorInTier | ||
| , // tierId | ||
| uint256 shareLimit, | ||
| uint256 reserveRatioBP, | ||
| uint256 forcedRebalanceThresholdBP, | ||
| uint256 infraFeeBP, | ||
| uint256 liquidityFeeBP, | ||
| uint256 reservationFeeBP | ||
| ) = _operatorGrid().vaultTierInfo(_vault); | ||
|
|
||
| _connectVault(_vault, | ||
| shareLimit, | ||
| reserveRatioBP, | ||
| forcedRebalanceThresholdBP, | ||
| infraFeeBP, | ||
| liquidityFeeBP, | ||
| reservationFeeBP | ||
| ); | ||
|
|
||
| IStakingVault(_vault).acceptOwnership(); | ||
|
|
||
| emit VaultConnected({ | ||
| vault: _vault, | ||
| shareLimit: shareLimit, | ||
| reserveRatioBP: reserveRatioBP, | ||
| forcedRebalanceThresholdBP: forcedRebalanceThresholdBP, | ||
| infraFeeBP: infraFeeBP, | ||
| liquidityFeeBP: liquidityFeeBP, | ||
| reservationFeeBP: reservationFeeBP | ||
| }); | ||
| } |
Check warning
Code scanning / Slither
Unused return Medium
| function _increaseLiability( | ||
| address _vault, | ||
| VaultRecord storage _record, | ||
| uint256 _amountOfShares, | ||
| uint256 _reserveRatioBP, | ||
| uint256 _lockableValueLimit, | ||
| uint256 _shareLimit, | ||
| bool _overrideOperatorLimits | ||
| ) internal { | ||
| uint256 sharesAfterMint = _record.liabilityShares + _amountOfShares; | ||
| if (sharesAfterMint > _shareLimit) { | ||
| revert ShareLimitExceeded(_vault, sharesAfterMint, _shareLimit); | ||
| } | ||
|
|
||
| // Calculate the minimum ETH that needs to be locked in the vault to maintain the reserve ratio | ||
| uint256 etherToLock = _locked(sharesAfterMint, _record.minimalReserve, _reserveRatioBP); | ||
| if (etherToLock > _lockableValueLimit) { | ||
| revert InsufficientValue(_vault, etherToLock, _lockableValueLimit); | ||
| } | ||
|
|
||
| if (sharesAfterMint > _record.maxLiabilityShares) { | ||
| _record.maxLiabilityShares = uint96(sharesAfterMint); | ||
| } | ||
|
|
||
| _record.liabilityShares = uint96(sharesAfterMint); | ||
|
|
||
| _operatorGrid().onMintedShares(_vault, _amountOfShares, _overrideOperatorLimits); | ||
| } |
Check warning
Code scanning / Slither
Potential Arithmetic Overflow Medium
In [sharesAfterMint = _record.liabilityShares + _amountOfShares](contracts/0.8.25/vaults/VaultHub.sol#L1164) intermidiate expressions returns type of lower order:
REF_879 + _amountOfShares returns uint96, but the type of the resulting expression is uint256.
| function _settleLidoFees( | ||
| address _vault, | ||
| VaultRecord storage _record, | ||
| VaultConnection storage _connection, | ||
| uint256 _valueToSettle | ||
| ) internal { | ||
| uint256 settledLidoFees = _record.settledLidoFees + _valueToSettle; | ||
| _record.settledLidoFees = uint128(settledLidoFees); | ||
|
|
||
| _withdraw(_vault, _record, LIDO_LOCATOR.treasury(), _valueToSettle); | ||
| _updateBeaconChainDepositsPause(_vault, _record, _connection); | ||
|
|
||
| emit LidoFeesSettled({ | ||
| vault: _vault, | ||
| transferred: _valueToSettle, | ||
| cumulativeLidoFees: _record.cumulativeLidoFees, | ||
| settledLidoFees: settledLidoFees | ||
| }); | ||
| } |
Check warning
Code scanning / Slither
Potential Arithmetic Overflow Medium
In [settledLidoFees = _record.settledLidoFees + _valueToSettle](contracts/0.8.25/vaults/VaultHub.sol#L1433) intermidiate expressions returns type of lower order:
REF_949 + _valueToSettle returns uint128, but the type of the resulting expression is uint256.
| function transferVaultOwnership(address _newOwner) external returns (bool) { | ||
| if (_newOwner == address(this)) revert DashboardNotAllowed(); | ||
| if (!_collectAndCheckConfirmations(msg.data, confirmingRoles())) return false; | ||
|
|
||
| disburseFee(); | ||
| _stopFeeAccrual(); | ||
|
|
||
| VAULT_HUB.transferVaultOwnership(address(_stakingVault()), _newOwner); | ||
| return true; | ||
| } |
Check warning
Code scanning / Slither
Reentrancy vulnerabilities Medium
External calls:
- disburseFee()
- VAULT_HUB.withdraw(address(_stakingVault()),_recipient,_ether)
State variables written after the call(s):
- _stopFeeAccrual()
- settledGrowth = _newSettledGrowth.toInt128()
NodeOperatorFee.settledGrowth can be used in cross function reentrancies:
- NodeOperatorFee._addFeeExemption(uint256)
- NodeOperatorFee._calculateFee()
- NodeOperatorFee._disburseFee(uint256,int256,address)
- NodeOperatorFee._setSettledGrowth(int256)
- NodeOperatorFee._stopFeeAccrual()
- Dashboard.connectToVaultHub()
- NodeOperatorFee.correctSettledGrowth(int256,int256)
- NodeOperatorFee.settledGrowth
| function voluntaryDisconnect() external { | ||
| // fee are not disbursed to the feeRecipient address to avoid reverts blocking the disconnection | ||
| _collectFeeLeftover(); | ||
| _stopFeeAccrual(); | ||
|
|
||
| _voluntaryDisconnect(); | ||
| } |
Check warning
Code scanning / Slither
Reentrancy vulnerabilities Medium
External calls:
- _collectFeeLeftover()
- VAULT_HUB.withdraw(address(_stakingVault()),_recipient,_ether)
State variables written after the call(s):
- _stopFeeAccrual()
- settledGrowth = _newSettledGrowth.toInt128()
NodeOperatorFee.settledGrowth can be used in cross function reentrancies:
- NodeOperatorFee._addFeeExemption(uint256)
- NodeOperatorFee._calculateFee()
- NodeOperatorFee._disburseFee(uint256,int256,address)
- NodeOperatorFee._setSettledGrowth(int256)
- NodeOperatorFee._stopFeeAccrual()
- Dashboard.connectToVaultHub()
- NodeOperatorFee.correctSettledGrowth(int256,int256)
- NodeOperatorFee.settledGrowth
| function _calculateTotalProtocolFeeShares( | ||
| ReportValues calldata _report, | ||
| CalculatedValues memory _update, | ||
| uint256 _internalSharesBeforeFees, | ||
| uint256 _totalFee, | ||
| uint256 _feePrecisionPoints | ||
| ) internal pure returns (uint256 sharesToMintAsFees) { | ||
| // we are calculating the share rate equal to the post-rebase share rate | ||
| // but with fees taken as ether deduction instead of minting shares | ||
| // to learn the amount of shares we need to mint to compensate for this fee | ||
|
|
||
| uint256 unifiedClBalance = _report.clBalance + _update.withdrawalsVaultTransfer; | ||
| // Don't mint/distribute any protocol fee on the non-profitable Lido oracle report | ||
| // (when consensus layer balance delta is zero or negative). | ||
| // See LIP-12 for details: | ||
| // https://research.lido.fi/t/lip-12-on-chain-part-of-the-rewards-distribution-after-the-merge/1625 | ||
| if (unifiedClBalance > _update.principalClBalance) { | ||
| uint256 totalRewards = unifiedClBalance - _update.principalClBalance + _update.elRewardsVaultTransfer; | ||
| // amount of fees in ether | ||
| uint256 feeEther = (totalRewards * _totalFee) / _feePrecisionPoints; | ||
| // but we won't pay fees in ether, so we need to calculate how many shares we need to mint as fees | ||
| // using the share rate that takes fees into account | ||
| // the share rate is the same as the post-rebase share rate | ||
| // but with fees taken as ether deduction instead of minting shares | ||
| // to learn the amount of shares we need to mint to compensate for this fee | ||
| sharesToMintAsFees = (feeEther * _internalSharesBeforeFees) / (_update.postInternalEther - feeEther); | ||
| } | ||
| } |
Check warning
Code scanning / Slither
Divide before multiply Medium
- feeEther = (totalRewards * _totalFee) / _feePrecisionPoints
- sharesToMintAsFees = (feeEther * _internalSharesBeforeFees) / (_update.postInternalEther - feeEther)
| function _distributeFee(FeeDistribution memory _feeDistribution) internal { | ||
| address[] memory recipients = _feeDistribution.moduleFeeRecipients; | ||
| uint256[] memory sharesToMint = _feeDistribution.moduleSharesToMint; | ||
| uint256 length = recipients.length; | ||
|
|
||
| for (uint256 i; i < length; ++i) { | ||
| uint256 moduleShares = sharesToMint[i]; | ||
| if (moduleShares > 0) { | ||
| LIDO.transferShares(recipients[i], moduleShares); | ||
| } | ||
| } | ||
|
|
||
| uint256 treasuryShares = _feeDistribution.treasurySharesToMint; | ||
| if (treasuryShares > 0) { // zero is an edge case when all fees goes to modules | ||
| LIDO.transferShares(LIDO_LOCATOR.treasury(), treasuryShares); | ||
| } | ||
| } |
Check warning
Code scanning / Slither
Unused return Medium
| function _distributeFee(FeeDistribution memory _feeDistribution) internal { | ||
| address[] memory recipients = _feeDistribution.moduleFeeRecipients; | ||
| uint256[] memory sharesToMint = _feeDistribution.moduleSharesToMint; | ||
| uint256 length = recipients.length; | ||
|
|
||
| for (uint256 i; i < length; ++i) { | ||
| uint256 moduleShares = sharesToMint[i]; | ||
| if (moduleShares > 0) { | ||
| LIDO.transferShares(recipients[i], moduleShares); | ||
| } | ||
| } | ||
|
|
||
| uint256 treasuryShares = _feeDistribution.treasurySharesToMint; | ||
| if (treasuryShares > 0) { // zero is an edge case when all fees goes to modules | ||
| LIDO.transferShares(LIDO_LOCATOR.treasury(), treasuryShares); | ||
| } | ||
| } |
Check warning
Code scanning / Slither
Unused return Medium
fix: lido impl check in kernel
Report Validator Exit Delay reenable
feat: remove SetLiabilitySharesTarget factory from vote script
stVaults
New way of isolated staking, through separate vaults, with optional stETH liquidity