Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 76 additions & 44 deletions contracts/0.8.25/vaults/dashboard/Dashboard.sol
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,24 @@ contract Dashboard is NodeOperatorFee {
bytes32 public constant FUND_ON_RECEIVE_FLAG_SLOT =
0x7408b7b034fda7051615c19182918ecb91d753231cffd86f81a45d996d63e038;

/**
* @notice The PDG policy modes.
* "STRICT": deposits require the full PDG process.
* "ALLOW_PROVE": allows the node operator to prove unknown validators to PDG.
* "ALLOW_DEPOSIT_AND_PROVE": allows the node operator to perform unguaranteed deposits
* (bypassing the predeposit requirement) and proving unknown validators.
*/
enum PDGPolicy {
STRICT,
ALLOW_PROVE,
ALLOW_DEPOSIT_AND_PROVE
}

/**
* @notice Current active PDG policy set by `DEFAULT_ADMIN_ROLE`.
*/
PDGPolicy public pdgPolicy = PDGPolicy.STRICT;

/**
* @notice Constructor sets the stETH, and WSTETH token addresses,
* and passes the address of the vault hub up the inheritance chain.
Expand Down Expand Up @@ -110,55 +128,13 @@ contract Dashboard is NodeOperatorFee {
return VAULT_HUB.vaultConnection(address(_stakingVault()));
}

/**
* @notice Returns the stETH share limit of the vault
*/
function shareLimit() external view returns (uint256) {
return vaultConnection().shareLimit;
}

/**
* @notice Returns the number of stETH shares minted
*/
function liabilityShares() public view returns (uint256) {
return VAULT_HUB.liabilityShares(address(_stakingVault()));
}

/**
* @notice Returns the reserve ratio of the vault in basis points
*/
function reserveRatioBP() public view returns (uint16) {
return vaultConnection().reserveRatioBP;
}

/**
* @notice Returns the rebalance threshold of the vault in basis points.
*/
function forcedRebalanceThresholdBP() external view returns (uint16) {
return vaultConnection().forcedRebalanceThresholdBP;
}

/**
* @notice Returns the infra fee basis points.
*/
function infraFeeBP() external view returns (uint16) {
return vaultConnection().infraFeeBP;
}

/**
* @notice Returns the liquidity fee basis points.
*/
function liquidityFeeBP() external view returns (uint16) {
return vaultConnection().liquidityFeeBP;
}

/**
* @notice Returns the reservation fee basis points.
*/
function reservationFeeBP() external view returns (uint16) {
return vaultConnection().reservationFeeBP;
}

/**
* @notice Returns the total value of the vault in ether.
*/
Expand Down Expand Up @@ -418,18 +394,33 @@ contract Dashboard is NodeOperatorFee {
_rebalanceVault(_getSharesByPooledEth(_ether));
}

/**
* @notice Changes the PDG policy
* @param _pdgPolicy new PDG policy
*/
function setPDGPolicy(PDGPolicy _pdgPolicy) external onlyRoleMemberOrAdmin(DEFAULT_ADMIN_ROLE) {
if (_pdgPolicy == pdgPolicy) revert PDGPolicyAlreadyActive();

pdgPolicy = _pdgPolicy;

emit PDGPolicyEnacted(_pdgPolicy);
}

/**
* @notice Withdraws ether from vault and deposits directly to provided validators bypassing the default PDG process,
* allowing validators to be proven post-factum via `proveUnknownValidatorsToPDG`
* clearing them for future deposits via `PDG.depositToBeaconChain`
* @param _deposits array of IStakingVault.Deposit structs containing deposit data
* @return totalAmount total amount of ether deposited to beacon chain
* @dev requires the caller to have the `UNGUARANTEED_BEACON_CHAIN_DEPOSIT_ROLE`
* @dev requires the PDG policy set to `ALLOW_DEPOSIT_AND_PROVE`
* @dev requires the caller to have the `NODE_OPERATOR_UNGUARANTEED_DEPOSIT_ROLE`
* @dev can be used as PDG shortcut if the node operator is trusted to not frontrun provided deposits
*/
function unguaranteedDepositToBeaconChain(
IStakingVault.Deposit[] calldata _deposits
) external returns (uint256 totalAmount) {
if (pdgPolicy != PDGPolicy.ALLOW_DEPOSIT_AND_PROVE) revert ForbiddenByPDGPolicy();

IStakingVault stakingVault_ = _stakingVault();
IDepositContract depositContract = stakingVault_.DEPOSIT_CONTRACT();

Expand Down Expand Up @@ -468,9 +459,12 @@ contract Dashboard is NodeOperatorFee {
/**
* @notice Proves validators with correct vault WC if they are unknown to PDG
* @param _witnesses array of IPredepositGuarantee.ValidatorWitness structs containing proof data for validators
* @dev requires the caller to have the `PDG_PROVE_VALIDATOR_ROLE`
* @dev requires the PDG policy set to `ALLOW_PROVE` or `ALLOW_DEPOSIT_AND_PROVE`
* @dev requires the caller to have the `NODE_OPERATOR_PROVE_UNKNOWN_VALIDATOR_ROLE`
*/
function proveUnknownValidatorsToPDG(IPredepositGuarantee.ValidatorWitness[] calldata _witnesses) external {
if (pdgPolicy == PDGPolicy.STRICT) revert ForbiddenByPDGPolicy();

_proveUnknownValidatorsToPDG(_witnesses);
}

Expand Down Expand Up @@ -674,6 +668,28 @@ contract Dashboard is NodeOperatorFee {
}
}

/**
* @dev Withdraws ether from vault to this contract for unguaranteed deposit to validators
* Requires the caller to have the `NODE_OPERATOR_UNGUARANTEED_DEPOSIT_ROLE`.
*/
function _withdrawForUnguaranteedDepositToBeaconChain(
uint256 _ether
) internal onlyRoleMemberOrAdmin(NODE_OPERATOR_UNGUARANTEED_DEPOSIT_ROLE) {
VAULT_HUB.withdraw(address(_stakingVault()), address(this), _ether);
}

/**
* @dev Proves validators unknown to PDG that have correct vault WC
* Requires the caller to have the `NODE_OPERATOR_PROVE_UNKNOWN_VALIDATOR_ROLE`.
*/
function _proveUnknownValidatorsToPDG(
IPredepositGuarantee.ValidatorWitness[] calldata _witnesses
) internal onlyRoleMemberOrAdmin(NODE_OPERATOR_PROVE_UNKNOWN_VALIDATOR_ROLE) {
for (uint256 i = 0; i < _witnesses.length; i++) {
VAULT_HUB.proveUnknownValidatorToPDG(address(_stakingVault()), _witnesses[i]);
}
}

// ==================== Events ====================

/**
Expand All @@ -684,6 +700,11 @@ contract Dashboard is NodeOperatorFee {
*/
event UnguaranteedDeposits(address indexed stakingVault, uint256 deposits, uint256 totalAmount);

/**
* @notice Emitted when the PDG policy is updated.
*/
event PDGPolicyEnacted(PDGPolicy pdgPolicy);

// ==================== Errors ====================

/**
Expand Down Expand Up @@ -712,4 +733,15 @@ contract Dashboard is NodeOperatorFee {
* @notice Error when attempting to abandon the Dashboard contract itself.
*/
error DashboardNotAllowed();

/**
* @notice Error when attempting to set the same PDG policy that is already active.
*/
error PDGPolicyAlreadyActive();

/**
* @notice Error when attempting to perform an operation that is not allowed
* by the current active PDG policy.
*/
error ForbiddenByPDGPolicy();
}
30 changes: 23 additions & 7 deletions contracts/0.8.25/vaults/dashboard/NodeOperatorFee.sol
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,24 @@ contract NodeOperatorFee is Permissions {
*/
bytes32 public constant NODE_OPERATOR_FEE_EXEMPT_ROLE = keccak256("vaults.NodeOperatorFee.FeeExemptRole");

/**
* @notice Node operator's sub-role for unguaranteed deposit
* Managed by `NODE_OPERATOR_MANAGER_ROLE`.
*
* @dev 0x5c17b14b08ace6dda14c9642528ae92de2a73d59eacb65c71f39f309a5611063
*/
bytes32 public constant NODE_OPERATOR_UNGUARANTEED_DEPOSIT_ROLE =
keccak256("vaults.NodeOperatorFee.UnguaranteedDepositRole");

/**
* @notice Node operator's sub-role for proving unknown validators.
* Managed by `NODE_OPERATOR_MANAGER_ROLE`.
*
* @dev 0x7b564705f4e61596c4a9469b6884980f89e475befabdb849d69719f0791628be
*/
bytes32 public constant NODE_OPERATOR_PROVE_UNKNOWN_VALIDATOR_ROLE =
keccak256("vaults.NodeOperatorFee.ProveUnknownValidatorsRole");

// ==================== Packed Storage Slot 1 ====================
/**
* @notice Address that receives node operator fee disbursements.
Expand Down Expand Up @@ -115,6 +133,8 @@ contract NodeOperatorFee is Permissions {
_grantRole(NODE_OPERATOR_MANAGER_ROLE, _nodeOperatorManager);
_setRoleAdmin(NODE_OPERATOR_MANAGER_ROLE, NODE_OPERATOR_MANAGER_ROLE);
_setRoleAdmin(NODE_OPERATOR_FEE_EXEMPT_ROLE, NODE_OPERATOR_MANAGER_ROLE);
_setRoleAdmin(NODE_OPERATOR_UNGUARANTEED_DEPOSIT_ROLE, NODE_OPERATOR_MANAGER_ROLE);
_setRoleAdmin(NODE_OPERATOR_PROVE_UNKNOWN_VALIDATOR_ROLE, NODE_OPERATOR_MANAGER_ROLE);
}

/**
Expand Down Expand Up @@ -156,7 +176,7 @@ contract NodeOperatorFee is Permissions {
* @param _isApproved True to approve, False to forbid
*/
function setApprovedToConnect(bool _isApproved) external onlyRoleMemberOrAdmin(NODE_OPERATOR_MANAGER_ROLE) {
_setApprovedToConnect(_isApproved);
_setApprovedToConnect(_isApproved);
}

/**
Expand Down Expand Up @@ -307,7 +327,7 @@ contract NodeOperatorFee is Permissions {
int128 unsettledGrowth = growth - settledGrowth;

if (unsettledGrowth > 0) {
fee = uint256(uint128(unsettledGrowth)) * uint256(feeRate) / TOTAL_BASIS_POINTS;
fee = (uint256(uint128(unsettledGrowth)) * uint256(feeRate)) / TOTAL_BASIS_POINTS;
}
}

Expand Down Expand Up @@ -352,11 +372,7 @@ contract NodeOperatorFee is Permissions {
* @param oldFeeRecipient the old node operator fee recipient
* @param newFeeRecipient the new node operator fee recipient
*/
event FeeRecipientSet(
address indexed sender,
address oldFeeRecipient,
address newFeeRecipient
);
event FeeRecipientSet(address indexed sender, address oldFeeRecipient, address newFeeRecipient);

/**
* @dev Emitted when the settled growth is set.
Expand Down
34 changes: 0 additions & 34 deletions contracts/0.8.25/vaults/dashboard/Permissions.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import {AccessControlConfirmable} from "contracts/0.8.25/utils/AccessControlConf
import {ILidoLocator} from "contracts/common/interfaces/ILidoLocator.sol";

import {IStakingVault} from "../interfaces/IStakingVault.sol";
import {IPredepositGuarantee} from "../interfaces/IPredepositGuarantee.sol";
import {OperatorGrid} from "../OperatorGrid.sol";
import {VaultHub} from "../VaultHub.sol";

Expand Down Expand Up @@ -88,19 +87,6 @@ abstract contract Permissions is AccessControlConfirmable {
/// @dev 0x9586321ac05f110e4b4a0a42aba899709345af0ca78910e8832ddfd71fed2bf4
bytes32 public constant VOLUNTARY_DISCONNECT_ROLE = keccak256("vaults.Permissions.VoluntaryDisconnect");

/**
* @notice Permission for proving valid vault validators unknown to the PDG
*/
/// @dev 0xb850402129bccae797798069a8cf3147a0cb7c3193f70558a75f7df0b8651c30
bytes32 public constant PDG_PROVE_VALIDATOR_ROLE = keccak256("vaults.Permissions.PDGProveValidator");

/**
* @notice Permission for unguaranteed deposit to trusted validators
*/
/// @dev 0xea6487df651bb740150364c496e1c7403dd62063c96e44906cc98c6a919a9d88
bytes32 public constant UNGUARANTEED_BEACON_CHAIN_DEPOSIT_ROLE =
keccak256("vaults.Permissions.UnguaranteedBeaconChainDeposit");

/**
* @dev Permission for vault configuration operations on the OperatorGrid (tier changes, tier sync, share limit updates).
*/
Expand Down Expand Up @@ -323,26 +309,6 @@ abstract contract Permissions is AccessControlConfirmable {
_stakingVault().acceptOwnership();
}

/**
* @dev Proves validators unknown to PDG that have correct vault WC
*/
function _proveUnknownValidatorsToPDG(
IPredepositGuarantee.ValidatorWitness[] calldata _witnesses
) internal onlyRoleMemberOrAdmin(PDG_PROVE_VALIDATOR_ROLE) {
for (uint256 i = 0; i < _witnesses.length; i++) {
VAULT_HUB.proveUnknownValidatorToPDG(address(_stakingVault()), _witnesses[i]);
}
}

/**
* @dev Withdraws ether from vault to this contract for unguaranteed deposit to validators
*/
function _withdrawForUnguaranteedDepositToBeaconChain(
uint256 _ether
) internal onlyRoleMemberOrAdmin(UNGUARANTEED_BEACON_CHAIN_DEPOSIT_ROLE) {
VAULT_HUB.withdraw(address(_stakingVault()), address(this), _ether);
}

/**
* @dev Checks the confirming roles and sets the owner on the StakingVault.
* @param _newOwner The address to set the owner to.
Expand Down
6 changes: 6 additions & 0 deletions lib/pdg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,3 +226,9 @@ export const prepareLocalMerkleTree = async (
buildProof,
};
};

export enum PDGPolicy {
STRICT,
ALLOW_PROVE,
ALLOW_DEPOSIT_AND_PROVE,
}
6 changes: 3 additions & 3 deletions lib/protocol/helpers/vaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ export const vaultRoleKeys = [
"validatorExitRequester",
"validatorWithdrawalTriggerer",
"disconnecter",
"unguaranteedDepositor",
"unknownValidatorProver",
"unguaranteedBeaconChainDepositor",
"tierChanger",
"nodeOperatorFeeExemptor",
"assetCollector",
Expand Down Expand Up @@ -143,8 +143,8 @@ export const getRoleMethods = (dashboard: Dashboard): VaultRoleMethods => {
validatorExitRequester: dashboard.REQUEST_VALIDATOR_EXIT_ROLE(),
validatorWithdrawalTriggerer: dashboard.TRIGGER_VALIDATOR_WITHDRAWAL_ROLE(),
disconnecter: dashboard.VOLUNTARY_DISCONNECT_ROLE(),
unknownValidatorProver: dashboard.PDG_PROVE_VALIDATOR_ROLE(),
unguaranteedBeaconChainDepositor: dashboard.UNGUARANTEED_BEACON_CHAIN_DEPOSIT_ROLE(),
unguaranteedDepositor: dashboard.NODE_OPERATOR_UNGUARANTEED_DEPOSIT_ROLE(),
unknownValidatorProver: dashboard.NODE_OPERATOR_PROVE_UNKNOWN_VALIDATOR_ROLE(),
tierChanger: dashboard.VAULT_CONFIGURATION_ROLE(),
nodeOperatorFeeExemptor: dashboard.NODE_OPERATOR_FEE_EXEMPT_ROLE(),
assetCollector: dashboard.COLLECT_VAULT_ERC20_ROLE(),
Expand Down
Loading