Skip to content

Conversation

@tamtamchik
Copy link
Member

@tamtamchik tamtamchik commented Nov 17, 2024

stVaults

New way of isolated staking, through separate vaults, with optional stETH liquidity

  • Support minting externally-backed stETH in the Lido contract
  • Extract protocol accounting to separate contract (two-step, better precision)
  • StakingVault + VaultHub
  • Dashboard
  • VaultFactory
  • OperatorGrid
  • PredepositGuarantee
  • LazyOracle

@folkyatina folkyatina changed the base branch from master to develop November 27, 2024 07:48
@folkyatina folkyatina changed the base branch from develop to master November 27, 2024 07:48
@TheDZhon TheDZhon self-requested a review November 27, 2024 08:05
Copy link
Contributor

@TheDZhon TheDZhon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👀

Copy link

@github-advanced-security github-advanced-security bot left a 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.

@github-actions
Copy link

github-actions bot commented Nov 27, 2024

badge

Hardhat Unit Tests Coverage Summary

Filename                                                                Stmts    Miss  Cover    Missing
--------------------------------------------------------------------  -------  ------  -------  -----------------------------------------------------------------------------------------------------------
contracts/0.4.24/Lido.sol                                                 281      11  96.09%   825-844, 940-952
contracts/0.4.24/StETH.sol                                                 80       0  100.00%
contracts/0.4.24/StETHPermit.sol                                           15       0  100.00%
contracts/0.4.24/lib/Packed64x4.sol                                         5       0  100.00%
contracts/0.4.24/lib/SigningKeys.sol                                       36       0  100.00%
contracts/0.4.24/lib/StakeLimitUtils.sol                                   41       0  100.00%
contracts/0.4.24/nos/NodeOperatorsRegistry.sol                            435       0  100.00%
contracts/0.4.24/utils/Pausable.sol                                         9       0  100.00%
contracts/0.4.24/utils/UnstructuredStorageExt.sol                          14       0  100.00%
contracts/0.4.24/utils/Versioned.sol                                        5       0  100.00%
contracts/0.6.12/WstETH.sol                                                17       0  100.00%
contracts/0.8.25/ValidatorExitDelayVerifier.sol                            75       0  100.00%
contracts/0.8.25/utils/AccessControlConfirmable.sol                         2       0  100.00%
contracts/0.8.25/utils/Confirmable2Addresses.sol                            5       0  100.00%
contracts/0.8.25/utils/Confirmations.sol                                   37       0  100.00%
contracts/0.8.25/utils/PausableUntilWithRoles.sol                           3       0  100.00%
contracts/0.8.25/vaults/LazyOracle.sol                                    134      18  86.57%   203-209, 248, 276-279, 436, 449, 467, 515, 556-558, 650, 658
contracts/0.8.25/vaults/OperatorGrid.sol                                  196       1  99.49%   203
contracts/0.8.25/vaults/PinnedBeaconProxy.sol                               6       0  100.00%
contracts/0.8.25/vaults/StakingVault.sol                                  111      14  87.39%   307-341
contracts/0.8.25/vaults/ValidatorConsolidationRequests.sol                 48       3  93.75%   183, 187, 199
contracts/0.8.25/vaults/VaultFactory.sol                                   34       0  100.00%
contracts/0.8.25/vaults/VaultHub.sol                                      425      76  82.12%   257-266, 281-287, 342-366, 383, 552-553, 595-688, 997-999, 1087-1091, 1147, 1202-1209, 1495-1496, 1511-1521
contracts/0.8.25/vaults/dashboard/Dashboard.sol                           137       8  94.16%   183-201, 327, 636-649
contracts/0.8.25/vaults/dashboard/NodeOperatorFee.sol                      70       0  100.00%
contracts/0.8.25/vaults/dashboard/Permissions.sol                          47       2  95.74%   321-330
contracts/0.8.25/vaults/interfaces/IPinnedBeaconProxy.sol                   0       0  100.00%
contracts/0.8.25/vaults/interfaces/IPredepositGuarantee.sol                 0       0  100.00%
contracts/0.8.25/vaults/interfaces/IStakingVault.sol                        0       0  100.00%
contracts/0.8.25/vaults/interfaces/IVaultFactory.sol                        0       0  100.00%
contracts/0.8.25/vaults/lib/PinnedBeaconUtils.sol                           5       0  100.00%
contracts/0.8.25/vaults/lib/RecoverTokens.sol                               5       0  100.00%
contracts/0.8.25/vaults/lib/RefSlotCache.sol                               36       0  100.00%
contracts/0.8.25/vaults/predeposit_guarantee/CLProofVerifier.sol           16       1  93.75%   214
contracts/0.8.25/vaults/predeposit_guarantee/MeIfNobodyElse.sol             3       0  100.00%
contracts/0.8.25/vaults/predeposit_guarantee/PredepositGuarantee.sol      213      12  94.37%   482-502, 531, 670, 677, 699
contracts/0.8.9/Accounting.sol                                             96       2  97.92%   349-350
contracts/0.8.9/BeaconChainDepositor.sol                                   21       2  90.48%   48, 51
contracts/0.8.9/Burner.sol                                                 92       0  100.00%
contracts/0.8.9/DepositSecurityModule.sol                                 128       0  100.00%
contracts/0.8.9/EIP712StETH.sol                                            16       0  100.00%
contracts/0.8.9/LidoExecutionLayerRewardsVault.sol                         16       0  100.00%
contracts/0.8.9/LidoLocator.sol                                            26       0  100.00%
contracts/0.8.9/OracleDaemonConfig.sol                                     28       0  100.00%
contracts/0.8.9/StakingRouter.sol                                         305       0  100.00%
contracts/0.8.9/TokenRateNotifier.sol                                      36      36  0.00%    35-130
contracts/0.8.9/TriggerableWithdrawalsGateway.sol                          54       1  98.15%   271
contracts/0.8.9/WithdrawalQueue.sol                                        88       0  100.00%
contracts/0.8.9/WithdrawalQueueBase.sol                                   146       0  100.00%
contracts/0.8.9/WithdrawalQueueERC721.sol                                  89       0  100.00%
contracts/0.8.9/WithdrawalVault.sol                                        32       0  100.00%
contracts/0.8.9/WithdrawalVaultEIP7002.sol                                 21       0  100.00%
contracts/0.8.9/lib/ExitLimitUtils.sol                                     35       0  100.00%
contracts/0.8.9/lib/Math.sol                                                4       0  100.00%
contracts/0.8.9/lib/PositiveTokenRebaseLimiter.sol                         22       0  100.00%
contracts/0.8.9/lib/UnstructuredRefStorage.sol                              2       0  100.00%
contracts/0.8.9/oracle/AccountingOracle.sol                               174       0  100.00%
contracts/0.8.9/oracle/BaseOracle.sol                                      89       1  98.88%   401
contracts/0.8.9/oracle/HashConsensus.sol                                  263       1  99.62%   1005
contracts/0.8.9/oracle/ValidatorsExitBus.sol                              138      10  92.75%   458-471, 541
contracts/0.8.9/oracle/ValidatorsExitBusOracle.sol                         52       1  98.08%   217
contracts/0.8.9/proxy/OssifiableProxy.sol                                  17       0  100.00%
contracts/0.8.9/proxy/WithdrawalsManagerProxy.sol                          60       0  100.00%
contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol               232      12  94.83%   307-309, 600-605, 800-835, 956
contracts/0.8.9/utils/DummyEmptyContract.sol                                0       0  100.00%
contracts/0.8.9/utils/PausableUntil.sol                                    31       0  100.00%
contracts/0.8.9/utils/Versioned.sol                                        11       0  100.00%
contracts/0.8.9/utils/access/AccessControl.sol                             23       0  100.00%
contracts/0.8.9/utils/access/AccessControlEnumerable.sol                    9       0  100.00%
contracts/common/utils/PausableUntil.sol                                   29       0  100.00%
TOTAL                                                                    4931     212  95.70%

