Skip to content

Commit 76822b9

Browse files
authored
Merge pull request #1458 from lidofinance/flag-ung-deps
fix: prevent owner from lowering NO fee via unguaranteed deposit
2 parents dec0f1f + 308050d commit 76822b9

File tree

12 files changed

+261
-246
lines changed

12 files changed

+261
-246
lines changed

contracts/0.8.25/vaults/dashboard/Dashboard.sol

Lines changed: 76 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,24 @@ contract Dashboard is NodeOperatorFee {
5050
bytes32 public constant FUND_ON_RECEIVE_FLAG_SLOT =
5151
0x7408b7b034fda7051615c19182918ecb91d753231cffd86f81a45d996d63e038;
5252

53+
/**
54+
* @notice The PDG policy modes.
55+
* "STRICT": deposits require the full PDG process.
56+
* "ALLOW_PROVE": allows the node operator to prove unknown validators to PDG.
57+
* "ALLOW_DEPOSIT_AND_PROVE": allows the node operator to perform unguaranteed deposits
58+
* (bypassing the predeposit requirement) and proving unknown validators.
59+
*/
60+
enum PDGPolicy {
61+
STRICT,
62+
ALLOW_PROVE,
63+
ALLOW_DEPOSIT_AND_PROVE
64+
}
65+
66+
/**
67+
* @notice Current active PDG policy set by `DEFAULT_ADMIN_ROLE`.
68+
*/
69+
PDGPolicy public pdgPolicy = PDGPolicy.STRICT;
70+
5371
/**
5472
* @notice Constructor sets the stETH, and WSTETH token addresses,
5573
* and passes the address of the vault hub up the inheritance chain.
@@ -110,55 +128,13 @@ contract Dashboard is NodeOperatorFee {
110128
return VAULT_HUB.vaultConnection(address(_stakingVault()));
111129
}
112130

113-
/**
114-
* @notice Returns the stETH share limit of the vault
115-
*/
116-
function shareLimit() external view returns (uint256) {
117-
return vaultConnection().shareLimit;
118-
}
119-
120131
/**
121132
* @notice Returns the number of stETH shares minted
122133
*/
123134
function liabilityShares() public view returns (uint256) {
124135
return VAULT_HUB.liabilityShares(address(_stakingVault()));
125136
}
126137

127-
/**
128-
* @notice Returns the reserve ratio of the vault in basis points
129-
*/
130-
function reserveRatioBP() public view returns (uint16) {
131-
return vaultConnection().reserveRatioBP;
132-
}
133-
134-
/**
135-
* @notice Returns the rebalance threshold of the vault in basis points.
136-
*/
137-
function forcedRebalanceThresholdBP() external view returns (uint16) {
138-
return vaultConnection().forcedRebalanceThresholdBP;
139-
}
140-
141-
/**
142-
* @notice Returns the infra fee basis points.
143-
*/
144-
function infraFeeBP() external view returns (uint16) {
145-
return vaultConnection().infraFeeBP;
146-
}
147-
148-
/**
149-
* @notice Returns the liquidity fee basis points.
150-
*/
151-
function liquidityFeeBP() external view returns (uint16) {
152-
return vaultConnection().liquidityFeeBP;
153-
}
154-
155-
/**
156-
* @notice Returns the reservation fee basis points.
157-
*/
158-
function reservationFeeBP() external view returns (uint16) {
159-
return vaultConnection().reservationFeeBP;
160-
}
161-
162138
/**
163139
* @notice Returns the total value of the vault in ether.
164140
*/
@@ -418,18 +394,33 @@ contract Dashboard is NodeOperatorFee {
418394
_rebalanceVault(_getSharesByPooledEth(_ether));
419395
}
420396

397+
/**
398+
* @notice Changes the PDG policy
399+
* @param _pdgPolicy new PDG policy
400+
*/
401+
function setPDGPolicy(PDGPolicy _pdgPolicy) external onlyRoleMemberOrAdmin(DEFAULT_ADMIN_ROLE) {
402+
if (_pdgPolicy == pdgPolicy) revert PDGPolicyAlreadyActive();
403+
404+
pdgPolicy = _pdgPolicy;
405+
406+
emit PDGPolicyEnacted(_pdgPolicy);
407+
}
408+
421409
/**
422410
* @notice Withdraws ether from vault and deposits directly to provided validators bypassing the default PDG process,
423411
* allowing validators to be proven post-factum via `proveUnknownValidatorsToPDG`
424412
* clearing them for future deposits via `PDG.depositToBeaconChain`
425413
* @param _deposits array of IStakingVault.Deposit structs containing deposit data
426414
* @return totalAmount total amount of ether deposited to beacon chain
427-
* @dev requires the caller to have the `UNGUARANTEED_BEACON_CHAIN_DEPOSIT_ROLE`
415+
* @dev requires the PDG policy set to `ALLOW_DEPOSIT_AND_PROVE`
416+
* @dev requires the caller to have the `NODE_OPERATOR_UNGUARANTEED_DEPOSIT_ROLE`
428417
* @dev can be used as PDG shortcut if the node operator is trusted to not frontrun provided deposits
429418
*/
430419
function unguaranteedDepositToBeaconChain(
431420
IStakingVault.Deposit[] calldata _deposits
432421
) external returns (uint256 totalAmount) {
422+
if (pdgPolicy != PDGPolicy.ALLOW_DEPOSIT_AND_PROVE) revert ForbiddenByPDGPolicy();
423+
433424
IStakingVault stakingVault_ = _stakingVault();
434425
IDepositContract depositContract = stakingVault_.DEPOSIT_CONTRACT();
435426

@@ -468,9 +459,12 @@ contract Dashboard is NodeOperatorFee {
468459
/**
469460
* @notice Proves validators with correct vault WC if they are unknown to PDG
470461
* @param _witnesses array of IPredepositGuarantee.ValidatorWitness structs containing proof data for validators
471-
* @dev requires the caller to have the `PDG_PROVE_VALIDATOR_ROLE`
462+
* @dev requires the PDG policy set to `ALLOW_PROVE` or `ALLOW_DEPOSIT_AND_PROVE`
463+
* @dev requires the caller to have the `NODE_OPERATOR_PROVE_UNKNOWN_VALIDATOR_ROLE`
472464
*/
473465
function proveUnknownValidatorsToPDG(IPredepositGuarantee.ValidatorWitness[] calldata _witnesses) external {
466+
if (pdgPolicy == PDGPolicy.STRICT) revert ForbiddenByPDGPolicy();
467+
474468
_proveUnknownValidatorsToPDG(_witnesses);
475469
}
476470

@@ -674,6 +668,28 @@ contract Dashboard is NodeOperatorFee {
674668
}
675669
}
676670

671+
/**
672+
* @dev Withdraws ether from vault to this contract for unguaranteed deposit to validators
673+
* Requires the caller to have the `NODE_OPERATOR_UNGUARANTEED_DEPOSIT_ROLE`.
674+
*/
675+
function _withdrawForUnguaranteedDepositToBeaconChain(
676+
uint256 _ether
677+
) internal onlyRoleMemberOrAdmin(NODE_OPERATOR_UNGUARANTEED_DEPOSIT_ROLE) {
678+
VAULT_HUB.withdraw(address(_stakingVault()), address(this), _ether);
679+
}
680+
681+
/**
682+
* @dev Proves validators unknown to PDG that have correct vault WC
683+
* Requires the caller to have the `NODE_OPERATOR_PROVE_UNKNOWN_VALIDATOR_ROLE`.
684+
*/
685+
function _proveUnknownValidatorsToPDG(
686+
IPredepositGuarantee.ValidatorWitness[] calldata _witnesses
687+
) internal onlyRoleMemberOrAdmin(NODE_OPERATOR_PROVE_UNKNOWN_VALIDATOR_ROLE) {
688+
for (uint256 i = 0; i < _witnesses.length; i++) {
689+
VAULT_HUB.proveUnknownValidatorToPDG(address(_stakingVault()), _witnesses[i]);
690+
}
691+
}
692+
677693
// ==================== Events ====================
678694

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

703+
/**
704+
* @notice Emitted when the PDG policy is updated.
705+
*/
706+
event PDGPolicyEnacted(PDGPolicy pdgPolicy);
707+
687708
// ==================== Errors ====================
688709

689710
/**
@@ -712,4 +733,15 @@ contract Dashboard is NodeOperatorFee {
712733
* @notice Error when attempting to abandon the Dashboard contract itself.
713734
*/
714735
error DashboardNotAllowed();
736+
737+
/**
738+
* @notice Error when attempting to set the same PDG policy that is already active.
739+
*/
740+
error PDGPolicyAlreadyActive();
741+
742+
/**
743+
* @notice Error when attempting to perform an operation that is not allowed
744+
* by the current active PDG policy.
745+
*/
746+
error ForbiddenByPDGPolicy();
715747
}

contracts/0.8.25/vaults/dashboard/NodeOperatorFee.sol

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,24 @@ contract NodeOperatorFee is Permissions {
4040
*/
4141
bytes32 public constant NODE_OPERATOR_FEE_EXEMPT_ROLE = keccak256("vaults.NodeOperatorFee.FeeExemptRole");
4242

43+
/**
44+
* @notice Node operator's sub-role for unguaranteed deposit
45+
* Managed by `NODE_OPERATOR_MANAGER_ROLE`.
46+
*
47+
* @dev 0x5c17b14b08ace6dda14c9642528ae92de2a73d59eacb65c71f39f309a5611063
48+
*/
49+
bytes32 public constant NODE_OPERATOR_UNGUARANTEED_DEPOSIT_ROLE =
50+
keccak256("vaults.NodeOperatorFee.UnguaranteedDepositRole");
51+
52+
/**
53+
* @notice Node operator's sub-role for proving unknown validators.
54+
* Managed by `NODE_OPERATOR_MANAGER_ROLE`.
55+
*
56+
* @dev 0x7b564705f4e61596c4a9469b6884980f89e475befabdb849d69719f0791628be
57+
*/
58+
bytes32 public constant NODE_OPERATOR_PROVE_UNKNOWN_VALIDATOR_ROLE =
59+
keccak256("vaults.NodeOperatorFee.ProveUnknownValidatorsRole");
60+
4361
// ==================== Packed Storage Slot 1 ====================
4462
/**
4563
* @notice Address that receives node operator fee disbursements.
@@ -115,6 +133,8 @@ contract NodeOperatorFee is Permissions {
115133
_grantRole(NODE_OPERATOR_MANAGER_ROLE, _nodeOperatorManager);
116134
_setRoleAdmin(NODE_OPERATOR_MANAGER_ROLE, NODE_OPERATOR_MANAGER_ROLE);
117135
_setRoleAdmin(NODE_OPERATOR_FEE_EXEMPT_ROLE, NODE_OPERATOR_MANAGER_ROLE);
136+
_setRoleAdmin(NODE_OPERATOR_UNGUARANTEED_DEPOSIT_ROLE, NODE_OPERATOR_MANAGER_ROLE);
137+
_setRoleAdmin(NODE_OPERATOR_PROVE_UNKNOWN_VALIDATOR_ROLE, NODE_OPERATOR_MANAGER_ROLE);
118138
}
119139

120140
/**
@@ -156,7 +176,7 @@ contract NodeOperatorFee is Permissions {
156176
* @param _isApproved True to approve, False to forbid
157177
*/
158178
function setApprovedToConnect(bool _isApproved) external onlyRoleMemberOrAdmin(NODE_OPERATOR_MANAGER_ROLE) {
159-
_setApprovedToConnect(_isApproved);
179+
_setApprovedToConnect(_isApproved);
160180
}
161181

162182
/**
@@ -307,7 +327,7 @@ contract NodeOperatorFee is Permissions {
307327
int128 unsettledGrowth = growth - settledGrowth;
308328

309329
if (unsettledGrowth > 0) {
310-
fee = uint256(uint128(unsettledGrowth)) * uint256(feeRate) / TOTAL_BASIS_POINTS;
330+
fee = (uint256(uint128(unsettledGrowth)) * uint256(feeRate)) / TOTAL_BASIS_POINTS;
311331
}
312332
}
313333

@@ -352,11 +372,7 @@ contract NodeOperatorFee is Permissions {
352372
* @param oldFeeRecipient the old node operator fee recipient
353373
* @param newFeeRecipient the new node operator fee recipient
354374
*/
355-
event FeeRecipientSet(
356-
address indexed sender,
357-
address oldFeeRecipient,
358-
address newFeeRecipient
359-
);
375+
event FeeRecipientSet(address indexed sender, address oldFeeRecipient, address newFeeRecipient);
360376

361377
/**
362378
* @dev Emitted when the settled growth is set.

contracts/0.8.25/vaults/dashboard/Permissions.sol

Lines changed: 0 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import {AccessControlConfirmable} from "contracts/0.8.25/utils/AccessControlConf
99
import {ILidoLocator} from "contracts/common/interfaces/ILidoLocator.sol";
1010

1111
import {IStakingVault} from "../interfaces/IStakingVault.sol";
12-
import {IPredepositGuarantee} from "../interfaces/IPredepositGuarantee.sol";
1312
import {OperatorGrid} from "../OperatorGrid.sol";
1413
import {VaultHub} from "../VaultHub.sol";
1514

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

91-
/**
92-
* @notice Permission for proving valid vault validators unknown to the PDG
93-
*/
94-
/// @dev 0xb850402129bccae797798069a8cf3147a0cb7c3193f70558a75f7df0b8651c30
95-
bytes32 public constant PDG_PROVE_VALIDATOR_ROLE = keccak256("vaults.Permissions.PDGProveValidator");
96-
97-
/**
98-
* @notice Permission for unguaranteed deposit to trusted validators
99-
*/
100-
/// @dev 0xea6487df651bb740150364c496e1c7403dd62063c96e44906cc98c6a919a9d88
101-
bytes32 public constant UNGUARANTEED_BEACON_CHAIN_DEPOSIT_ROLE =
102-
keccak256("vaults.Permissions.UnguaranteedBeaconChainDeposit");
103-
10490
/**
10591
* @dev Permission for vault configuration operations on the OperatorGrid (tier changes, tier sync, share limit updates).
10692
*/
@@ -323,26 +309,6 @@ abstract contract Permissions is AccessControlConfirmable {
323309
_stakingVault().acceptOwnership();
324310
}
325311

326-
/**
327-
* @dev Proves validators unknown to PDG that have correct vault WC
328-
*/
329-
function _proveUnknownValidatorsToPDG(
330-
IPredepositGuarantee.ValidatorWitness[] calldata _witnesses
331-
) internal onlyRoleMemberOrAdmin(PDG_PROVE_VALIDATOR_ROLE) {
332-
for (uint256 i = 0; i < _witnesses.length; i++) {
333-
VAULT_HUB.proveUnknownValidatorToPDG(address(_stakingVault()), _witnesses[i]);
334-
}
335-
}
336-
337-
/**
338-
* @dev Withdraws ether from vault to this contract for unguaranteed deposit to validators
339-
*/
340-
function _withdrawForUnguaranteedDepositToBeaconChain(
341-
uint256 _ether
342-
) internal onlyRoleMemberOrAdmin(UNGUARANTEED_BEACON_CHAIN_DEPOSIT_ROLE) {
343-
VAULT_HUB.withdraw(address(_stakingVault()), address(this), _ether);
344-
}
345-
346312
/**
347313
* @dev Checks the confirming roles and sets the owner on the StakingVault.
348314
* @param _newOwner The address to set the owner to.

lib/pdg.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,3 +226,9 @@ export const prepareLocalMerkleTree = async (
226226
buildProof,
227227
};
228228
};
229+
230+
export enum PDGPolicy {
231+
STRICT,
232+
ALLOW_PROVE,
233+
ALLOW_DEPOSIT_AND_PROVE,
234+
}

lib/protocol/helpers/vaults.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,8 @@ export const vaultRoleKeys = [
5050
"validatorExitRequester",
5151
"validatorWithdrawalTriggerer",
5252
"disconnecter",
53+
"unguaranteedDepositor",
5354
"unknownValidatorProver",
54-
"unguaranteedBeaconChainDepositor",
5555
"tierChanger",
5656
"nodeOperatorFeeExemptor",
5757
"assetCollector",
@@ -143,8 +143,8 @@ export const getRoleMethods = (dashboard: Dashboard): VaultRoleMethods => {
143143
validatorExitRequester: dashboard.REQUEST_VALIDATOR_EXIT_ROLE(),
144144
validatorWithdrawalTriggerer: dashboard.TRIGGER_VALIDATOR_WITHDRAWAL_ROLE(),
145145
disconnecter: dashboard.VOLUNTARY_DISCONNECT_ROLE(),
146-
unknownValidatorProver: dashboard.PDG_PROVE_VALIDATOR_ROLE(),
147-
unguaranteedBeaconChainDepositor: dashboard.UNGUARANTEED_BEACON_CHAIN_DEPOSIT_ROLE(),
146+
unguaranteedDepositor: dashboard.NODE_OPERATOR_UNGUARANTEED_DEPOSIT_ROLE(),
147+
unknownValidatorProver: dashboard.NODE_OPERATOR_PROVE_UNKNOWN_VALIDATOR_ROLE(),
148148
tierChanger: dashboard.VAULT_CONFIGURATION_ROLE(),
149149
nodeOperatorFeeExemptor: dashboard.NODE_OPERATOR_FEE_EXEMPT_ROLE(),
150150
assetCollector: dashboard.COLLECT_VAULT_ERC20_ROLE(),

0 commit comments

Comments
 (0)