-
Notifications
You must be signed in to change notification settings - Fork 3.7k
/
stake.move
2763 lines (2442 loc) · 135 KB
/
stake.move
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
///
/// Validator lifecycle:
/// 1. Prepare a validator node set up and call stake::initialize_validator
/// 2. Once ready to deposit stake (or have funds assigned by a staking service in exchange for ownership capability),
/// call stake::add_stake (or *_with_cap versions if called from the staking service)
/// 3. Call stake::join_validator_set (or _with_cap version) to join the active validator set. Changes are effective in
/// the next epoch.
/// 4. Validate and gain rewards. The stake will automatically be locked up for a fixed duration (set by governance) and
/// automatically renewed at expiration.
/// 5. At any point, if the validator operator wants to update the consensus key or network/fullnode addresses, they can
/// call stake::rotate_consensus_key and stake::update_network_and_fullnode_addresses. Similar to changes to stake, the
/// changes to consensus key/network/fullnode addresses are only effective in the next epoch.
/// 6. Validator can request to unlock their stake at any time. However, their stake will only become withdrawable when
/// their current lockup expires. This can be at most as long as the fixed lockup duration.
/// 7. After exiting, the validator can either explicitly leave the validator set by calling stake::leave_validator_set
/// or if their stake drops below the min required, they would get removed at the end of the epoch.
/// 8. Validator can always rejoin the validator set by going through steps 2-3 again.
/// 9. An owner can always switch operators by calling stake::set_operator.
/// 10. An owner can always switch designated voter by calling stake::set_designated_voter.
module aptos_framework::stake {
use std::error;
use std::features;
use std::option::{Self, Option};
use std::signer;
use std::vector;
use aptos_std::bls12381;
use aptos_std::math64::min;
use aptos_std::table::{Self, Table};
use aptos_framework::aptos_coin::AptosCoin;
use aptos_framework::account;
use aptos_framework::coin::{Self, Coin, MintCapability};
use aptos_framework::event::{Self, EventHandle};
use aptos_framework::timestamp;
use aptos_framework::system_addresses;
use aptos_framework::staking_config::{Self, StakingConfig, StakingRewardsConfig};
use aptos_framework::chain_status;
friend aptos_framework::block;
friend aptos_framework::genesis;
friend aptos_framework::reconfiguration;
friend aptos_framework::transaction_fee;
/// Validator Config not published.
const EVALIDATOR_CONFIG: u64 = 1;
/// Not enough stake to join validator set.
const ESTAKE_TOO_LOW: u64 = 2;
/// Too much stake to join validator set.
const ESTAKE_TOO_HIGH: u64 = 3;
/// Account is already a validator or pending validator.
const EALREADY_ACTIVE_VALIDATOR: u64 = 4;
/// Account is not a validator.
const ENOT_VALIDATOR: u64 = 5;
/// Can't remove last validator.
const ELAST_VALIDATOR: u64 = 6;
/// Total stake exceeds maximum allowed.
const ESTAKE_EXCEEDS_MAX: u64 = 7;
/// Account is already registered as a validator candidate.
const EALREADY_REGISTERED: u64 = 8;
/// Account does not have the right operator capability.
const ENOT_OPERATOR: u64 = 9;
/// Validators cannot join or leave post genesis on this test network.
const ENO_POST_GENESIS_VALIDATOR_SET_CHANGE_ALLOWED: u64 = 10;
/// Invalid consensus public key
const EINVALID_PUBLIC_KEY: u64 = 11;
/// Validator set exceeds the limit
const EVALIDATOR_SET_TOO_LARGE: u64 = 12;
/// Voting power increase has exceeded the limit for this current epoch.
const EVOTING_POWER_INCREASE_EXCEEDS_LIMIT: u64 = 13;
/// Stake pool does not exist at the provided pool address.
const ESTAKE_POOL_DOES_NOT_EXIST: u64 = 14;
/// Owner capability does not exist at the provided account.
const EOWNER_CAP_NOT_FOUND: u64 = 15;
/// An account cannot own more than one owner capability.
const EOWNER_CAP_ALREADY_EXISTS: u64 = 16;
/// Validator is not defined in the ACL of entities allowed to be validators
const EINELIGIBLE_VALIDATOR: u64 = 17;
/// Cannot update stake pool's lockup to earlier than current lockup.
const EINVALID_LOCKUP: u64 = 18;
/// Table to store collected transaction fees for each validator already exists.
const EFEES_TABLE_ALREADY_EXISTS: u64 = 19;
/// Validator status enum. We can switch to proper enum later once Move supports it.
const VALIDATOR_STATUS_PENDING_ACTIVE: u64 = 1;
const VALIDATOR_STATUS_ACTIVE: u64 = 2;
const VALIDATOR_STATUS_PENDING_INACTIVE: u64 = 3;
const VALIDATOR_STATUS_INACTIVE: u64 = 4;
/// Limit the maximum size to u16::max, it's the current limit of the bitvec
/// https://github.com/aptos-labs/aptos-core/blob/main/crates/aptos-bitvec/src/lib.rs#L20
const MAX_VALIDATOR_SET_SIZE: u64 = 65536;
/// Limit the maximum value of `rewards_rate` in order to avoid any arithmetic overflow.
const MAX_REWARDS_RATE: u64 = 1000000;
const MAX_U64: u128 = 18446744073709551615;
/// Capability that represents ownership and can be used to control the validator and the associated stake pool.
/// Having this be separate from the signer for the account that the validator resources are hosted at allows
/// modules to have control over a validator.
struct OwnerCapability has key, store {
pool_address: address,
}
/// Each validator has a separate StakePool resource and can provide a stake.
/// Changes in stake for an active validator:
/// 1. If a validator calls add_stake, the newly added stake is moved to pending_active.
/// 2. If validator calls unlock, their stake is moved to pending_inactive.
/// 2. When the next epoch starts, any pending_inactive stake is moved to inactive and can be withdrawn.
/// Any pending_active stake is moved to active and adds to the validator's voting power.
///
/// Changes in stake for an inactive validator:
/// 1. If a validator calls add_stake, the newly added stake is moved directly to active.
/// 2. If validator calls unlock, their stake is moved directly to inactive.
/// 3. When the next epoch starts, the validator can be activated if their active stake is more than the minimum.
struct StakePool has key {
// active stake
active: Coin<AptosCoin>,
// inactive stake, can be withdrawn
inactive: Coin<AptosCoin>,
// pending activation for next epoch
pending_active: Coin<AptosCoin>,
// pending deactivation for next epoch
pending_inactive: Coin<AptosCoin>,
locked_until_secs: u64,
// Track the current operator of the validator node.
// This allows the operator to be different from the original account and allow for separation of
// the validator operations and ownership.
// Only the account holding OwnerCapability of the staking pool can update this.
operator_address: address,
// Track the current vote delegator of the staking pool.
// Only the account holding OwnerCapability of the staking pool can update this.
delegated_voter: address,
// The events emitted for the entire StakePool's lifecycle.
initialize_validator_events: EventHandle<RegisterValidatorCandidateEvent>,
set_operator_events: EventHandle<SetOperatorEvent>,
add_stake_events: EventHandle<AddStakeEvent>,
reactivate_stake_events: EventHandle<ReactivateStakeEvent>,
rotate_consensus_key_events: EventHandle<RotateConsensusKeyEvent>,
update_network_and_fullnode_addresses_events: EventHandle<UpdateNetworkAndFullnodeAddressesEvent>,
increase_lockup_events: EventHandle<IncreaseLockupEvent>,
join_validator_set_events: EventHandle<JoinValidatorSetEvent>,
distribute_rewards_events: EventHandle<DistributeRewardsEvent>,
unlock_stake_events: EventHandle<UnlockStakeEvent>,
withdraw_stake_events: EventHandle<WithdrawStakeEvent>,
leave_validator_set_events: EventHandle<LeaveValidatorSetEvent>,
}
/// Validator info stored in validator address.
struct ValidatorConfig has key, copy, store, drop {
consensus_pubkey: vector<u8>,
network_addresses: vector<u8>,
// to make it compatible with previous definition, remove later
fullnode_addresses: vector<u8>,
// Index in the active set if the validator corresponding to this stake pool is active.
validator_index: u64,
}
/// Consensus information per validator, stored in ValidatorSet.
struct ValidatorInfo has copy, store, drop {
addr: address,
voting_power: u64,
config: ValidatorConfig,
}
/// Full ValidatorSet, stored in @aptos_framework.
/// 1. join_validator_set adds to pending_active queue.
/// 2. leave_valdiator_set moves from active to pending_inactive queue.
/// 3. on_new_epoch processes two pending queues and refresh ValidatorInfo from the owner's address.
struct ValidatorSet has key {
consensus_scheme: u8,
// Active validators for the current epoch.
active_validators: vector<ValidatorInfo>,
// Pending validators to leave in next epoch (still active).
pending_inactive: vector<ValidatorInfo>,
// Pending validators to join in next epoch.
pending_active: vector<ValidatorInfo>,
// Current total voting power.
total_voting_power: u128,
// Total voting power waiting to join in the next epoch.
total_joining_power: u128,
}
/// AptosCoin capabilities, set during genesis and stored in @CoreResource account.
/// This allows the Stake module to mint rewards to stakers.
struct AptosCoinCapabilities has key {
mint_cap: MintCapability<AptosCoin>,
}
struct IndividualValidatorPerformance has store, drop {
successful_proposals: u64,
failed_proposals: u64,
}
struct ValidatorPerformance has key {
validators: vector<IndividualValidatorPerformance>,
}
struct RegisterValidatorCandidateEvent has drop, store {
pool_address: address,
}
struct SetOperatorEvent has drop, store {
pool_address: address,
old_operator: address,
new_operator: address,
}
struct AddStakeEvent has drop, store {
pool_address: address,
amount_added: u64,
}
struct ReactivateStakeEvent has drop, store {
pool_address: address,
amount: u64,
}
struct RotateConsensusKeyEvent has drop, store {
pool_address: address,
old_consensus_pubkey: vector<u8>,
new_consensus_pubkey: vector<u8>,
}
struct UpdateNetworkAndFullnodeAddressesEvent has drop, store {
pool_address: address,
old_network_addresses: vector<u8>,
new_network_addresses: vector<u8>,
old_fullnode_addresses: vector<u8>,
new_fullnode_addresses: vector<u8>,
}
struct IncreaseLockupEvent has drop, store {
pool_address: address,
old_locked_until_secs: u64,
new_locked_until_secs: u64,
}
struct JoinValidatorSetEvent has drop, store {
pool_address: address,
}
struct DistributeRewardsEvent has drop, store {
pool_address: address,
rewards_amount: u64,
}
struct UnlockStakeEvent has drop, store {
pool_address: address,
amount_unlocked: u64,
}
struct WithdrawStakeEvent has drop, store {
pool_address: address,
amount_withdrawn: u64,
}
struct LeaveValidatorSetEvent has drop, store {
pool_address: address,
}
/// Stores transaction fees assigned to validators. All fees are distributed to validators
/// at the end of the epoch.
struct ValidatorFees has key {
fees_table: Table<address, Coin<AptosCoin>>,
}
/// Initializes the resource storing information about collected transaction fees per validator.
/// Used by `transaction_fee.move` to initialize fee collection and distribution.
public(friend) fun initialize_validator_fees(aptos_framework: &signer) {
system_addresses::assert_aptos_framework(aptos_framework);
assert!(
!exists<ValidatorFees>(@aptos_framework),
error::already_exists(EFEES_TABLE_ALREADY_EXISTS)
);
move_to(aptos_framework, ValidatorFees { fees_table: table::new() });
}
/// Stores the transaction fee collected to the specified validator address.
public(friend) fun add_transaction_fee(validator_addr: address, fee: Coin<AptosCoin>) acquires ValidatorFees {
let fees_table = &mut borrow_global_mut<ValidatorFees>(@aptos_framework).fees_table;
if (table::contains(fees_table, validator_addr)) {
let collected_fee = table::borrow_mut(fees_table, validator_addr);
coin::merge(collected_fee, fee);
} else {
table::add(fees_table, validator_addr, fee);
}
}
#[view]
/// Return the lockup expiration of the stake pool at `pool_address`.
/// This will throw an error if there's no stake pool at `pool_address`.
public fun get_lockup_secs(pool_address: address): u64 acquires StakePool {
assert_stake_pool_exists(pool_address);
borrow_global<StakePool>(pool_address).locked_until_secs
}
#[view]
/// Return the remaining lockup of the stake pool at `pool_address`.
/// This will throw an error if there's no stake pool at `pool_address`.
public fun get_remaining_lockup_secs(pool_address: address): u64 acquires StakePool {
assert_stake_pool_exists(pool_address);
let lockup_time = borrow_global<StakePool>(pool_address).locked_until_secs;
if (lockup_time <= timestamp::now_seconds()) {
0
} else {
lockup_time - timestamp::now_seconds()
}
}
#[view]
/// Return the different stake amounts for `pool_address` (whether the validator is active or not).
/// The returned amounts are for (active, inactive, pending_active, pending_inactive) stake respectively.
public fun get_stake(pool_address: address): (u64, u64, u64, u64) acquires StakePool {
assert_stake_pool_exists(pool_address);
let stake_pool = borrow_global<StakePool>(pool_address);
(
coin::value(&stake_pool.active),
coin::value(&stake_pool.inactive),
coin::value(&stake_pool.pending_active),
coin::value(&stake_pool.pending_inactive),
)
}
#[view]
/// Returns the validator's state.
public fun get_validator_state(pool_address: address): u64 acquires ValidatorSet {
let validator_set = borrow_global<ValidatorSet>(@aptos_framework);
if (option::is_some(&find_validator(&validator_set.pending_active, pool_address))) {
VALIDATOR_STATUS_PENDING_ACTIVE
} else if (option::is_some(&find_validator(&validator_set.active_validators, pool_address))) {
VALIDATOR_STATUS_ACTIVE
} else if (option::is_some(&find_validator(&validator_set.pending_inactive, pool_address))) {
VALIDATOR_STATUS_PENDING_INACTIVE
} else {
VALIDATOR_STATUS_INACTIVE
}
}
#[view]
/// Return the voting power of the validator in the current epoch.
/// This is the same as the validator's total active and pending_inactive stake.
public fun get_current_epoch_voting_power(pool_address: address): u64 acquires StakePool, ValidatorSet {
assert_stake_pool_exists(pool_address);
let validator_state = get_validator_state(pool_address);
// Both active and pending inactive validators can still vote in the current epoch.
if (validator_state == VALIDATOR_STATUS_ACTIVE || validator_state == VALIDATOR_STATUS_PENDING_INACTIVE) {
let active_stake = coin::value(&borrow_global<StakePool>(pool_address).active);
let pending_inactive_stake = coin::value(&borrow_global<StakePool>(pool_address).pending_inactive);
active_stake + pending_inactive_stake
} else {
0
}
}
#[view]
/// Return the delegated voter of the validator at `pool_address`.
public fun get_delegated_voter(pool_address: address): address acquires StakePool {
assert_stake_pool_exists(pool_address);
borrow_global<StakePool>(pool_address).delegated_voter
}
#[view]
/// Return the operator of the validator at `pool_address`.
public fun get_operator(pool_address: address): address acquires StakePool {
assert_stake_pool_exists(pool_address);
borrow_global<StakePool>(pool_address).operator_address
}
/// Return the pool address in `owner_cap`.
public fun get_owned_pool_address(owner_cap: &OwnerCapability): address {
owner_cap.pool_address
}
#[view]
/// Return the validator index for `pool_address`.
public fun get_validator_index(pool_address: address): u64 acquires ValidatorConfig {
assert_stake_pool_exists(pool_address);
borrow_global<ValidatorConfig>(pool_address).validator_index
}
#[view]
/// Return the number of successful and failed proposals for the proposal at the given validator index.
public fun get_current_epoch_proposal_counts(validator_index: u64): (u64, u64) acquires ValidatorPerformance {
let validator_performances = &borrow_global<ValidatorPerformance>(@aptos_framework).validators;
let validator_performance = vector::borrow(validator_performances, validator_index);
(validator_performance.successful_proposals, validator_performance.failed_proposals)
}
#[view]
/// Return the validator's config.
public fun get_validator_config(pool_address: address): (vector<u8>, vector<u8>, vector<u8>) acquires ValidatorConfig {
assert_stake_pool_exists(pool_address);
let validator_config = borrow_global<ValidatorConfig>(pool_address);
(validator_config.consensus_pubkey, validator_config.network_addresses, validator_config.fullnode_addresses)
}
#[view]
public fun stake_pool_exists(addr: address): bool {
exists<StakePool>(addr)
}
/// Initialize validator set to the core resource account.
public(friend) fun initialize(aptos_framework: &signer) {
system_addresses::assert_aptos_framework(aptos_framework);
move_to(aptos_framework, ValidatorSet {
consensus_scheme: 0,
active_validators: vector::empty(),
pending_active: vector::empty(),
pending_inactive: vector::empty(),
total_voting_power: 0,
total_joining_power: 0,
});
move_to(aptos_framework, ValidatorPerformance {
validators: vector::empty(),
});
}
/// This is only called during Genesis, which is where MintCapability<AptosCoin> can be created.
/// Beyond genesis, no one can create AptosCoin mint/burn capabilities.
public(friend) fun store_aptos_coin_mint_cap(aptos_framework: &signer, mint_cap: MintCapability<AptosCoin>) {
system_addresses::assert_aptos_framework(aptos_framework);
move_to(aptos_framework, AptosCoinCapabilities { mint_cap })
}
/// Allow on chain governance to remove validators from the validator set.
public fun remove_validators(
aptos_framework: &signer,
validators: &vector<address>,
) acquires ValidatorSet {
system_addresses::assert_aptos_framework(aptos_framework);
let validator_set = borrow_global_mut<ValidatorSet>(@aptos_framework);
let active_validators = &mut validator_set.active_validators;
let pending_inactive = &mut validator_set.pending_inactive;
let len = vector::length(validators);
let i = 0;
// Remove each validator from the validator set.
while ({
spec {
invariant spec_validators_are_initialized(active_validators);
invariant spec_validator_indices_are_valid(active_validators);
invariant spec_validators_are_initialized(pending_inactive);
invariant spec_validator_indices_are_valid(pending_inactive);
};
i < len
}) {
let validator = *vector::borrow(validators, i);
let validator_index = find_validator(active_validators, validator);
if (option::is_some(&validator_index)) {
let validator_info = vector::swap_remove(active_validators, *option::borrow(&validator_index));
vector::push_back(pending_inactive, validator_info);
};
i = i + 1;
};
}
/// Initialize the validator account and give ownership to the signing account
/// except it leaves the ValidatorConfig to be set by another entity.
/// Note: this triggers setting the operator and owner, set it to the account's address
/// to set later.
public entry fun initialize_stake_owner(
owner: &signer,
initial_stake_amount: u64,
operator: address,
voter: address,
) acquires AllowedValidators, OwnerCapability, StakePool, ValidatorSet {
initialize_owner(owner);
move_to(owner, ValidatorConfig {
consensus_pubkey: vector::empty(),
network_addresses: vector::empty(),
fullnode_addresses: vector::empty(),
validator_index: 0,
});
if (initial_stake_amount > 0) {
add_stake(owner, initial_stake_amount);
};
let account_address = signer::address_of(owner);
if (account_address != operator) {
set_operator(owner, operator)
};
if (account_address != voter) {
set_delegated_voter(owner, voter)
};
}
/// Initialize the validator account and give ownership to the signing account.
public entry fun initialize_validator(
account: &signer,
consensus_pubkey: vector<u8>,
proof_of_possession: vector<u8>,
network_addresses: vector<u8>,
fullnode_addresses: vector<u8>,
) acquires AllowedValidators {
// Checks the public key has a valid proof-of-possession to prevent rogue-key attacks.
let pubkey_from_pop = &mut bls12381::public_key_from_bytes_with_pop(
consensus_pubkey,
&proof_of_possession_from_bytes(proof_of_possession)
);
assert!(option::is_some(pubkey_from_pop), error::invalid_argument(EINVALID_PUBLIC_KEY));
initialize_owner(account);
move_to(account, ValidatorConfig {
consensus_pubkey,
network_addresses,
fullnode_addresses,
validator_index: 0,
});
}
fun initialize_owner(owner: &signer) acquires AllowedValidators {
let owner_address = signer::address_of(owner);
assert!(is_allowed(owner_address), error::not_found(EINELIGIBLE_VALIDATOR));
assert!(!stake_pool_exists(owner_address), error::already_exists(EALREADY_REGISTERED));
move_to(owner, StakePool {
active: coin::zero<AptosCoin>(),
pending_active: coin::zero<AptosCoin>(),
pending_inactive: coin::zero<AptosCoin>(),
inactive: coin::zero<AptosCoin>(),
locked_until_secs: 0,
operator_address: owner_address,
delegated_voter: owner_address,
// Events.
initialize_validator_events: account::new_event_handle<RegisterValidatorCandidateEvent>(owner),
set_operator_events: account::new_event_handle<SetOperatorEvent>(owner),
add_stake_events: account::new_event_handle<AddStakeEvent>(owner),
reactivate_stake_events: account::new_event_handle<ReactivateStakeEvent>(owner),
rotate_consensus_key_events: account::new_event_handle<RotateConsensusKeyEvent>(owner),
update_network_and_fullnode_addresses_events: account::new_event_handle<UpdateNetworkAndFullnodeAddressesEvent>(owner),
increase_lockup_events: account::new_event_handle<IncreaseLockupEvent>(owner),
join_validator_set_events: account::new_event_handle<JoinValidatorSetEvent>(owner),
distribute_rewards_events: account::new_event_handle<DistributeRewardsEvent>(owner),
unlock_stake_events: account::new_event_handle<UnlockStakeEvent>(owner),
withdraw_stake_events: account::new_event_handle<WithdrawStakeEvent>(owner),
leave_validator_set_events: account::new_event_handle<LeaveValidatorSetEvent>(owner),
});
move_to(owner, OwnerCapability { pool_address: owner_address });
}
/// Extract and return owner capability from the signing account.
public fun extract_owner_cap(owner: &signer): OwnerCapability acquires OwnerCapability {
let owner_address = signer::address_of(owner);
assert_owner_cap_exists(owner_address);
move_from<OwnerCapability>(owner_address)
}
/// Deposit `owner_cap` into `account`. This requires `account` to not already have owernship of another
/// staking pool.
public fun deposit_owner_cap(owner: &signer, owner_cap: OwnerCapability) {
assert!(!exists<OwnerCapability>(signer::address_of(owner)), error::not_found(EOWNER_CAP_ALREADY_EXISTS));
move_to(owner, owner_cap);
}
/// Destroy `owner_cap`.
public fun destroy_owner_cap(owner_cap: OwnerCapability) {
let OwnerCapability { pool_address: _ } = owner_cap;
}
/// Allows an owner to change the operator of the stake pool.
public entry fun set_operator(owner: &signer, new_operator: address) acquires OwnerCapability, StakePool {
let owner_address = signer::address_of(owner);
assert_owner_cap_exists(owner_address);
let ownership_cap = borrow_global<OwnerCapability>(owner_address);
set_operator_with_cap(ownership_cap, new_operator);
}
/// Allows an account with ownership capability to change the operator of the stake pool.
public fun set_operator_with_cap(owner_cap: &OwnerCapability, new_operator: address) acquires StakePool {
let pool_address = owner_cap.pool_address;
assert_stake_pool_exists(pool_address);
let stake_pool = borrow_global_mut<StakePool>(pool_address);
let old_operator = stake_pool.operator_address;
stake_pool.operator_address = new_operator;
event::emit_event(
&mut stake_pool.set_operator_events,
SetOperatorEvent {
pool_address,
old_operator,
new_operator,
},
);
}
/// Allows an owner to change the delegated voter of the stake pool.
public entry fun set_delegated_voter(owner: &signer, new_voter: address) acquires OwnerCapability, StakePool {
let owner_address = signer::address_of(owner);
assert_owner_cap_exists(owner_address);
let ownership_cap = borrow_global<OwnerCapability>(owner_address);
set_delegated_voter_with_cap(ownership_cap, new_voter);
}
/// Allows an owner to change the delegated voter of the stake pool.
public fun set_delegated_voter_with_cap(owner_cap: &OwnerCapability, new_voter: address) acquires StakePool {
let pool_address = owner_cap.pool_address;
assert_stake_pool_exists(pool_address);
let stake_pool = borrow_global_mut<StakePool>(pool_address);
stake_pool.delegated_voter = new_voter;
}
/// Add `amount` of coins from the `account` owning the StakePool.
public entry fun add_stake(owner: &signer, amount: u64) acquires OwnerCapability, StakePool, ValidatorSet {
let owner_address = signer::address_of(owner);
assert_owner_cap_exists(owner_address);
let ownership_cap = borrow_global<OwnerCapability>(owner_address);
add_stake_with_cap(ownership_cap, coin::withdraw<AptosCoin>(owner, amount));
}
/// Add `coins` into `pool_address`. this requires the corresponding `owner_cap` to be passed in.
public fun add_stake_with_cap(owner_cap: &OwnerCapability, coins: Coin<AptosCoin>) acquires StakePool, ValidatorSet {
let pool_address = owner_cap.pool_address;
assert_stake_pool_exists(pool_address);
let amount = coin::value(&coins);
if (amount == 0) {
coin::destroy_zero(coins);
return
};
// Only track and validate voting power increase for active and pending_active validator.
// Pending_inactive validator will be removed from the validator set in the next epoch.
// Inactive validator's total stake will be tracked when they join the validator set.
let validator_set = borrow_global_mut<ValidatorSet>(@aptos_framework);
// Search directly rather using get_validator_state to save on unnecessary loops.
if (option::is_some(&find_validator(&validator_set.active_validators, pool_address)) ||
option::is_some(&find_validator(&validator_set.pending_active, pool_address))) {
update_voting_power_increase(amount);
};
// Add to pending_active if it's a current validator because the stake is not counted until the next epoch.
// Otherwise, the delegation can be added to active directly as the validator is also activated in the epoch.
let stake_pool = borrow_global_mut<StakePool>(pool_address);
if (is_current_epoch_validator(pool_address)) {
coin::merge<AptosCoin>(&mut stake_pool.pending_active, coins);
} else {
coin::merge<AptosCoin>(&mut stake_pool.active, coins);
};
let (_, maximum_stake) = staking_config::get_required_stake(&staking_config::get());
let voting_power = get_next_epoch_voting_power(stake_pool);
assert!(voting_power <= maximum_stake, error::invalid_argument(ESTAKE_EXCEEDS_MAX));
event::emit_event(
&mut stake_pool.add_stake_events,
AddStakeEvent {
pool_address,
amount_added: amount,
},
);
}
/// Move `amount` of coins from pending_inactive to active.
public entry fun reactivate_stake(owner: &signer, amount: u64) acquires OwnerCapability, StakePool {
let owner_address = signer::address_of(owner);
assert_owner_cap_exists(owner_address);
let ownership_cap = borrow_global<OwnerCapability>(owner_address);
reactivate_stake_with_cap(ownership_cap, amount);
}
public fun reactivate_stake_with_cap(owner_cap: &OwnerCapability, amount: u64) acquires StakePool {
let pool_address = owner_cap.pool_address;
assert_stake_pool_exists(pool_address);
// Cap the amount to reactivate by the amount in pending_inactive.
let stake_pool = borrow_global_mut<StakePool>(pool_address);
let total_pending_inactive = coin::value(&stake_pool.pending_inactive);
amount = min(amount, total_pending_inactive);
// Since this does not count as a voting power change (pending inactive still counts as voting power in the
// current epoch), stake can be immediately moved from pending inactive to active.
// We also don't need to check voting power increase as there's none.
let reactivated_coins = coin::extract(&mut stake_pool.pending_inactive, amount);
coin::merge(&mut stake_pool.active, reactivated_coins);
event::emit_event(
&mut stake_pool.reactivate_stake_events,
ReactivateStakeEvent {
pool_address,
amount,
},
);
}
/// Rotate the consensus key of the validator, it'll take effect in next epoch.
public entry fun rotate_consensus_key(
operator: &signer,
pool_address: address,
new_consensus_pubkey: vector<u8>,
proof_of_possession: vector<u8>,
) acquires StakePool, ValidatorConfig {
assert_stake_pool_exists(pool_address);
let stake_pool = borrow_global_mut<StakePool>(pool_address);
assert!(signer::address_of(operator) == stake_pool.operator_address, error::unauthenticated(ENOT_OPERATOR));
assert!(exists<ValidatorConfig>(pool_address), error::not_found(EVALIDATOR_CONFIG));
let validator_info = borrow_global_mut<ValidatorConfig>(pool_address);
let old_consensus_pubkey = validator_info.consensus_pubkey;
// Checks the public key has a valid proof-of-possession to prevent rogue-key attacks.
let pubkey_from_pop = &mut bls12381::public_key_from_bytes_with_pop(
new_consensus_pubkey,
&proof_of_possession_from_bytes(proof_of_possession)
);
assert!(option::is_some(pubkey_from_pop), error::invalid_argument(EINVALID_PUBLIC_KEY));
validator_info.consensus_pubkey = new_consensus_pubkey;
event::emit_event(
&mut stake_pool.rotate_consensus_key_events,
RotateConsensusKeyEvent {
pool_address,
old_consensus_pubkey,
new_consensus_pubkey,
},
);
}
/// Update the network and full node addresses of the validator. This only takes effect in the next epoch.
public entry fun update_network_and_fullnode_addresses(
operator: &signer,
pool_address: address,
new_network_addresses: vector<u8>,
new_fullnode_addresses: vector<u8>,
) acquires StakePool, ValidatorConfig {
assert_stake_pool_exists(pool_address);
let stake_pool = borrow_global_mut<StakePool>(pool_address);
assert!(signer::address_of(operator) == stake_pool.operator_address, error::unauthenticated(ENOT_OPERATOR));
assert!(exists<ValidatorConfig>(pool_address), error::not_found(EVALIDATOR_CONFIG));
let validator_info = borrow_global_mut<ValidatorConfig>(pool_address);
let old_network_addresses = validator_info.network_addresses;
validator_info.network_addresses = new_network_addresses;
let old_fullnode_addresses = validator_info.fullnode_addresses;
validator_info.fullnode_addresses = new_fullnode_addresses;
event::emit_event(
&mut stake_pool.update_network_and_fullnode_addresses_events,
UpdateNetworkAndFullnodeAddressesEvent {
pool_address,
old_network_addresses,
new_network_addresses,
old_fullnode_addresses,
new_fullnode_addresses,
},
);
}
/// Similar to increase_lockup_with_cap but will use ownership capability from the signing account.
public entry fun increase_lockup(owner: &signer) acquires OwnerCapability, StakePool {
let owner_address = signer::address_of(owner);
assert_owner_cap_exists(owner_address);
let ownership_cap = borrow_global<OwnerCapability>(owner_address);
increase_lockup_with_cap(ownership_cap);
}
/// Unlock from active delegation, it's moved to pending_inactive if locked_until_secs < current_time or
/// directly inactive if it's not from an active validator.
public fun increase_lockup_with_cap(owner_cap: &OwnerCapability) acquires StakePool {
let pool_address = owner_cap.pool_address;
assert_stake_pool_exists(pool_address);
let config = staking_config::get();
let stake_pool = borrow_global_mut<StakePool>(pool_address);
let old_locked_until_secs = stake_pool.locked_until_secs;
let new_locked_until_secs = timestamp::now_seconds() + staking_config::get_recurring_lockup_duration(&config);
assert!(old_locked_until_secs < new_locked_until_secs, error::invalid_argument(EINVALID_LOCKUP));
stake_pool.locked_until_secs = new_locked_until_secs;
event::emit_event(
&mut stake_pool.increase_lockup_events,
IncreaseLockupEvent {
pool_address,
old_locked_until_secs,
new_locked_until_secs,
},
);
}
/// This can only called by the operator of the validator/staking pool.
public entry fun join_validator_set(
operator: &signer,
pool_address: address
) acquires StakePool, ValidatorConfig, ValidatorSet {
assert!(
staking_config::get_allow_validator_set_change(&staking_config::get()),
error::invalid_argument(ENO_POST_GENESIS_VALIDATOR_SET_CHANGE_ALLOWED),
);
join_validator_set_internal(operator, pool_address);
}
/// Request to have `pool_address` join the validator set. Can only be called after calling `initialize_validator`.
/// If the validator has the required stake (more than minimum and less than maximum allowed), they will be
/// added to the pending_active queue. All validators in this queue will be added to the active set when the next
/// epoch starts (eligibility will be rechecked).
///
/// This internal version can only be called by the Genesis module during Genesis.
public(friend) fun join_validator_set_internal(
operator: &signer,
pool_address: address
) acquires StakePool, ValidatorConfig, ValidatorSet {
assert_stake_pool_exists(pool_address);
let stake_pool = borrow_global_mut<StakePool>(pool_address);
assert!(signer::address_of(operator) == stake_pool.operator_address, error::unauthenticated(ENOT_OPERATOR));
assert!(
get_validator_state(pool_address) == VALIDATOR_STATUS_INACTIVE,
error::invalid_state(EALREADY_ACTIVE_VALIDATOR),
);
let config = staking_config::get();
let (minimum_stake, maximum_stake) = staking_config::get_required_stake(&config);
let voting_power = get_next_epoch_voting_power(stake_pool);
assert!(voting_power >= minimum_stake, error::invalid_argument(ESTAKE_TOO_LOW));
assert!(voting_power <= maximum_stake, error::invalid_argument(ESTAKE_TOO_HIGH));
// Track and validate voting power increase.
update_voting_power_increase(voting_power);
// Add validator to pending_active, to be activated in the next epoch.
let validator_config = borrow_global_mut<ValidatorConfig>(pool_address);
assert!(!vector::is_empty(&validator_config.consensus_pubkey), error::invalid_argument(EINVALID_PUBLIC_KEY));
// Validate the current validator set size has not exceeded the limit.
let validator_set = borrow_global_mut<ValidatorSet>(@aptos_framework);
vector::push_back(&mut validator_set.pending_active, generate_validator_info(pool_address, stake_pool, *validator_config));
let validator_set_size = vector::length(&validator_set.active_validators) + vector::length(&validator_set.pending_active);
assert!(validator_set_size <= MAX_VALIDATOR_SET_SIZE, error::invalid_argument(EVALIDATOR_SET_TOO_LARGE));
event::emit_event(
&mut stake_pool.join_validator_set_events,
JoinValidatorSetEvent { pool_address },
);
}
/// Similar to unlock_with_cap but will use ownership capability from the signing account.
public entry fun unlock(owner: &signer, amount: u64) acquires OwnerCapability, StakePool {
let owner_address = signer::address_of(owner);
assert_owner_cap_exists(owner_address);
let ownership_cap = borrow_global<OwnerCapability>(owner_address);
unlock_with_cap(amount, ownership_cap);
}
/// Unlock `amount` from the active stake. Only possible if the lockup has expired.
public fun unlock_with_cap(amount: u64, owner_cap: &OwnerCapability) acquires StakePool {
// Short-circuit if amount to unlock is 0 so we don't emit events.
if (amount == 0) {
return
};
// Unlocked coins are moved to pending_inactive. When the current lockup cycle expires, they will be moved into
// inactive in the earliest possible epoch transition.
let pool_address = owner_cap.pool_address;
assert_stake_pool_exists(pool_address);
let stake_pool = borrow_global_mut<StakePool>(pool_address);
// Cap amount to unlock by maximum active stake.
let amount = min(amount, coin::value(&stake_pool.active));
let unlocked_stake = coin::extract(&mut stake_pool.active, amount);
coin::merge<AptosCoin>(&mut stake_pool.pending_inactive, unlocked_stake);
event::emit_event(
&mut stake_pool.unlock_stake_events,
UnlockStakeEvent {
pool_address,
amount_unlocked: amount,
},
);
}
/// Withdraw from `account`'s inactive stake.
public entry fun withdraw(
owner: &signer,
withdraw_amount: u64
) acquires OwnerCapability, StakePool, ValidatorSet {
let owner_address = signer::address_of(owner);
assert_owner_cap_exists(owner_address);
let ownership_cap = borrow_global<OwnerCapability>(owner_address);
let coins = withdraw_with_cap(ownership_cap, withdraw_amount);
coin::deposit<AptosCoin>(owner_address, coins);
}
/// Withdraw from `pool_address`'s inactive stake with the corresponding `owner_cap`.
public fun withdraw_with_cap(
owner_cap: &OwnerCapability,
withdraw_amount: u64
): Coin<AptosCoin> acquires StakePool, ValidatorSet {
let pool_address = owner_cap.pool_address;
assert_stake_pool_exists(pool_address);
let stake_pool = borrow_global_mut<StakePool>(pool_address);
// There's an edge case where a validator unlocks their stake and leaves the validator set before
// the stake is fully unlocked (the current lockup cycle has not expired yet).
// This can leave their stake stuck in pending_inactive even after the current lockup cycle expires.
if (get_validator_state(pool_address) == VALIDATOR_STATUS_INACTIVE &&
timestamp::now_seconds() >= stake_pool.locked_until_secs) {
let pending_inactive_stake = coin::extract_all(&mut stake_pool.pending_inactive);
coin::merge(&mut stake_pool.inactive, pending_inactive_stake);
};
// Cap withdraw amount by total ianctive coins.
withdraw_amount = min(withdraw_amount, coin::value(&stake_pool.inactive));
if (withdraw_amount == 0) return coin::zero<AptosCoin>();
event::emit_event(
&mut stake_pool.withdraw_stake_events,
WithdrawStakeEvent {
pool_address,
amount_withdrawn: withdraw_amount,
},
);
coin::extract(&mut stake_pool.inactive, withdraw_amount)
}
/// Request to have `pool_address` leave the validator set. The validator is only actually removed from the set when
/// the next epoch starts.
/// The last validator in the set cannot leave. This is an edge case that should never happen as long as the network
/// is still operational.
///
/// Can only be called by the operator of the validator/staking pool.
public entry fun leave_validator_set(
operator: &signer,
pool_address: address
) acquires StakePool, ValidatorSet {
let config = staking_config::get();
assert!(
staking_config::get_allow_validator_set_change(&config),
error::invalid_argument(ENO_POST_GENESIS_VALIDATOR_SET_CHANGE_ALLOWED),
);
assert_stake_pool_exists(pool_address);
let stake_pool = borrow_global_mut<StakePool>(pool_address);
// Account has to be the operator.
assert!(signer::address_of(operator) == stake_pool.operator_address, error::unauthenticated(ENOT_OPERATOR));
let validator_set = borrow_global_mut<ValidatorSet>(@aptos_framework);
// If the validator is still pending_active, directly kick the validator out.
let maybe_pending_active_index = find_validator(&validator_set.pending_active, pool_address);
if (option::is_some(&maybe_pending_active_index)) {
vector::swap_remove(
&mut validator_set.pending_active, option::extract(&mut maybe_pending_active_index));
// Decrease the voting power increase as the pending validator's voting power was added when they requested
// to join. Now that they changed their mind, their voting power should not affect the joining limit of this
// epoch.
let validator_stake = (get_next_epoch_voting_power(stake_pool) as u128);
// total_joining_power should be larger than validator_stake but just in case there has been a small
// rounding error somewhere that can lead to an underflow, we still want to allow this transaction to
// succeed.
if (validator_set.total_joining_power > validator_stake) {
validator_set.total_joining_power = validator_set.total_joining_power - validator_stake;
} else {
validator_set.total_joining_power = 0;
};
} else {
// Validate that the validator is already part of the validator set.
let maybe_active_index = find_validator(&validator_set.active_validators, pool_address);
assert!(option::is_some(&maybe_active_index), error::invalid_state(ENOT_VALIDATOR));
let validator_info = vector::swap_remove(
&mut validator_set.active_validators, option::extract(&mut maybe_active_index));
assert!(vector::length(&validator_set.active_validators) > 0, error::invalid_state(ELAST_VALIDATOR));
vector::push_back(&mut validator_set.pending_inactive, validator_info);
event::emit_event(
&mut stake_pool.leave_validator_set_events,
LeaveValidatorSetEvent {
pool_address,
},
);
};
}
/// Returns true if the current validator can still vote in the current epoch.
/// This includes validators that requested to leave but are still in the pending_inactive queue and will be removed
/// when the epoch starts.
public fun is_current_epoch_validator(pool_address: address): bool acquires ValidatorSet {
assert_stake_pool_exists(pool_address);
let validator_state = get_validator_state(pool_address);
validator_state == VALIDATOR_STATUS_ACTIVE || validator_state == VALIDATOR_STATUS_PENDING_INACTIVE
}
/// Update the validator performance (proposal statistics). This is only called by block::prologue().
/// This function cannot abort.
public(friend) fun update_performance_statistics(proposer_index: Option<u64>, failed_proposer_indices: vector<u64>) acquires ValidatorPerformance {
// Validator set cannot change until the end of the epoch, so the validator index in arguments should
// match with those of the validators in ValidatorPerformance resource.
let validator_perf = borrow_global_mut<ValidatorPerformance>(@aptos_framework);
let validator_len = vector::length(&validator_perf.validators);
// proposer_index is an option because it can be missing (for NilBlocks)
if (option::is_some(&proposer_index)) {
let cur_proposer_index = option::extract(&mut proposer_index);
// Here, and in all other vector::borrow, skip any validator indices that are out of bounds,
// this ensures that this function doesn't abort if there are out of bounds errors.
if (cur_proposer_index < validator_len) {
let validator = vector::borrow_mut(&mut validator_perf.validators, cur_proposer_index);
spec {