Diff against master

Filename                                                                Stmts    Miss  Cover
--------------------------------------------------------------------  -------  ------  --------
contracts/0.4.24/Lido.sol                                                 +69     +11  -3.91%
contracts/0.4.24/StETH.sol                                                 +8       0  +100.00%
contracts/0.4.24/lib/StakeLimitUtils.sol                                   +4       0  +100.00%
contracts/0.4.24/utils/UnstructuredStorageExt.sol                         +14       0  +100.00%
contracts/0.8.25/utils/AccessControlConfirmable.sol                        +2       0  +100.00%
contracts/0.8.25/utils/Confirmable2Addresses.sol                           +5       0  +100.00%
contracts/0.8.25/utils/Confirmations.sol                                  +37       0  +100.00%
contracts/0.8.25/utils/PausableUntilWithRoles.sol                          +3       0  +100.00%
contracts/0.8.25/vaults/LazyOracle.sol                                   +134     +18  +86.57%
contracts/0.8.25/vaults/OperatorGrid.sol                                 +196      +1  +99.49%
contracts/0.8.25/vaults/PinnedBeaconProxy.sol                              +6       0  +100.00%
contracts/0.8.25/vaults/StakingVault.sol                                 +111     +14  +87.39%
contracts/0.8.25/vaults/ValidatorConsolidationRequests.sol                +48      +3  +93.75%
contracts/0.8.25/vaults/VaultFactory.sol                                  +34       0  +100.00%
contracts/0.8.25/vaults/VaultHub.sol                                     +425     +76  +82.12%
contracts/0.8.25/vaults/dashboard/Dashboard.sol                          +137      +8  +94.16%
contracts/0.8.25/vaults/dashboard/NodeOperatorFee.sol                     +70       0  +100.00%
contracts/0.8.25/vaults/dashboard/Permissions.sol                         +47      +2  +95.74%
contracts/0.8.25/vaults/interfaces/IPinnedBeaconProxy.sol                   0       0  +100.00%
contracts/0.8.25/vaults/interfaces/IPredepositGuarantee.sol                 0       0  +100.00%
contracts/0.8.25/vaults/interfaces/IStakingVault.sol                        0       0  +100.00%
contracts/0.8.25/vaults/interfaces/IVaultFactory.sol                        0       0  +100.00%
contracts/0.8.25/vaults/lib/PinnedBeaconUtils.sol                          +5       0  +100.00%
contracts/0.8.25/vaults/lib/RecoverTokens.sol                              +5       0  +100.00%
contracts/0.8.25/vaults/lib/RefSlotCache.sol                              +36       0  +100.00%
contracts/0.8.25/vaults/predeposit_guarantee/CLProofVerifier.sol          +16      +1  +93.75%
contracts/0.8.25/vaults/predeposit_guarantee/MeIfNobodyElse.sol            +3       0  +100.00%
contracts/0.8.25/vaults/predeposit_guarantee/PredepositGuarantee.sol     +213     +12  +94.37%
contracts/0.8.9/Accounting.sol                                            +96      +2  +97.92%
contracts/0.8.9/Burner.sol                                                +21       0  +100.00%
contracts/0.8.9/LidoLocator.sol                                            +6       0  +100.00%
contracts/0.8.9/TokenRateNotifier.sol                                     +36     +36  +100.00%
contracts/0.8.9/oracle/AccountingOracle.sol                               -19       0  +100.00%
contracts/0.8.9/oracle/ValidatorsExitBus.sol                                0      +9  -6.53%
contracts/0.8.9/proxy/WithdrawalsManagerProxy.sol                         +60       0  +100.00%
contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol                 0     +12  -5.17%
contracts/common/utils/PausableUntil.sol                                  +29       0  +100.00%
TOTAL                                                                   +1857    +205  -1.62%

