-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathStakeModuleLib.sol
More file actions
1361 lines (1185 loc) · 46 KB
/
StakeModuleLib.sol
File metadata and controls
1361 lines (1185 loc) · 46 KB
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
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.20;
// internal - globals
import {ID_TYPE} from "../../../globals/id_type.sol";
import {PERCENTAGE_DENOMINATOR, gETH_DENOMINATOR} from "../../../globals/macros.sol";
import {VALIDATOR_STATE} from "../../../globals/validator_state.sol";
import {RESERVED_KEY_SPACE as rks} from "../../../globals/reserved_key_space.sol";
// internal - interfaces
import {IgETH} from "../../../interfaces/IgETH.sol";
import {IGeodePackage} from "../../../interfaces/packages/IGeodePackage.sol";
import {ILiquidityPackage} from "../../../interfaces/packages/ILiquidityPackage.sol";
import {IWhitelist} from "../../../interfaces/helpers/IWhitelist.sol";
// internal - structs
import {DataStoreModuleStorage} from "../../DataStoreModule/structs/storage.sol";
import {StakeModuleStorage} from "../structs/storage.sol";
import {ConstantValidatorData} from "../structs/helpers.sol";
import {Validator} from "../structs/utils.sol";
// internal - libraries
import {DataStoreModuleLib as DSML} from "../../DataStoreModule/libs/DataStoreModuleLib.sol";
import {DepositContractLib as DCL} from "./DepositContractLib.sol";
/**
* @title SML: Stake Module Library (The Staking Library)
*
* @notice Creating a global standard for Staking, allowing anyone to OWN a trustless staking pool,
* improving the user experience for stakers and removing the "need" for centralized or decentralized intermediaries.
* * Exclusively contains functions related to:
* * 1. Pool and Operator management, after initiation (review IEL).
* * 2. Validator Delegation.
* * 3. Depositing.
* * 4. Staking Operations.
*
* @dev review: DataStoreModule for the id based isolated storage logic.
* @dev review: InitiatorExtensionLib for initiator functions.
* @dev review: OracleExtensionLib for oracle logic.
*
* @dev Every pool is isolated and every validator is unique. We segregate all the risk.
*
* @dev CONTROLLER and Maintainer:
* CONTROLLER is the owner of an ID, it manages the pool/operator. Its security is exteremely important.
* maintainer is the worker, can be used to automate some daily tasks:
* * distributing validators for Staking Pools or creating validators for Operators.
* * not so crucial in terms of security.
*
* @dev Users:
* Type 4 : Permissioned Operators
* * Needs to be onboarded by the Dual Governance (Senate + Governance).
* * Maintains Beacon Chain Validators on behalf of the Staking Pools.
* * Can participate in the Operator Marketplace after initiation.
* * Can utilize maintainers for staking operations.
*
* Type 5 : Permissionless Configurable Staking Pools
* * Permissionless to create.
* * Can utilize powers of packages and middlewares such as Bound Liquidity Packages, gETHMiddlewares etc.
* * Can be public or private, can use a whitelist if private.
* * Can utilize maintainers for validator distribution on Operator Marketplace.
* * Uses a Withdrawal Package to be given as withdrawalCredential on validator creation,
* * accruing rewards and keeping Staked Ether safe and isolated.
*
* @dev Packages:
* An ID can only point to one version of a Package at a time.
* Built by utilizing the Modules!
* Can be upgraded by a dual governance, via pullUpgrade.
* * A Package's dual governance consists of Portal(governance) and the pool owner(senate).
*
* Type 10011 : Withdrawal Package
* * Mandatory.
* * CONTROLLER is the implementation contract position (always)
* * Version Release Requires the approval of Senate
* * Upgrading to a new version is optional for pool owners.
* * * Staking Pools are in "Isolation Mode" until their Withdrawal Package is upgraded.
* * * Meaning, no more Depositing or Validator Proposal can happen.
* * Custodian of the validator funds after creation, including any type of rewards and fees.
*
* Type 10021 : Liquidity Package implementation
* * Optional.
* * CONTROLLER is the implementation contract position (always)
* * Version Release Requires the approval of Senate
* * Upgrading to a new version is optional for pool owners.
* * * Liquidity Packages are in "Isolation Mode" until upgraded.
*
* @dev Middlewares:
* Can support many different versions that can be utilized by the Pool Owners.
* No particular way to build one.
* Cannot be upgraded.
* Currently only gETHMiddlewares.
*
* Type 20011 : gETHMiddleware
* * Optional.
* * CONTROLLER is the implementation contract position (always)
* * Requires the approval of Senate
* * Currently should be utilized on initiation.
*
* @dev Contracts relying on this library must initialize StakeModuleLib.StakeModuleStorage
*
* @dev Functions are protected with authentication function
*
* @author Ice Bear & Crash Bandicoot
*/
library StakeModuleLib {
using DSML for DataStoreModuleStorage;
/**
* @custom:section ** CONSTANTS **
*/
/// @notice limiting the GOVERNANCE_FEE to 5%
uint256 internal constant MAX_POOL_INFRASTRUCTURE_FEE = 5e8; // (PERCENTAGE_DENOMINATOR * 5) / 100;
/// @notice limit the beacon delays on entry and exit since it can be adjusted by the governance.
uint256 internal constant MAX_BEACON_DELAY = 90 days; // = MIN_VALIDATOR_PERIOD
/// @notice limiting the pool and operator maintenance fee, 10%
uint256 internal constant MAX_MAINTENANCE_FEE = 1e9; // (PERCENTAGE_DENOMINATOR * 10) / 100;
/// @notice effective on allowance per operator, prevents overflow. Exclusive, save gas with +1.
uint256 internal constant MAX_ALLOWANCE = 1e6;
/// @notice price of gETH is only valid for 24H, minting is not allowed afterwards.
uint256 internal constant PRICE_EXPIRY = 24 hours;
/// @notice ignoring any buybacks if the Liquidity Package has a low debt
uint256 internal constant IGNORABLE_DEBT = 1 ether;
/// @notice limiting the operator.validatorPeriod, between 3 months to 2 years
uint256 internal constant MIN_VALIDATOR_PERIOD = 90 days; // 3 * 30 days
uint256 internal constant MAX_VALIDATOR_PERIOD = 730 days; // 2 * 365 days
/// @notice some parameter changes are effective after a delay
uint256 internal constant SWITCH_LATENCY = 3 days;
/**
* @custom:section ** EVENTS **
*/
event InfrastructureFeeSet(uint256 _type, uint256 fee);
event BeaconDelaySet(uint256 entryDelay, uint256 exitDelay);
event VisibilitySet(uint256 id, bool isPrivate);
event YieldReceiverSet(uint256 indexed poolId, address yieldReceiver);
event MaintainerChanged(uint256 indexed id, address newMaintainer);
event FeeSwitched(uint256 indexed id, uint256 fee, uint256 effectiveAfter);
event ValidatorPeriodSwitched(uint256 indexed operatorId, uint256 period, uint256 effectiveAfter);
event Delegation(uint256 poolId, uint256 indexed operatorId, uint256 allowance);
event FallbackOperator(uint256 poolId, uint256 indexed operatorId, uint256 threshold);
event Deposit(uint256 indexed poolId, uint256 boughtgETH, uint256 mintedgETH);
event StakeProposal(uint256 poolId, uint256 operatorId, bytes[] pubkeys);
event Stake(bytes[] pubkeys);
event ExitRequest(bytes pubkey);
event Exit(bytes pubkey);
/**
* @custom:section ** GOVERNING **
*
* @custom:visibility -> external
* @dev IMPORTANT! These functions should be governed by a governance! Which is not done here!
*/
/**
* @notice Set the maxiumum allowed beacon delay for blaming validators on creation and exit.
* @dev high beacon delays will affect the ux negatively, low delays can cause issues for operators.
*/
function setBeaconDelays(StakeModuleStorage storage self, uint256 entry, uint256 exit) external {
require(entry < MAX_BEACON_DELAY, "SML:> MAX");
require(exit < MAX_BEACON_DELAY, "SML:> MAX");
self.BEACON_DELAY_ENTRY = entry;
self.BEACON_DELAY_EXIT = exit;
emit BeaconDelaySet(entry, exit);
}
/**
* @notice Set a fee (denominated in PERCENTAGE_DENOMINATOR) for any given TYPE.
* @dev Changing the Staking Pool fee, only applies to the newly created validators.
* @dev advise that there can be other fees within the package, thus we will never allow
* * governance to take more than x%, while x<80(at least). For now, 50% limit on all fees
* * makes sense to us. However, it can be adjusted to any value between 0-80 in the future.
*/
function setInfrastructureFee(
StakeModuleStorage storage self,
uint256 _type,
uint256 fee
) external {
if (_type == ID_TYPE.POOL) {
require(fee <= MAX_POOL_INFRASTRUCTURE_FEE, "PORTAL:> MAX");
} else {
require(fee < PERCENTAGE_DENOMINATOR / 2, "SML:> 50%");
}
self.infrastructureFees[_type] = fee;
emit InfrastructureFeeSet(_type, fee);
}
/**
* @custom:section ** AUTHENTICATION **
*
* @custom:visibility -> view-internal
*/
/**
* @notice restricts the access to given function based on TYPE and msg.sender
* @param _expectCONTROLLER restricts the access to only CONTROLLER.
* @param _expectMaintainer restricts the access to only maintainer.
* @param _restrictionMap Restricts which TYPEs can pass the authentication.
* * [0: Operator = TYPE(4), 1: Pool = TYPE(5)]
* @dev can only be used after an ID is initiated
* @dev CONTROLLERS and maintainers of the Prisoned Operators cannot access.
*/
function _authenticate(
DataStoreModuleStorage storage DATASTORE,
uint256 _id,
bool _expectCONTROLLER,
bool _expectMaintainer,
bool[2] memory _restrictionMap
) internal view {
require(DATASTORE.readUint(_id, rks.initiated) != 0, "SML:not initiated");
uint256 typeOfId = DATASTORE.readUint(_id, rks.TYPE);
if (typeOfId == ID_TYPE.OPERATOR) {
require(_restrictionMap[0], "SML:TYPE not allowed");
if (_expectCONTROLLER || _expectMaintainer) {
require(!isPrisoned(DATASTORE, _id), "SML:prisoned, get in touch with governance");
}
} else if (typeOfId == ID_TYPE.POOL) {
require(_restrictionMap[1], "SML:TYPE not allowed");
} else {
revert("SML:invalid TYPE");
}
if (_expectMaintainer) {
require(
msg.sender == DATASTORE.readAddress(_id, rks.maintainer),
"SML:sender not maintainer"
);
return;
}
if (_expectCONTROLLER) {
require(
msg.sender == DATASTORE.readAddress(_id, rks.CONTROLLER),
"SML:sender not CONTROLLER"
);
return;
}
}
/**
* @custom:subsection ** POOL VISIBILITY **
*/
/**
* @custom:visibility -> external
*/
/**
* @notice changes the visibility of the pool
* @param makePrivate true if pool should be private, false for public pools
* @dev whitelist is cleared when pool is set to public, to prevent legacy bugs if ever made private again.
* Note private pools can whitelist addresses with the help of a third party contract.
*/
function setPoolVisibility(
DataStoreModuleStorage storage DATASTORE,
uint256 poolId,
bool makePrivate
) public {
_authenticate(DATASTORE, poolId, true, false, [false, true]);
require(makePrivate != isPrivatePool(DATASTORE, poolId), "SML:already set");
DATASTORE.writeUint(poolId, rks.privatePool, makePrivate ? 1 : 0);
if (!makePrivate) {
DATASTORE.writeAddress(poolId, rks.whitelist, address(0));
}
emit VisibilitySet(poolId, makePrivate);
}
/**
* @notice private pools can whitelist addresses with the help of a third party contract.
* @dev Whitelisting contracts should implement IWhitelist interface.
*/
function setWhitelist(
DataStoreModuleStorage storage DATASTORE,
uint256 poolId,
address whitelist
) external {
_authenticate(DATASTORE, poolId, true, false, [false, true]);
require(isPrivatePool(DATASTORE, poolId), "SML:must be private pool");
DATASTORE.writeAddress(poolId, rks.whitelist, whitelist);
}
/**
* @custom:visibility -> view-public
*/
/**
* @notice returns true if the pool is private
*/
function isPrivatePool(
DataStoreModuleStorage storage DATASTORE,
uint256 poolId
) public view returns (bool) {
return (DATASTORE.readUint(poolId, rks.privatePool) == 1);
}
/**
* @notice checks if the Whitelist allows staker to use given private pool
* @dev Owner of the pool doesn't need whitelisting
* @dev Otherwise requires a whitelisting address to be set
*/
function isWhitelisted(
DataStoreModuleStorage storage DATASTORE,
uint256 poolId,
address staker
) public view returns (bool) {
if (DATASTORE.readAddress(poolId, rks.CONTROLLER) == staker) {
return true;
}
address whitelist = DATASTORE.readAddress(poolId, rks.whitelist);
if (whitelist == address(0)) {
return false;
}
if (whitelist.code.length > 0) {
try IWhitelist(whitelist).isAllowed(staker) returns (bool _isAllowed) {
return _isAllowed;
} catch {
return false;
}
} else {
return false;
}
}
/**
* @custom:section ** ID MANAGEMENT **
*
*/
/**
* @custom:subsection ** YIELD SEPARATION **
*/
/**
* @custom:visibility -> external
*/
/**
* @notice Set the yield receiver address to activate or deactivete yield separation logic.
* * If set other than address(0) separation will be activated, if set back to address(0)
* * separation will be deactivated again.
* @param poolId the gETH id of the Pool
* @param yieldReceiver address of the yield receiver
* @dev Only CONTROLLER of pool can set yield receier.
*/
function setYieldReceiver(
DataStoreModuleStorage storage DATASTORE,
uint256 poolId,
address yieldReceiver
) external {
_authenticate(DATASTORE, poolId, true, false, [false, true]);
DATASTORE.writeAddress(poolId, rks.yieldReceiver, yieldReceiver);
emit YieldReceiverSet(poolId, yieldReceiver);
}
/**
* @custom:subsection ** MAINTAINER **
*/
/**
* @custom:visibility -> internal
*/
/**
* @notice Set the maintainer address on initiation or later
* @param _newMaintainer address of the new maintainer
*/
function _setMaintainer(
DataStoreModuleStorage storage DATASTORE,
uint256 _id,
address _newMaintainer
) internal {
require(_newMaintainer != address(0), "SML:maintainer cannot be zero");
DATASTORE.writeAddress(_id, rks.maintainer, _newMaintainer);
emit MaintainerChanged(_id, _newMaintainer);
}
/**
* @custom:visibility -> external
*/
/**
* @notice CONTROLLER of the ID can change the maintainer to any address other than ZERO_ADDRESS
* @dev there can only be 1 maintainer per ID.
* @dev it is wise to change the maintainer before the CONTROLLER, in case of any migration
* @dev we don't use _authenticate here because malicious maintainers can imprison operators
* * and prevent them entering here, smh.
*/
function changeMaintainer(
DataStoreModuleStorage storage DATASTORE,
uint256 id,
address newMaintainer
) external {
require(DATASTORE.readUint(id, rks.initiated) != 0, "SML:ID is not initiated");
require(msg.sender == DATASTORE.readAddress(id, rks.CONTROLLER), "SML:sender not CONTROLLER");
uint256 typeOfId = DATASTORE.readUint(id, rks.TYPE);
require(typeOfId == ID_TYPE.OPERATOR || typeOfId == ID_TYPE.POOL, "SML:invalid TYPE");
_setMaintainer(DATASTORE, id, newMaintainer);
}
/**
* @custom:subsection ** MAINTENANCE FEE **
*/
/**
* @custom:visibility -> view-public
*/
/**
* @notice Gets fee as a percentage, PERCENTAGE_DENOMINATOR = 100%
*
* @dev respecs to the switching delay.
*
* @return fee = percentage * PERCENTAGE_DENOMINATOR / 100
*/
function getMaintenanceFee(
DataStoreModuleStorage storage DATASTORE,
uint256 id
) public view returns (uint256 fee) {
if (DATASTORE.readUint(id, rks.feeSwitch) > block.timestamp) {
return DATASTORE.readUint(id, rks.priorFee);
}
return DATASTORE.readUint(id, rks.fee);
}
/**
* @custom:visibility -> internal
*/
/**
* @notice internal function to set fee with NO DELAY
*/
function _setMaintenanceFee(
DataStoreModuleStorage storage DATASTORE,
uint256 _id,
uint256 _newFee
) internal {
require(_newFee <= MAX_MAINTENANCE_FEE, "SML:> MAX_MAINTENANCE_FEE");
DATASTORE.writeUint(_id, rks.fee, _newFee);
}
/**
* @custom:visibility -> external
*/
/**
* @notice Changes the fee that is applied to the newly created validators, with A DELAY OF SWITCH_LATENCY.
* @dev Cannot be called again while its currently switching.
* @dev advise that 100% == PERCENTAGE_DENOMINATOR
*/
function switchMaintenanceFee(
DataStoreModuleStorage storage DATASTORE,
uint256 id,
uint256 newFee
) external {
_authenticate(DATASTORE, id, true, false, [true, true]);
require(block.timestamp > DATASTORE.readUint(id, rks.feeSwitch), "SML:currently switching");
DATASTORE.writeUint(id, rks.priorFee, DATASTORE.readUint(id, rks.fee));
DATASTORE.writeUint(id, rks.feeSwitch, block.timestamp + SWITCH_LATENCY);
_setMaintenanceFee(DATASTORE, id, newFee);
emit FeeSwitched(id, newFee, block.timestamp + SWITCH_LATENCY);
}
/**
* @custom:subsection ** INTERNAL WALLET **
*
* @dev Internal wallet of an ID accrues fees over time.
* It is also used by Node Operators to fund 1 ETH per validator proposal, which is reimbursed if/when activated.
*/
/**
* @custom:visibility -> internal
*/
/**
* @notice Simply increases the balance of an IDs Maintainer wallet
* @param _value Ether (in Wei) amount to increase the wallet balance.
*/
function _increaseWalletBalance(
DataStoreModuleStorage storage DATASTORE,
uint256 _id,
uint256 _value
) internal {
DATASTORE.addUint(_id, rks.wallet, _value);
}
/**
* @notice To decrease the balance of an Operator's wallet internally
* @param _value Ether (in Wei) amount to decrease the wallet balance and send back to Maintainer.
*/
function _decreaseWalletBalance(
DataStoreModuleStorage storage DATASTORE,
uint256 _id,
uint256 _value
) internal {
require(DATASTORE.readUint(_id, rks.wallet) >= _value, "SML:insufficient wallet balance");
DATASTORE.subUint(_id, rks.wallet, _value);
}
/**
* @custom:visibility -> external
*/
/**
* @notice external function to increase the internal wallet balance
* @dev anyone can increase the balance directly, useful for Withdrawal Packages and distributed fees etc.
*/
function increaseWalletBalance(
DataStoreModuleStorage storage DATASTORE,
uint256 id
) external returns (bool success) {
_authenticate(DATASTORE, id, false, false, [true, true]);
_increaseWalletBalance(DATASTORE, id, msg.value);
success = true;
}
/**
* @notice external function to decrease the internal wallet balance
* @dev only CONTROLLER can decrease the balance externally,
* @return success if the amount was sent and deducted
*/
function decreaseWalletBalance(
DataStoreModuleStorage storage DATASTORE,
uint256 id,
uint256 value
) external returns (bool success) {
_authenticate(DATASTORE, id, true, false, [true, true]);
require(address(this).balance >= value, "SML:insufficient contract balance");
_decreaseWalletBalance(DATASTORE, id, value);
address controller = DATASTORE.readAddress(id, rks.CONTROLLER);
(success, ) = payable(controller).call{value: value}("");
require(success, "SML:Failed to send ETH");
}
/**
* @custom:subsection ** OPERATORS PERIOD **
*/
/**
* @custom:visibility -> view-public
*/
function getValidatorPeriod(
DataStoreModuleStorage storage DATASTORE,
uint256 id
) public view returns (uint256 period) {
if (DATASTORE.readUint(id, rks.periodSwitch) > block.timestamp) {
return DATASTORE.readUint(id, rks.priorPeriod);
}
return DATASTORE.readUint(id, rks.validatorPeriod);
}
/**
* @custom:visibility -> internal
*/
/**
* @notice internal function to set validator period with NO DELAY
*/
function _setValidatorPeriod(
DataStoreModuleStorage storage DATASTORE,
uint256 _operatorId,
uint256 _newPeriod
) internal {
require(_newPeriod >= MIN_VALIDATOR_PERIOD, "SML:< MIN_VALIDATOR_PERIOD");
require(_newPeriod <= MAX_VALIDATOR_PERIOD, "SML:> MAX_VALIDATOR_PERIOD");
DATASTORE.writeUint(_operatorId, rks.validatorPeriod, _newPeriod);
}
/**
* @custom:visibility -> external
*/
/**
* @notice updates validatorPeriod for given operator, with A DELAY OF SWITCH_LATENCY.
* @dev limited by MIN_VALIDATOR_PERIOD and MAX_VALIDATOR_PERIOD
*/
function switchValidatorPeriod(
DataStoreModuleStorage storage DATASTORE,
uint256 operatorId,
uint256 newPeriod
) external {
_authenticate(DATASTORE, operatorId, true, false, [true, false]);
require(
block.timestamp > DATASTORE.readUint(operatorId, rks.periodSwitch),
"SML:currently switching"
);
DATASTORE.writeUint(
operatorId,
rks.priorPeriod,
DATASTORE.readUint(operatorId, rks.validatorPeriod)
);
DATASTORE.writeUint(operatorId, rks.periodSwitch, block.timestamp + SWITCH_LATENCY);
_setValidatorPeriod(DATASTORE, operatorId, newPeriod);
emit ValidatorPeriodSwitched(operatorId, newPeriod, block.timestamp + SWITCH_LATENCY);
}
/**
* @custom:section ** PRISON **
*
* @custom:visibility -> view-public
* @dev check OEL.blameProposal and OEL.blameExit for imprisonment details
*/
/**
* @notice Checks if the given operator is Prisoned
* @dev rks.release key refers to the end of the last imprisonment, when the limitations of operator is lifted
*/
function isPrisoned(
DataStoreModuleStorage storage DATASTORE,
uint256 operatorId
) public view returns (bool) {
return (block.timestamp < DATASTORE.readUint(operatorId, rks.release));
}
/**
* @custom:section ** VALIDATOR DELEGATION **
*/
/**
* @custom:visibility -> view-public
*/
/**
* @notice maximum number of remaining operator allowance that the given Operator is allowed to create for given Pool
* @dev an operator cannot create new validators if:
* * 1. operator is a monopoly
* * 2. allowance is filled
* * * But if operator is set as a fallback, it can if set fallbackThreshold is reached on all allowances.
* @dev If operator withdraws a validator, then able to create a new one.
* @dev prestake checks the approved validator count to make sure the number of validators are not bigger than allowance
* @dev allowance doesn't change when new validators created or old ones are unstaked.
*/
function operatorAllowance(
StakeModuleStorage storage self,
DataStoreModuleStorage storage DATASTORE,
uint256 poolId,
uint256 operatorId
) public view returns (uint256 remValidators) {
// monopoly check
{
// readUint for an array gives us length
uint256 numOperatorValidators = DATASTORE.readUint(operatorId, rks.validators);
uint256 monopoly_threshold = self.MONOPOLY_THRESHOLD;
if (numOperatorValidators >= monopoly_threshold) {
return 0;
} else {
remValidators = monopoly_threshold - numOperatorValidators;
}
}
// fallback check
{
if (operatorId == DATASTORE.readUint(poolId, rks.fallbackOperator)) {
// readUint for an array gives us length
uint256 numPoolValidators = DATASTORE.readUint(poolId, rks.validators);
uint256 totalAllowance = DATASTORE.readUint(poolId, rks.totalAllowance);
if (
totalAllowance == 0 ||
(((numPoolValidators * PERCENTAGE_DENOMINATOR) / totalAllowance) >=
DATASTORE.readUint(poolId, rks.fallbackThreshold))
) {
return remValidators;
}
}
}
// approval check
{
uint256 allowance = DATASTORE.readUint(poolId, DSML.getKey(operatorId, rks.allowance));
uint256 pooledValidators = DATASTORE.readUint(
poolId,
DSML.getKey(operatorId, rks.proposedValidators)
) + DATASTORE.readUint(poolId, DSML.getKey(operatorId, rks.activeValidators));
if (pooledValidators >= allowance) {
return 0;
} else {
uint256 remAllowance = allowance - pooledValidators;
if (remValidators > remAllowance) {
remValidators = remAllowance;
}
}
}
}
/**
* @custom:visibility -> internal
*/
/**
* @notice To give allowence to node operator for a pool. It re-sets the allowance with the given value.
* @dev The value that is returned is not the new allowance, but the old one since it is required
* * at the point where it is being returned.
* @return oldAllowance to be used later, nothing is done with it within this function.
*/
function _approveOperator(
DataStoreModuleStorage storage DATASTORE,
uint256 poolId,
uint256 operatorId,
uint256 allowance
) internal returns (uint256 oldAllowance) {
bytes32 allowanceKey = DSML.getKey(operatorId, rks.allowance);
oldAllowance = DATASTORE.readUint(poolId, allowanceKey);
DATASTORE.writeUint(poolId, allowanceKey, allowance);
emit Delegation(poolId, operatorId, allowance);
}
/**
* @custom:visibility -> external
*/
/**
* @notice To allow a Node Operator run validators for your Pool with a given number of validators.
* * This number can be set again at any given point in the future.
* @param poolId the gETH id of the Pool
* @param operatorIds array of Operator IDs to allow them create validators
* @param allowances the MAX number of validators that can be created by the Operator, for given Pool
* @dev When decreased the approved validator count below current active+proposed validators,
* operator cannot create new validators.
*/
function delegate(
DataStoreModuleStorage storage DATASTORE,
uint256 poolId,
uint256[] calldata operatorIds,
uint256[] calldata allowances
) external {
_authenticate(DATASTORE, poolId, false, true, [false, true]);
uint256 operatorIdsLen = operatorIds.length;
require(operatorIdsLen == allowances.length, "SML:allowances should match");
for (uint256 i; i < operatorIdsLen; ) {
require(
DATASTORE.readUint(operatorIds[i], rks.TYPE) == ID_TYPE.OPERATOR,
"SML:id not operator"
);
require(allowances[i] <= MAX_ALLOWANCE, "SML:> MAX_ALLOWANCE, set fallback");
unchecked {
i += 1;
}
}
uint256 newCumulativeSubset;
uint256 oldCumulativeSubset;
for (uint256 i; i < operatorIdsLen; ) {
newCumulativeSubset += allowances[i];
oldCumulativeSubset += _approveOperator(DATASTORE, poolId, operatorIds[i], allowances[i]);
unchecked {
i += 1;
}
}
if (newCumulativeSubset > oldCumulativeSubset) {
DATASTORE.addUint(poolId, rks.totalAllowance, newCumulativeSubset - oldCumulativeSubset);
} else if (newCumulativeSubset < oldCumulativeSubset) {
DATASTORE.subUint(poolId, rks.totalAllowance, oldCumulativeSubset - newCumulativeSubset);
}
}
/**
* @notice To allow a Node Operator run validators for your Pool without a limit
* * after pool reaches a given treshold as percentage.
* * fallback operator and percentage can be set again at any given point in the future.
* * cannot set an operator as a fallback operator while it is currently in prison.
* @param poolId the gETH id of the Pool
* @param operatorId Operator ID to allow create validators
* @param fallbackThreshold the percentage (with PERCENTAGE_DENOMINATOR) that fallback operator
* * is activated for given Pool. Should not be greater than 100.
*/
function setFallbackOperator(
DataStoreModuleStorage storage DATASTORE,
uint256 poolId,
uint256 operatorId,
uint256 fallbackThreshold
) external {
_authenticate(DATASTORE, poolId, false, true, [false, true]);
if (operatorId == 0) {
DATASTORE.writeUint(poolId, rks.fallbackOperator, 0);
DATASTORE.writeUint(poolId, rks.fallbackThreshold, 0);
emit FallbackOperator(poolId, 0, 0);
} else {
require(
DATASTORE.readUint(operatorId, rks.TYPE) == ID_TYPE.OPERATOR,
"SML:fallback not operator"
);
require(
fallbackThreshold <= PERCENTAGE_DENOMINATOR,
"SML:threshold cannot be greater than 100"
);
DATASTORE.writeUint(poolId, rks.fallbackThreshold, fallbackThreshold);
DATASTORE.writeUint(poolId, rks.fallbackOperator, operatorId);
emit FallbackOperator(poolId, operatorId, fallbackThreshold);
}
}
/**
* @custom:section ** POOLING **
*/
/**
* @custom:subsection ** DEPOSIT HELPERS **
*/
/**
* @custom:visibility -> view-internal
*/
function _isGeodePackageIsolated(address _packageAddress) internal view returns (bool) {
return IGeodePackage(_packageAddress).isolationMode();
}
/**
* @notice returns wrapped bound liquidity package. If deployed, if not in isolationMode.
* @dev returns address(0) if no pool or it is under isolation
*/
function _getLiquidityPackage(
DataStoreModuleStorage storage DATASTORE,
uint256 _poolId
) internal view returns (ILiquidityPackage) {
address liqPool = DATASTORE.readAddress(_poolId, rks.liquidityPackage);
if (liqPool == address(0)) {
return ILiquidityPackage(address(0));
} else if (_isGeodePackageIsolated(liqPool)) {
return ILiquidityPackage(address(0));
} else {
return ILiquidityPackage(liqPool);
}
}
/**
* @custom:visibility -> view-public
*/
/**
* @notice returns true if the price is valid:
* - last price syncinc happened less than 24h
* - there has been no oracle reports since the last update
*
* @dev known bug / feature: if there have been no oracle updates,
* * this function will return true.
*
* lastupdate + PRICE_EXPIRY >= block.timestamp ? true
* : lastupdate >= self.ORACLE_UPDATE_TIMESTAMP ? true
* : false
*/
function isPriceValid(
StakeModuleStorage storage self,
uint256 poolId
) public view returns (bool isValid) {
uint256 lastupdate = self.gETH.priceUpdateTimestamp(poolId);
unchecked {
isValid =
lastupdate + PRICE_EXPIRY >= block.timestamp &&
lastupdate >= self.ORACLE_UPDATE_TIMESTAMP;
}
}
/**
* @notice checks if staking is allowed in given staking pool
* @notice staking is not allowed if:
* 1. Price is not valid
* 2. WithdrawalPackage is in Isolation Mode, can have many reasons
*/
function isMintingAllowed(
StakeModuleStorage storage self,
DataStoreModuleStorage storage DATASTORE,
uint256 poolId
) public view returns (bool) {
return
(isPriceValid(self, poolId)) &&
!(_isGeodePackageIsolated(DATASTORE.readAddress(poolId, rks.withdrawalPackage)));
}
/**
* @custom:subsection ** DEPOSIT **
*/
/**
* @custom:visibility -> internal
*/
/**
* @notice mints gETH for a given ETH amount, keeps the tokens in Portal.
* @dev fails if minting is not allowed: invalid price, or isolationMode.
*/
function _mintgETH(
StakeModuleStorage storage self,
DataStoreModuleStorage storage DATASTORE,
uint256 _poolId,
uint256 _ethAmount
) internal returns (uint256 mintedgETH) {
require(isMintingAllowed(self, DATASTORE, _poolId), "SML:minting is not allowed");
uint256 price = self.gETH.pricePerShare(_poolId);
require(price > 0, "SML:price is zero?");
mintedgETH = (((_ethAmount * gETH_DENOMINATOR) / price));
self.gETH.mint(address(this), _poolId, mintedgETH, "");
DATASTORE.addUint(_poolId, rks.surplus, _ethAmount);
}
/**
* @notice conducts a buyback using the given liquidity package
* @param _poolId id of the gETH that will be bought
* @param _maxEthToSell max ETH amount to sell in the liq pool
* @param _deadline TX is expected to revert by Swap.sol if not meet
* @dev this function assumes that pool is deployed by deployLiquidityPackage
* as index 0 is ETH and index 1 is gETH!
*/
function _buyback(
DataStoreModuleStorage storage DATASTORE,
uint256 _poolId,
uint256 _maxEthToSell,
uint256 _deadline
) internal returns (uint256 remETH, uint256 boughtgETH) {
ILiquidityPackage LP = _getLiquidityPackage(DATASTORE, _poolId);
// skip if no liquidity package is found
if (address(LP) != address(0)) {
uint256 debt = LP.getDebt();
// skip if debt is too low
if (debt > IGNORABLE_DEBT) {
if (_maxEthToSell > debt) {
// if debt is lower, then only sell debt
remETH = _maxEthToSell - debt;
} else {
// if eth is lower, then sell all eth, remETH already 0
debt = _maxEthToSell;
}
// SWAP in LP
boughtgETH = LP.swap{value: debt}(0, 1, debt, 0, _deadline);
} else {
remETH = _maxEthToSell;
}
} else {
remETH = _maxEthToSell;
}
}
/**
* @custom:visibility -> external
*/
/**
* @notice Allowing users to deposit into a staking pool.
* @notice If a pool is not public, only the controller and if there is a whitelist contract, the whitelisted addresses can deposit.
* @param poolId id of the staking pool, liquidity package and gETH to be used.
* @param mingETH liquidity package parameter
* @param deadline liquidity package parameter
* @dev an example for minting + buybacks
* Buys from DWP if price is low -debt-, mints new tokens if surplus is sent -more than debt-
* * debt msgValue
* * 100 10 => buyback
* * 100 100 => buyback
* * 10 100 => buyback + mint
* * 1 x => mint
* * 0.5 x => mint
* * 0 x => mint
*/
function deposit(
StakeModuleStorage storage self,
DataStoreModuleStorage storage DATASTORE,
uint256 poolId,
uint256 mingETH,