-
Notifications
You must be signed in to change notification settings - Fork 4
/
CSAccounting.sol
662 lines (604 loc) · 26.9 KB
/
CSAccounting.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
// SPDX-FileCopyrightText: 2024 Lido <[email protected]>
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.24;
import { PausableUntil } from "./lib/utils/PausableUntil.sol";
import { AccessControlEnumerableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/extensions/AccessControlEnumerableUpgradeable.sol";
import { CSBondCore } from "./abstract/CSBondCore.sol";
import { CSBondCurve } from "./abstract/CSBondCurve.sol";
import { CSBondLock } from "./abstract/CSBondLock.sol";
import { ICSModule } from "./interfaces/ICSModule.sol";
import { ICSAccounting } from "./interfaces/ICSAccounting.sol";
import { ICSFeeDistributor } from "./interfaces/ICSFeeDistributor.sol";
import { AssetRecoverer } from "./abstract/AssetRecoverer.sol";
import { AssetRecovererLib } from "./lib/AssetRecovererLib.sol";
/// @author vgorkavenko
/// @notice This contract stores the Node Operators' bonds in the form of stETH shares,
/// so it should be considered in the recovery process
contract CSAccounting is
ICSAccounting,
CSBondCore,
CSBondCurve,
CSBondLock,
PausableUntil,
AccessControlEnumerableUpgradeable,
AssetRecoverer
{
bytes32 public constant PAUSE_ROLE = keccak256("PAUSE_ROLE");
bytes32 public constant RESUME_ROLE = keccak256("RESUME_ROLE");
bytes32 public constant ACCOUNTING_MANAGER_ROLE =
keccak256("ACCOUNTING_MANAGER_ROLE");
bytes32 public constant MANAGE_BOND_CURVES_ROLE =
keccak256("MANAGE_BOND_CURVES_ROLE");
bytes32 public constant SET_BOND_CURVE_ROLE =
keccak256("SET_BOND_CURVE_ROLE");
bytes32 public constant RESET_BOND_CURVE_ROLE =
keccak256("RESET_BOND_CURVE_ROLE");
bytes32 public constant RECOVERER_ROLE = keccak256("RECOVERER_ROLE");
ICSModule public immutable CSM;
ICSFeeDistributor public feeDistributor;
address public chargePenaltyRecipient;
event BondLockCompensated(uint256 indexed nodeOperatorId, uint256 amount);
event ChargePenaltyRecipientSet(address chargePenaltyRecipient);
error SenderIsNotCSM();
error ZeroModuleAddress();
error ZeroAdminAddress();
error ZeroFeeDistributorAddress();
error ZeroChargePenaltyRecipientAddress();
error NodeOperatorDoesNotExist();
error ElRewardsVaultReceiveFailed();
modifier onlyCSM() {
if (msg.sender != address(CSM)) revert SenderIsNotCSM();
_;
}
/// @param lidoLocator Lido locator contract address
/// @param communityStakingModule Community Staking Module contract address
/// @param maxCurveLength Max number of the points in the bond curves
/// @param minBondLockRetentionPeriod Min time in seconds for the bondLock retention period
/// @param maxBondLockRetentionPeriod Max time in seconds for the bondLock retention period
constructor(
address lidoLocator,
address communityStakingModule,
uint256 maxCurveLength,
uint256 minBondLockRetentionPeriod,
uint256 maxBondLockRetentionPeriod
)
CSBondCore(lidoLocator)
CSBondCurve(maxCurveLength)
CSBondLock(minBondLockRetentionPeriod, maxBondLockRetentionPeriod)
{
if (communityStakingModule == address(0)) {
revert ZeroModuleAddress();
}
CSM = ICSModule(communityStakingModule);
_disableInitializers();
}
/// @param bondCurve Initial bond curve
/// @param admin Admin role member address
/// @param _feeDistributor Fee Distributor contract address
/// @param bondLockRetentionPeriod Retention period for locked bond in seconds
/// @param _chargePenaltyRecipient Recipient of the charge penalty type
function initialize(
uint256[] calldata bondCurve,
address admin,
address _feeDistributor,
uint256 bondLockRetentionPeriod,
address _chargePenaltyRecipient
) external initializer {
__AccessControlEnumerable_init();
__CSBondCurve_init(bondCurve);
__CSBondLock_init(bondLockRetentionPeriod);
if (admin == address(0)) {
revert ZeroAdminAddress();
}
if (_feeDistributor == address(0)) {
revert ZeroFeeDistributorAddress();
}
_grantRole(DEFAULT_ADMIN_ROLE, admin);
_grantRole(SET_BOND_CURVE_ROLE, address(CSM));
_grantRole(RESET_BOND_CURVE_ROLE, address(CSM));
feeDistributor = ICSFeeDistributor(_feeDistributor);
_setChargePenaltyRecipient(_chargePenaltyRecipient);
LIDO.approve(address(WSTETH), type(uint256).max);
LIDO.approve(address(WITHDRAWAL_QUEUE), type(uint256).max);
LIDO.approve(LIDO_LOCATOR.burner(), type(uint256).max);
}
/// @notice Resume reward claims and deposits
function resume() external onlyRole(RESUME_ROLE) {
_resume();
}
/// @notice Pause reward claims and deposits for `duration` seconds
/// @dev Must be called together with `CSModule.pauseFor`
/// @dev Passing MAX_UINT_256 as `duration` pauses indefinitely
/// @param duration Duration of the pause in seconds
function pauseFor(uint256 duration) external onlyRole(PAUSE_ROLE) {
_pauseFor(duration);
}
/// @notice Set charge recipient address
/// @param _chargePenaltyRecipient Charge recipient address
function setChargePenaltyRecipient(
address _chargePenaltyRecipient
) external onlyRole(ACCOUNTING_MANAGER_ROLE) {
_setChargePenaltyRecipient(_chargePenaltyRecipient);
}
/// @notice Set bond lock retention period
/// @param retention Period in seconds to retain bond lock
function setLockedBondRetentionPeriod(
uint256 retention
) external onlyRole(ACCOUNTING_MANAGER_ROLE) {
CSBondLock._setBondLockRetentionPeriod(retention);
}
/// @notice Add a new bond curve
/// @param bondCurve Bond curve definition to add
/// @return id Id of the added curve
function addBondCurve(
uint256[] calldata bondCurve
) external onlyRole(MANAGE_BOND_CURVES_ROLE) returns (uint256 id) {
id = CSBondCurve._addBondCurve(bondCurve);
}
/// @notice Update existing bond curve
/// @dev If the curve is updated to a curve with higher values for any point,
/// Extensive checks should be performed to avoid inconsistency in the keys accounting
/// @param curveId Bond curve ID to update
/// @param bondCurve Bond curve definition
function updateBondCurve(
uint256 curveId,
uint256[] calldata bondCurve
) external onlyRole(MANAGE_BOND_CURVES_ROLE) {
CSBondCurve._updateBondCurve(curveId, bondCurve);
}
/// @notice Set the bond curve for the given Node Operator
/// @dev If called externally, the `normalizeQueue` method from CSModule.sol should be called after
/// to ensure key pointers consistency
/// @param nodeOperatorId ID of the Node Operator
/// @param curveId ID of the bond curve to set
function setBondCurve(
uint256 nodeOperatorId,
uint256 curveId
) external onlyRole(SET_BOND_CURVE_ROLE) {
_onlyExistingNodeOperator(nodeOperatorId);
CSBondCurve._setBondCurve(nodeOperatorId, curveId);
}
/// @notice Reset bond curve to the default one for the given Node Operator
/// @dev If called externally, the `normalizeQueue` method from CSModule.sol should be called after
/// to ensure key pointers consistency
/// @param nodeOperatorId ID of the Node Operator
function resetBondCurve(
uint256 nodeOperatorId
) external onlyRole(RESET_BOND_CURVE_ROLE) {
_onlyExistingNodeOperator(nodeOperatorId);
CSBondCurve._resetBondCurve(nodeOperatorId);
}
/// @notice Stake user's ETH with Lido and deposit stETH to the bond
/// @dev Called by CSM exclusively
/// @param from Address to stake ETH and deposit stETH from
/// @param nodeOperatorId ID of the Node Operator
function depositETH(
address from,
uint256 nodeOperatorId
) external payable whenResumed onlyCSM {
CSBondCore._depositETH(from, nodeOperatorId);
}
/// @notice Deposit user's stETH to the bond for the given Node Operator
/// @dev Called by CSM exclusively
/// @param from Address to deposit stETH from
/// @param nodeOperatorId ID of the Node Operator
/// @param stETHAmount Amount of stETH to deposit
/// @param permit stETH permit for the contract
function depositStETH(
address from,
uint256 nodeOperatorId,
uint256 stETHAmount,
PermitInput calldata permit
) external whenResumed onlyCSM {
// @dev for some reason foundry coverage consider this if as not fully covered. Check tests to see it is covered indeed
// preventing revert for already used permit or avoid permit usage in case of value == 0
if (
permit.value > 0 &&
LIDO.allowance(from, address(this)) < permit.value
) {
// solhint-disable-next-line func-named-parameters
LIDO.permit(
from,
address(this),
permit.value,
permit.deadline,
permit.v,
permit.r,
permit.s
);
}
CSBondCore._depositStETH(from, nodeOperatorId, stETHAmount);
}
/// @notice Unwrap the user's wstETH and deposit stETH to the bond for the given Node Operator
/// @dev Called by CSM exclusively
/// @param from Address to unwrap wstETH from
/// @param nodeOperatorId ID of the Node Operator
/// @param wstETHAmount Amount of wstETH to deposit
/// @param permit wstETH permit for the contract
function depositWstETH(
address from,
uint256 nodeOperatorId,
uint256 wstETHAmount,
PermitInput calldata permit
) external whenResumed onlyCSM {
// @dev for some reason foundry coverage consider this if as not fully covered. Check tests to see it is covered indeed
// preventing revert for already used permit or avoid permit usage in case of value == 0
if (
permit.value > 0 &&
WSTETH.allowance(from, address(this)) < permit.value
) {
// solhint-disable-next-line func-named-parameters
WSTETH.permit(
from,
address(this),
permit.value,
permit.deadline,
permit.v,
permit.r,
permit.s
);
}
CSBondCore._depositWstETH(from, nodeOperatorId, wstETHAmount);
}
/// @notice Claim full reward (fee + bond) in stETH for the given Node Operator with desirable value.
/// `rewardsProof` and `cumulativeFeeShares` might be empty in order to claim only excess bond
/// @dev Called by CSM exclusively
/// @param nodeOperatorId ID of the Node Operator
/// @param stETHAmount Amount of stETH to claim
/// @param rewardAddress Reward address of the node operator
/// @param cumulativeFeeShares Cumulative fee stETH shares for the Node Operator
/// @param rewardsProof Merkle proof of the rewards
/// @dev It's impossible to use single-leaf proof via this method, so this case should be treated carefully by
/// off-chain tooling, e.g. to make sure a tree has at least 2 leafs.
function claimRewardsStETH(
uint256 nodeOperatorId,
uint256 stETHAmount,
address rewardAddress,
uint256 cumulativeFeeShares,
bytes32[] calldata rewardsProof
) external whenResumed onlyCSM {
if (rewardsProof.length != 0) {
_pullFeeRewards(nodeOperatorId, cumulativeFeeShares, rewardsProof);
}
CSBondCore._claimStETH(nodeOperatorId, stETHAmount, rewardAddress);
}
/// @notice Claim full reward (fee + bond) in wstETH for the given Node Operator available for this moment.
/// `rewardsProof` and `cumulativeFeeShares` might be empty in order to claim only excess bond
/// @dev Called by CSM exclusively
/// @param nodeOperatorId ID of the Node Operator
/// @param wstETHAmount Amount of wstETH to claim
/// @param rewardAddress Reward address of the node operator
/// @param cumulativeFeeShares Cumulative fee stETH shares for the Node Operator
/// @param rewardsProof Merkle proof of the rewards
/// @dev It's impossible to use single-leaf proof via this method, so this case should be treated carefully by
/// off-chain tooling, e.g. to make sure a tree has at least 2 leafs.
function claimRewardsWstETH(
uint256 nodeOperatorId,
uint256 wstETHAmount,
address rewardAddress,
uint256 cumulativeFeeShares,
bytes32[] calldata rewardsProof
) external whenResumed onlyCSM {
if (rewardsProof.length != 0) {
_pullFeeRewards(nodeOperatorId, cumulativeFeeShares, rewardsProof);
}
CSBondCore._claimWstETH(nodeOperatorId, wstETHAmount, rewardAddress);
}
/// @notice Request full reward (fee + bond) in Withdrawal NFT (unstETH) for the given Node Operator available for this moment.
/// `rewardsProof` and `cumulativeFeeShares` might be empty in order to claim only excess bond
/// @dev Reverts if amount isn't between `MIN_STETH_WITHDRAWAL_AMOUNT` and `MAX_STETH_WITHDRAWAL_AMOUNT`
/// @dev Called by CSM exclusively
/// @param nodeOperatorId ID of the Node Operator
/// @param stEthAmount Amount of ETH to request
/// @param rewardAddress Reward address of the node operator
/// @param cumulativeFeeShares Cumulative fee stETH shares for the Node Operator
/// @param rewardsProof Merkle proof of the rewards
/// @dev It's impossible to use single-leaf proof via this method, so this case should be treated carefully by
/// off-chain tooling, e.g. to make sure a tree has at least 2 leafs.
function claimRewardsUnstETH(
uint256 nodeOperatorId,
uint256 stEthAmount,
address rewardAddress,
uint256 cumulativeFeeShares,
bytes32[] calldata rewardsProof
) external whenResumed onlyCSM {
if (rewardsProof.length != 0) {
_pullFeeRewards(nodeOperatorId, cumulativeFeeShares, rewardsProof);
}
CSBondCore._claimUnstETH(nodeOperatorId, stEthAmount, rewardAddress);
}
/// @notice Lock bond in ETH for the given Node Operator
/// @dev Called by CSM exclusively
/// @param nodeOperatorId ID of the Node Operator
/// @param amount Amount to lock in ETH (stETH)
function lockBondETH(
uint256 nodeOperatorId,
uint256 amount
) external onlyCSM {
CSBondLock._lock(nodeOperatorId, amount);
}
/// @notice Release locked bond in ETH for the given Node Operator
/// @dev Called by CSM exclusively
/// @param nodeOperatorId ID of the Node Operator
/// @param amount Amount to release in ETH (stETH)
function releaseLockedBondETH(
uint256 nodeOperatorId,
uint256 amount
) external onlyCSM {
CSBondLock._reduceAmount(nodeOperatorId, amount);
}
/// @notice Compensate locked bond ETH for the given Node Operator
//// @dev Called by CSM exclusively
/// @param nodeOperatorId ID of the Node Operator
function compensateLockedBondETH(
uint256 nodeOperatorId
) external payable onlyCSM {
(bool success, ) = LIDO_LOCATOR.elRewardsVault().call{
value: msg.value
}("");
if (!success) revert ElRewardsVaultReceiveFailed();
CSBondLock._reduceAmount(nodeOperatorId, msg.value);
emit BondLockCompensated(nodeOperatorId, msg.value);
}
/// @notice Settle locked bond ETH for the given Node Operator
/// @dev Called by CSM exclusively
/// @param nodeOperatorId ID of the Node Operator
function settleLockedBondETH(uint256 nodeOperatorId) external onlyCSM {
uint256 lockedAmount = CSBondLock.getActualLockedBond(nodeOperatorId);
if (lockedAmount > 0) {
CSBondCore._burn(nodeOperatorId, lockedAmount);
// reduce all locked bond even if bond isn't covered lock fully
CSBondLock._remove(nodeOperatorId);
}
}
/// @notice Penalize bond by burning stETH shares of the given Node Operator
/// @dev Called by CSM exclusively
/// @param nodeOperatorId ID of the Node Operator
/// @param amount Amount to penalize in ETH (stETH)
function penalize(uint256 nodeOperatorId, uint256 amount) external onlyCSM {
CSBondCore._burn(nodeOperatorId, amount);
}
/// @notice Charge fee from bond by transferring stETH shares of the given Node Operator to the charge recipient
/// @dev Called by CSM exclusively
/// @param nodeOperatorId ID of the Node Operator
/// @param amount Amount to charge in ETH (stETH)
function chargeFee(
uint256 nodeOperatorId,
uint256 amount
) external onlyCSM {
CSBondCore._charge(nodeOperatorId, amount, chargePenaltyRecipient);
}
/// @notice Pull fees from CSFeeDistributor to the Node Operator's bond
/// @dev Permissionless method. Can be called before penalty application to ensure that rewards are also penalized
/// @param nodeOperatorId ID of the Node Operator
/// @param cumulativeFeeShares Cumulative fee stETH shares for the Node Operator
/// @param rewardsProof Merkle proof of the rewards
function pullFeeRewards(
uint256 nodeOperatorId,
uint256 cumulativeFeeShares,
bytes32[] calldata rewardsProof
) external {
_onlyExistingNodeOperator(nodeOperatorId);
_pullFeeRewards(nodeOperatorId, cumulativeFeeShares, rewardsProof);
}
/// @notice Recover ERC20 tokens from the contract
/// @param token Address of the ERC20 token to recover
/// @param amount Amount of the ERC20 token to recover
function recoverERC20(address token, uint256 amount) external override {
_onlyRecoverer();
if (token == address(LIDO)) {
revert NotAllowedToRecover();
}
AssetRecovererLib.recoverERC20(token, amount);
}
/// @notice Recover all stETH shares from the contract
/// @dev Accounts for the bond funds stored during recovery
function recoverStETHShares() external {
_onlyRecoverer();
uint256 shares = LIDO.sharesOf(address(this)) - totalBondShares();
AssetRecovererLib.recoverStETHShares(address(LIDO), shares);
}
/// @notice Service method to update allowance to Burner in case it has changed
function renewBurnerAllowance() external {
LIDO.approve(LIDO_LOCATOR.burner(), type(uint256).max);
}
/// @notice Get current and required bond amounts in ETH (stETH) for the given Node Operator
/// @dev To calculate excess bond amount subtract `required` from `current` value.
/// To calculate missed bond amount subtract `current` from `required` value
/// @param nodeOperatorId ID of the Node Operator
/// @return current Current bond amount in ETH
/// @return required Required bond amount in ETH
function getBondSummary(
uint256 nodeOperatorId
) public view returns (uint256 current, uint256 required) {
unchecked {
current = CSBondCore.getBond(nodeOperatorId);
// @dev 'getActualLockedBond' is uint128, so no overflow expected in practice
required =
CSBondCurve.getBondAmountByKeysCount(
CSM.getNodeOperatorNonWithdrawnKeys(nodeOperatorId),
CSBondCurve.getBondCurve(nodeOperatorId)
) +
CSBondLock.getActualLockedBond(nodeOperatorId);
}
}
/// @notice Get current and required bond amounts in stETH shares for the given Node Operator
/// @dev To calculate excess bond amount subtract `required` from `current` value.
/// To calculate missed bond amount subtract `current` from `required` value
/// @param nodeOperatorId ID of the Node Operator
/// @return current Current bond amount in stETH shares
/// @return required Required bond amount in stETH shares
function getBondSummaryShares(
uint256 nodeOperatorId
) public view returns (uint256 current, uint256 required) {
unchecked {
current = CSBondCore.getBondShares(nodeOperatorId);
// @dev 'getActualLockedBond' is uint128, so no overflow expected in practice
required = _sharesByEth(
CSBondCurve.getBondAmountByKeysCount(
CSM.getNodeOperatorNonWithdrawnKeys(nodeOperatorId),
CSBondCurve.getBondCurve(nodeOperatorId)
) + CSBondLock.getActualLockedBond(nodeOperatorId)
);
}
}
/// @notice Get the number of the unbonded keys
/// @param nodeOperatorId ID of the Node Operator
/// @return Unbonded keys count
function getUnbondedKeysCount(
uint256 nodeOperatorId
) public view returns (uint256) {
return
_getUnbondedKeysCount({
nodeOperatorId: nodeOperatorId,
accountLockedBond: true
});
}
/// @notice Get the number of the unbonded keys to be ejected using a forcedTargetLimit
/// @param nodeOperatorId ID of the Node Operator
/// @return Unbonded keys count
function getUnbondedKeysCountToEject(
uint256 nodeOperatorId
) public view returns (uint256) {
return
_getUnbondedKeysCount({
nodeOperatorId: nodeOperatorId,
accountLockedBond: false
});
}
/// @notice Get the required bond in ETH (inc. missed and excess) for the given Node Operator to upload new deposit data
/// @param nodeOperatorId ID of the Node Operator
/// @param additionalKeys Number of new keys to add
/// @return Required bond amount in ETH
function getRequiredBondForNextKeys(
uint256 nodeOperatorId,
uint256 additionalKeys
) public view returns (uint256) {
uint256 current = CSBondCore.getBond(nodeOperatorId);
uint256 requiredForNewTotalKeys = CSBondCurve.getBondAmountByKeysCount(
CSM.getNodeOperatorNonWithdrawnKeys(nodeOperatorId) +
additionalKeys,
CSBondCurve.getBondCurve(nodeOperatorId)
);
uint256 totalRequired = requiredForNewTotalKeys +
CSBondLock.getActualLockedBond(nodeOperatorId);
unchecked {
return totalRequired > current ? totalRequired - current : 0;
}
}
/// @notice Get the bond amount in wstETH required for the `keysCount` keys using the default bond curve
/// @param keysCount Keys count to calculate the required bond amount
/// @param curveId Id of the curve to perform calculations against
/// @return wstETH amount required for the `keysCount`
function getBondAmountByKeysCountWstETH(
uint256 keysCount,
uint256 curveId
) public view returns (uint256) {
return
_sharesByEth(
CSBondCurve.getBondAmountByKeysCount(keysCount, curveId)
);
}
/// @notice Get the bond amount in wstETH required for the `keysCount` keys using the custom bond curve
/// @param keysCount Keys count to calculate the required bond amount
/// @param curve Bond curve definition.
/// Use CSBondCurve.getBondCurve(id) method to get the definition for the exiting curve
/// @return wstETH amount required for the `keysCount`
function getBondAmountByKeysCountWstETH(
uint256 keysCount,
BondCurve memory curve
) public view returns (uint256) {
return
_sharesByEth(
CSBondCurve.getBondAmountByKeysCount(keysCount, curve)
);
}
/// @notice Get the required bond in wstETH (inc. missed and excess) for the given Node Operator to upload new keys
/// @param nodeOperatorId ID of the Node Operator
/// @param additionalKeys Number of new keys to add
/// @return Required bond in wstETH
function getRequiredBondForNextKeysWstETH(
uint256 nodeOperatorId,
uint256 additionalKeys
) public view returns (uint256) {
return
_sharesByEth(
getRequiredBondForNextKeys(nodeOperatorId, additionalKeys)
);
}
function _pullFeeRewards(
uint256 nodeOperatorId,
uint256 cumulativeFeeShares,
bytes32[] calldata rewardsProof
) internal {
uint256 distributed = feeDistributor.distributeFees(
nodeOperatorId,
cumulativeFeeShares,
rewardsProof
);
CSBondCore._increaseBond(nodeOperatorId, distributed);
}
/// @dev Overrides the original implementation to account for a locked bond and withdrawn validators
function _getClaimableBondShares(
uint256 nodeOperatorId
) internal view override returns (uint256) {
unchecked {
uint256 current = CSBondCore.getBondShares(nodeOperatorId);
uint256 required = _sharesByEth(
CSBondCurve.getBondAmountByKeysCount(
CSM.getNodeOperatorNonWithdrawnKeys(nodeOperatorId),
CSBondCurve.getBondCurve(nodeOperatorId)
) + CSBondLock.getActualLockedBond(nodeOperatorId)
);
return current > required ? current - required : 0;
}
}
/// @dev Unbonded stands for the amount of the keys not fully covered with the bond
function _getUnbondedKeysCount(
uint256 nodeOperatorId,
bool accountLockedBond
) internal view returns (uint256) {
uint256 nonWithdrawnKeys = CSM.getNodeOperatorNonWithdrawnKeys(
nodeOperatorId
);
unchecked {
/// 10 wei added to account for possible stETH rounding errors
/// https://github.com/lidofinance/lido-dao/issues/442#issuecomment-1182264205.
/// Should be sufficient for ~ 40 years
uint256 currentBond = CSBondCore._ethByShares(
getBondShares(nodeOperatorId)
) + 10 wei;
if (accountLockedBond) {
uint256 lockedBond = CSBondLock.getActualLockedBond(
nodeOperatorId
);
if (currentBond <= lockedBond) return nonWithdrawnKeys;
currentBond -= lockedBond;
}
uint256 bondedKeys = CSBondCurve.getKeysCountByBondAmount(
currentBond,
CSBondCurve.getBondCurve(nodeOperatorId)
);
return
nonWithdrawnKeys > bondedKeys
? nonWithdrawnKeys - bondedKeys
: 0;
}
}
function _onlyRecoverer() internal view override {
_checkRole(RECOVERER_ROLE);
}
function _onlyExistingNodeOperator(uint256 nodeOperatorId) internal view {
if (nodeOperatorId < CSM.getNodeOperatorsCount()) return;
revert NodeOperatorDoesNotExist();
}
function _setChargePenaltyRecipient(
address _chargePenaltyRecipient
) private {
if (_chargePenaltyRecipient == address(0)) {
revert ZeroChargePenaltyRecipientAddress();
}
chargePenaltyRecipient = _chargePenaltyRecipient;
emit ChargePenaltyRecipientSet(_chargePenaltyRecipient);
}
}