Results for commit: 5c36d33

Minimum allowed coverage is 80%

♻️ This comment has been updated with latest results

Copy link
Contributor

@TheDZhon TheDZhon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👀

Copy link
Contributor

@TheDZhon TheDZhon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👀

@tamtamchik tamtamchik changed the base branch from master to develop November 29, 2024 11:56
Copy link
Contributor

@TheDZhon TheDZhon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👀

Copy link
Contributor

@TheDZhon TheDZhon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👀 👀 👀

Copy link
Contributor

@TheDZhon TheDZhon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚛

Copy link
Contributor

@TheDZhon TheDZhon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚛 🚛 🚛

@tamtamchik tamtamchik added solidity Smart contract code changes next upgrade Things to pickup for the next protocol upgrade vaults Lido stVaults related changes labels Dec 12, 2024
Copy link
Contributor

@TheDZhon TheDZhon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First lap finished 👍

Comment on lines +1285 to +1287
function _getClBalanceAndClValidators() internal view returns (uint256, uint256) {
return CL_BALANCE_AND_CL_VALIDATORS_POSITION.getLowAndHighUint128();
}

Check warning

Code scanning / Slither

Unused return Medium

Comment on lines +382 to +410
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

Comment on lines +372 to +417
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
});
}
Comment on lines +1155 to +1182
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

VaultHub._increaseLiability(address,VaultHub.VaultRecord,uint256,uint256,uint256,uint256,bool) contains integer variables whose type is larger than the type of one of its intermediate expressions. Consider casting sub expressions explicitly as they might lead to unexpected overflow:
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.
Comment on lines +1427 to +1445
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

VaultHub._settleLidoFees(address,VaultHub.VaultRecord,VaultHub.VaultConnection,uint256) contains integer variables whose type is larger than the type of one of its intermediate expressions. Consider casting sub expressions explicitly as they might lead to unexpected overflow:
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.
Comment on lines +256 to +265
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;
}
Comment on lines +279 to +285
function voluntaryDisconnect() external {
// fee are not disbursed to the feeRecipient address to avoid reverts blocking the disconnection
_collectFeeLeftover();
_stopFeeAccrual();

_voluntaryDisconnect();
}
Comment on lines +275 to +302
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);
}
}
Comment on lines +439 to +455
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

Comment on lines +439 to +455
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

Ziars
Ziars previously approved these changes Dec 2, 2025
feat: remove SetLiabilitySharesTarget factory from vote script
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

solidity Smart contract code changes tests When it comes to testing the code vaults Lido stVaults related changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.