Skip to content

Commit

Permalink
feat: integrate predeposit guarantee into locator
Browse files Browse the repository at this point in the history
  • Loading branch information
Jeday committed Feb 10, 2025
1 parent 7b61c6a commit 6e3b647
Show file tree
Hide file tree
Showing 11 changed files with 282 additions and 166 deletions.
42 changes: 18 additions & 24 deletions contracts/0.8.25/Accounting.sol
Original file line number Diff line number Diff line change
Expand Up @@ -83,16 +83,10 @@ contract Accounting is VaultHub {
/// @notice deposit size in wei (for pre-maxEB accounting)
uint256 private constant DEPOSIT_SIZE = 32 ether;

/// @notice Lido Locator contract
ILidoLocator public immutable LIDO_LOCATOR;
/// @notice Lido contract
ILido public immutable LIDO;

constructor(
ILidoLocator _lidoLocator,
ILido _lido
) VaultHub(_lido) {
LIDO_LOCATOR = _lidoLocator;
constructor(ILidoLocator _lidoLocator, ILido _lido) VaultHub(_lidoLocator, _lido) {
LIDO = _lido;
}

Expand Down Expand Up @@ -217,22 +211,27 @@ contract Accounting is VaultHub {
update.withdrawals -
update.principalClBalance + // total cl rewards (or penalty)
update.elRewards + // ELRewards
postExternalEther - _pre.externalEther // vaults rebase
- update.etherToFinalizeWQ; // withdrawals
postExternalEther -
_pre.externalEther - // vaults rebase
update.etherToFinalizeWQ; // withdrawals

// Calculate the amount of ether locked in the vaults to back external balance of stETH
// and the amount of shares to mint as fees to the treasury for each vaults
(update.vaultsLockedEther, update.vaultsTreasuryFeeShares, update.totalVaultsTreasuryFeeShares) =
_calculateVaultsRebase(
update.postTotalShares,
update.postTotalPooledEther,
_pre.totalShares,
_pre.totalPooledEther,
update.sharesToMintAsFees
);
(
update.vaultsLockedEther,
update.vaultsTreasuryFeeShares,
update.totalVaultsTreasuryFeeShares
) = _calculateVaultsRebase(
update.postTotalShares,
update.postTotalPooledEther,
_pre.totalShares,
_pre.totalPooledEther,
update.sharesToMintAsFees
);

update.postTotalPooledEther +=
update.totalVaultsTreasuryFeeShares * update.postTotalPooledEther / update.postTotalShares;
(update.totalVaultsTreasuryFeeShares * update.postTotalPooledEther) /
update.postTotalShares;
update.postTotalShares += update.totalVaultsTreasuryFeeShares;
}

Expand Down Expand Up @@ -305,12 +304,7 @@ contract Accounting is VaultHub {
];
}

LIDO.processClStateUpdate(
_report.timestamp,
_pre.clValidators,
_report.clValidators,
_report.clBalance
);
LIDO.processClStateUpdate(_report.timestamp, _pre.clValidators, _report.clValidators, _report.clBalance);

if (_update.totalSharesToBurn > 0) {
_contracts.burner.commitSharesToBurn(_update.totalSharesToBurn);
Expand Down
18 changes: 14 additions & 4 deletions contracts/0.8.25/vaults/StakingVault.sol
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,6 @@ contract StakingVault is IStakingVault, OwnableUpgradeable {
*/
uint64 private constant _VERSION = 1;

bytes32 public constant DEPOSIT_GUARDIAN_MESSAGE_PREFIX = keccak256("StakingVault.DepositGuardianMessagePrefix");

/**
* @notice Address of `VaultHub`
* Set immutably in the constructor to avoid storage costs
Expand Down Expand Up @@ -254,15 +252,27 @@ contract StakingVault is IStakingVault, OwnableUpgradeable {
/**
* @notice Returns the address of the node operator
* Node operator is the party responsible for managing the validators.
* In the context of this contract, the node operator performs deposits to the beacon chain
* and processes validator exit requests submitted by `owner` through `requestValidatorExit()`.
* In the context of this contract, the node operator runs vault validators on CL and
* processes validator exit requests submitted by `owner` through `requestValidatorExit()`.
* Node operator address is set in the initialization and can never be changed.
* @return Address of the node operator
*/
function nodeOperator() external view returns (address) {
return _getStorage().nodeOperator;
}

/**
* @notice Returns the address of the deposit guardian
* Trusted party responsible for securely depositing validators to the beacon chain.
* In the context of this contract, the deposit guardian performs deposits through `depositToBeaconChain()`.
* DepositGuardian address is set in the initialization and can be changed by the owner with `setDepositGuardian`
* only on the condition that the vault is not connected to the VaultHub.
* @return Address of the deposit guardian
*/
function depositGuardian() external view returns (address) {
return _getStorage().depositGuardian;
}

/**
* @notice Returns the 0x01-type withdrawal credentials for the validators deposited from this `StakingVault`
* All CL rewards are sent to this contract. Only 0x01-type withdrawal credentials are supported for now.
Expand Down
38 changes: 29 additions & 9 deletions contracts/0.8.25/vaults/VaultHub.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {IBeacon} from "@openzeppelin/contracts-v5.2/proxy/beacon/IBeacon.sol";
import {OwnableUpgradeable} from "contracts/openzeppelin/5.2/upgradeable/access/OwnableUpgradeable.sol";

import {IStakingVault} from "./interfaces/IStakingVault.sol";
import {ILidoLocator} from "contracts/common/interfaces/ILidoLocator.sol";
import {ILido as IStETH} from "../interfaces/ILido.sol";

import {PausableUntilWithRoles} from "../utils/PausableUntilWithRoles.sol";
Expand Down Expand Up @@ -72,11 +73,13 @@ abstract contract VaultHub is PausableUntilWithRoles {

/// @notice Lido stETH contract
IStETH public immutable STETH;
/// @notice Lido Locator contract
ILidoLocator public immutable LIDO_LOCATOR;

/// @param _stETH Lido stETH contract
constructor(IStETH _stETH) {
constructor(ILidoLocator _locator, IStETH _stETH) {
STETH = _stETH;

LIDO_LOCATOR = _locator;
_disableInitializers();
}

Expand Down Expand Up @@ -140,9 +143,11 @@ abstract contract VaultHub is PausableUntilWithRoles {
) external onlyRole(VAULT_MASTER_ROLE) {
if (_vault == address(0)) revert ZeroArgument("_vault");
if (_reserveRatioBP == 0) revert ZeroArgument("_reserveRatioBP");
if (_reserveRatioBP > TOTAL_BASIS_POINTS) revert ReserveRatioTooHigh(_vault, _reserveRatioBP, TOTAL_BASIS_POINTS);
if (_reserveRatioBP > TOTAL_BASIS_POINTS)
revert ReserveRatioTooHigh(_vault, _reserveRatioBP, TOTAL_BASIS_POINTS);
if (_reserveRatioThresholdBP == 0) revert ZeroArgument("_reserveRatioThresholdBP");
if (_reserveRatioThresholdBP > _reserveRatioBP) revert ReserveRatioTooHigh(_vault, _reserveRatioThresholdBP, _reserveRatioBP);
if (_reserveRatioThresholdBP > _reserveRatioBP)
revert ReserveRatioTooHigh(_vault, _reserveRatioThresholdBP, _reserveRatioBP);
if (_treasuryFeeBP > TOTAL_BASIS_POINTS) revert TreasuryFeeTooHigh(_vault, _treasuryFeeBP, TOTAL_BASIS_POINTS);
if (vaultsCount() == MAX_VAULTS_COUNT) revert TooManyVaults();
_checkShareLimitUpperBound(_vault, _shareLimit);
Expand All @@ -153,6 +158,9 @@ abstract contract VaultHub is PausableUntilWithRoles {
bytes32 vaultProxyCodehash = address(_vault).codehash;
if (!$.vaultProxyCodehash[vaultProxyCodehash]) revert VaultProxyNotAllowed(_vault);

if (IStakingVault(_vault).depositGuardian() != LIDO_LOCATOR.predepositGuarantee())
revert VaultDepositGuardianNotAllowed(IStakingVault(_vault).depositGuardian());

VaultSocket memory vr = VaultSocket(
_vault,
0, // sharesMinted
Expand Down Expand Up @@ -308,8 +316,10 @@ abstract contract VaultHub is PausableUntilWithRoles {
// reserveRatio = BPS_BASE - maxMintableRatio
// X = (mintedStETH * BPS_BASE - vault.valuation() * maxMintableRatio) / reserveRatio

uint256 amountToRebalance = (mintedStETH * TOTAL_BASIS_POINTS -
IStakingVault(_vault).valuation() * maxMintableRatio) / reserveRatioBP;
uint256 amountToRebalance = (mintedStETH *
TOTAL_BASIS_POINTS -
IStakingVault(_vault).valuation() *
maxMintableRatio) / reserveRatioBP;

// TODO: add some gas compensation here
IStakingVault(_vault).rebalance(amountToRebalance);
Expand Down Expand Up @@ -356,7 +366,11 @@ abstract contract VaultHub is PausableUntilWithRoles {
uint256 _preTotalShares,
uint256 _preTotalPooledEther,
uint256 _sharesToMintAsFees
) internal view returns (uint256[] memory lockedEther, uint256[] memory treasuryFeeShares, uint256 totalTreasuryFeeShares) {
)
internal
view
returns (uint256[] memory lockedEther, uint256[] memory treasuryFeeShares, uint256 totalTreasuryFeeShares)
{
/// HERE WILL BE ACCOUNTING DRAGON

// \||/
Expand Down Expand Up @@ -425,7 +439,8 @@ abstract contract VaultHub is PausableUntilWithRoles {

// TODO: optimize potential rewards calculation
uint256 potentialRewards = ((chargeableValue * (_postTotalPooledEther * _preTotalShares)) /
(_postTotalSharesNoFees * _preTotalPooledEther) - chargeableValue);
(_postTotalSharesNoFees * _preTotalPooledEther) -
chargeableValue);
uint256 treasuryFee = (potentialRewards * _socket.treasuryFeeBP) / TOTAL_BASIS_POINTS;

treasuryFeeShares = (treasuryFee * _preTotalShares) / _preTotalPooledEther;
Expand Down Expand Up @@ -480,7 +495,11 @@ abstract contract VaultHub is PausableUntilWithRoles {

/// @dev returns total number of stETH shares that is possible to mint on the provided vault with provided reserveRatio
/// it does not count shares that is already minted, but does count shareLimit on the vault
function _maxMintableShares(address _vault, uint256 _reserveRatio, uint256 _shareLimit) internal view returns (uint256) {
function _maxMintableShares(
address _vault,
uint256 _reserveRatio,
uint256 _shareLimit
) internal view returns (uint256) {
uint256 maxStETHMinted = (IStakingVault(_vault).valuation() * (TOTAL_BASIS_POINTS - _reserveRatio)) /
TOTAL_BASIS_POINTS;

Expand Down Expand Up @@ -529,4 +548,5 @@ abstract contract VaultHub is PausableUntilWithRoles {
error AlreadyExists(bytes32 codehash);
error NoMintedSharesShouldBeLeft(address vault, uint256 sharesMinted);
error VaultProxyNotAllowed(address beacon);
error VaultDepositGuardianNotAllowed(address depositGuardian);
}
2 changes: 2 additions & 0 deletions contracts/0.8.25/vaults/interfaces/IStakingVault.sol
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ interface IStakingVault {

function nodeOperator() external view returns (address);

function depositGuardian() external view returns (address);

function locked() external view returns (uint256);

function valuation() external view returns (uint256);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ abstract contract CLProofVerifier {
}

// See `BEACON_ROOTS_ADDRESS` constant in the EIP-4788.
address public immutable BEACON_ROOTS = 0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02;
address public constant BEACON_ROOTS = 0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02;

// Index of parent node for (Pubkey,WC) in validator container
GIndex public immutable GI_PUBKEY_WC_PARENT = pack(1 << 2, 2);
Expand Down
Loading

0 comments on commit 6e3b647

Please sign in to comment.