diff --git a/aptos-move/framework/cached-packages/src/aptos_framework_sdk_builder.rs b/aptos-move/framework/cached-packages/src/aptos_framework_sdk_builder.rs index a75cf4008e055..bb0301ff16c20 100644 --- a/aptos-move/framework/cached-packages/src/aptos_framework_sdk_builder.rs +++ b/aptos-move/framework/cached-packages/src/aptos_framework_sdk_builder.rs @@ -550,6 +550,21 @@ pub enum EntryFunctionCall { stakes: Vec, }, + /// Updates the `principle_stake` of each `delegator` in `delegators` according to the amount specified + /// at the corresponding index of `new_principle_stakes`. Also ensures that the `delegator`'s `active` stake + /// is as close to the specified amount as possible. The locked amount is subject to the vesting schedule + /// specified when the delegation pool corresponding to `pool_address` was created. + /// + /// Note that this function is only temporarily intended to work as specified above and exists to enable The + /// Supra Foundation to ensure that the allocations of all investors are subject to the terms specified in the + /// corresponding legal contracts. It will be deactivated before the validator set it opened up to external + /// validator-owners to prevent it from being abused. + PboDelegationPoolLockDelegatorsStakes { + pool_address: AccountAddress, + delegators: Vec, + new_principle_stakes: Vec, + }, + /// Move `amount` of coins from pending_inactive to active. PboDelegationPoolReactivateStake { pool_address: AccountAddress, @@ -560,6 +575,11 @@ pub enum EntryFunctionCall { /// rightful owner of `old_delegator` but has lost access and the delegator is also the rightful /// owner of `new_delegator` , Only for those stakeholders which were added at the time of creation /// This does not apply to anyone who added stake later or operator + /// + /// Note that this function is only temporarily intended to work as specified above and exists to enable The + /// Supra Foundation to ensure that the allocations of all investors are subject to the terms specified in the + /// corresponding legal contracts. It will be deactivated before the validator set it opened up to external + /// validator-owners to prevent it from being abused. PboDelegationPoolReplaceDelegator { pool_address: AccountAddress, old_delegator: AccountAddress, @@ -1510,6 +1530,15 @@ impl EntryFunctionCall { delegators, stakes, } => pbo_delegation_pool_fund_delegators_with_stake(pool_address, delegators, stakes), + PboDelegationPoolLockDelegatorsStakes { + pool_address, + delegators, + new_principle_stakes, + } => pbo_delegation_pool_lock_delegators_stakes( + pool_address, + delegators, + new_principle_stakes, + ), PboDelegationPoolReactivateStake { pool_address, amount, @@ -3307,6 +3336,38 @@ pub fn pbo_delegation_pool_fund_delegators_with_stake( )) } +/// Updates the `principle_stake` of each `delegator` in `delegators` according to the amount specified +/// at the corresponding index of `new_principle_stakes`. Also ensures that the `delegator`'s `active` stake +/// is as close to the specified amount as possible. The locked amount is subject to the vesting schedule +/// specified when the delegation pool corresponding to `pool_address` was created. +/// +/// Note that this function is only temporarily intended to work as specified above and exists to enable The +/// Supra Foundation to ensure that the allocations of all investors are subject to the terms specified in the +/// corresponding legal contracts. It will be deactivated before the validator set it opened up to external +/// validator-owners to prevent it from being abused. +pub fn pbo_delegation_pool_lock_delegators_stakes( + pool_address: AccountAddress, + delegators: Vec, + new_principle_stakes: Vec, +) -> TransactionPayload { + TransactionPayload::EntryFunction(EntryFunction::new( + ModuleId::new( + AccountAddress::new([ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, + ]), + ident_str!("pbo_delegation_pool").to_owned(), + ), + ident_str!("lock_delegators_stakes").to_owned(), + vec![], + vec![ + bcs::to_bytes(&pool_address).unwrap(), + bcs::to_bytes(&delegators).unwrap(), + bcs::to_bytes(&new_principle_stakes).unwrap(), + ], + )) +} + /// Move `amount` of coins from pending_inactive to active. pub fn pbo_delegation_pool_reactivate_stake( pool_address: AccountAddress, @@ -3333,6 +3394,11 @@ pub fn pbo_delegation_pool_reactivate_stake( /// rightful owner of `old_delegator` but has lost access and the delegator is also the rightful /// owner of `new_delegator` , Only for those stakeholders which were added at the time of creation /// This does not apply to anyone who added stake later or operator +/// +/// Note that this function is only temporarily intended to work as specified above and exists to enable The +/// Supra Foundation to ensure that the allocations of all investors are subject to the terms specified in the +/// corresponding legal contracts. It will be deactivated before the validator set it opened up to external +/// validator-owners to prevent it from being abused. pub fn pbo_delegation_pool_replace_delegator( pool_address: AccountAddress, old_delegator: AccountAddress, @@ -5974,6 +6040,20 @@ mod decoder { } } + pub fn pbo_delegation_pool_lock_delegators_stakes( + payload: &TransactionPayload, + ) -> Option { + if let TransactionPayload::EntryFunction(script) = payload { + Some(EntryFunctionCall::PboDelegationPoolLockDelegatorsStakes { + pool_address: bcs::from_bytes(script.args().get(0)?).ok()?, + delegators: bcs::from_bytes(script.args().get(1)?).ok()?, + new_principle_stakes: bcs::from_bytes(script.args().get(2)?).ok()?, + }) + } else { + None + } + } + pub fn pbo_delegation_pool_reactivate_stake( payload: &TransactionPayload, ) -> Option { @@ -7330,6 +7410,10 @@ static SCRIPT_FUNCTION_DECODER_MAP: once_cell::sync::Lazy + + + + +## Struct `UnlockScheduleApplied` + + + +
#[event]
+struct UnlockScheduleApplied has drop, store
+
+ + + +
+Fields + + +
+
+pool_address: address +
+
+ +
+
+delegator: address +
+
+ +
+
+amount: u64 +
+
+ +
+
+ +
@@ -1361,6 +1412,16 @@ Requested amount too high, the balance would fall below principle stake after un + + +Balance is not enough. + + +
const EBALANCE_NOT_SUFFICIENT: u64 = 39;
+
+ + + Coin value is not the same with principle stake. @@ -1459,6 +1520,27 @@ The voter does not have sufficient stake to create a proposal. + + +Thrown by lock_delegators_stakes when a given delegator has less than the specified +amount of stake available in the specified stake pool. + + +
const EINSUFFICIENT_STAKE_TO_LOCK: u64 = 40;
+
+ + + + + +Minimum amount of coins to be unlocked. + + +
const EMINIMUM_UNLOCK_AMOUNT: u64 = 38;
+
+ + + @@ -1762,8 +1844,8 @@ Return whether a delegation pool has already enabled partial govnernance voting.
public fun partial_governance_voting_enabled(pool_address: address): bool {
-    exists<GovernanceRecords>(pool_address) && stake::get_delegated_voter(pool_address) ==
-         pool_address
+    exists<GovernanceRecords>(pool_address)
+        && stake::get_delegated_voter(pool_address) == pool_address
 }
 
@@ -1815,9 +1897,12 @@ Return whether the commission percentage for the next lockup cycle is effective. Implementation -
public fun is_next_commission_percentage_effective(pool_address: address): bool acquires NextCommissionPercentage {
-    exists<NextCommissionPercentage>(pool_address) && timestamp::now_seconds() >= borrow_global<
-        NextCommissionPercentage>(pool_address).effective_after_secs
+
public fun is_next_commission_percentage_effective(
+    pool_address: address
+): bool acquires NextCommissionPercentage {
+    exists<NextCommissionPercentage>(pool_address)
+        && timestamp::now_seconds()
+            >= borrow_global<NextCommissionPercentage>(pool_address).effective_after_secs
 }
 
@@ -1842,7 +1927,9 @@ Return the operator commission percentage set on the delegation pool pool_ Implementation -
public fun operator_commission_percentage(pool_address: address): u64 acquires DelegationPool, NextCommissionPercentage {
+
public fun operator_commission_percentage(
+    pool_address: address
+): u64 acquires DelegationPool, NextCommissionPercentage {
     assert_delegation_pool_exists(pool_address);
     if (is_next_commission_percentage_effective(pool_address)) {
         operator_commission_percentage_next_lockup_cycle(pool_address)
@@ -1873,7 +1960,9 @@ Return the operator commission percentage for the next lockup cycle.
 Implementation
 
 
-
public fun operator_commission_percentage_next_lockup_cycle(pool_address: address): u64 acquires DelegationPool, NextCommissionPercentage {
+
public fun operator_commission_percentage_next_lockup_cycle(
+    pool_address: address
+): u64 acquires DelegationPool, NextCommissionPercentage {
     assert_delegation_pool_exists(pool_address);
     if (exists<NextCommissionPercentage>(pool_address)) {
         borrow_global<NextCommissionPercentage>(pool_address).commission_percentage_next_lockup_cycle
@@ -1906,7 +1995,9 @@ Return the number of delegators owning active stake within pool_addresspublic fun shareholders_count_active_pool(pool_address: address): u64 acquires DelegationPool {
     assert_delegation_pool_exists(pool_address);
-    pool_u64::shareholders_count(&borrow_global<DelegationPool>(pool_address).active_shares)
+    pool_u64::shareholders_count(
+        &borrow_global<DelegationPool>(pool_address).active_shares
+    )
 }
 
@@ -1960,31 +2051,32 @@ some stake and the stake pool's lockup cycle has not ended, their coins are not Implementation -
public fun get_pending_withdrawal(pool_address: address, delegator_address: address)
-    : (bool, u64) acquires DelegationPool {
+
public fun get_pending_withdrawal(
+    pool_address: address, delegator_address: address
+): (bool, u64) acquires DelegationPool {
     assert_delegation_pool_exists(pool_address);
     let pool = borrow_global<DelegationPool>(pool_address);
-    let (lockup_cycle_ended, _, pending_inactive, _, commission_pending_inactive) = calculate_stake_pool_drift(
-        pool
-    );
+    let (lockup_cycle_ended, _, pending_inactive, _, commission_pending_inactive) =
+        calculate_stake_pool_drift(pool);
 
-    let (withdrawal_exists, withdrawal_olc) = pending_withdrawal_exists(pool,
-        delegator_address);
+    let (withdrawal_exists, withdrawal_olc) =
+        pending_withdrawal_exists(pool, delegator_address);
     if (!withdrawal_exists) {
         // if no pending withdrawal, there is neither inactive nor pending_inactive stake
-        (false, 0) } else {
+        (false, 0)
+    } else {
         // delegator has either inactive or pending_inactive stake due to automatic withdrawals
         let inactive_shares = table::borrow(&pool.inactive_shares, withdrawal_olc);
         if (withdrawal_olc.index < pool.observed_lockup_cycle.index) {
             // if withdrawal's lockup cycle ended on delegation pool then it is inactive
             (true, pool_u64::balance(inactive_shares, delegator_address))
-        }
-        else {
+        } else {
             pending_inactive = pool_u64::shares_to_amount_with_total_coins(
                 inactive_shares,
                 pool_u64::shares(inactive_shares, delegator_address),
                 // exclude operator pending_inactive rewards not converted to shares yet
-                pending_inactive - commission_pending_inactive);
+                pending_inactive - commission_pending_inactive
+            );
             // if withdrawal's lockup cycle ended ONLY on stake pool then it is also inactive
             (lockup_cycle_ended, pending_inactive)
         }
@@ -2014,39 +2106,43 @@ in each of its individual states: (active,inactive,Implementation
 
 
-
public fun get_stake(pool_address: address, delegator_address: address): (u64, u64, u64) acquires DelegationPool, BeneficiaryForOperator {
+
public fun get_stake(
+    pool_address: address, delegator_address: address
+): (u64, u64, u64) acquires DelegationPool, BeneficiaryForOperator {
     assert_delegation_pool_exists(pool_address);
     let pool = borrow_global<DelegationPool>(pool_address);
     let (lockup_cycle_ended, active, _, commission_active, commission_pending_inactive) =
-         calculate_stake_pool_drift(pool);
+        calculate_stake_pool_drift(pool);
 
     let total_active_shares = pool_u64::total_shares(&pool.active_shares);
-    let delegator_active_shares = pool_u64::shares(&pool.active_shares,
-        delegator_address);
+    let delegator_active_shares =
+        pool_u64::shares(&pool.active_shares, delegator_address);
 
     let (_, _, pending_active, _) = stake::get_stake(pool_address);
     if (pending_active == 0) {
         // zero `pending_active` stake indicates that either there are no `add_stake` fees or
         // previous epoch has ended and should identify shares owning these fees as released
-        total_active_shares = total_active_shares - pool_u64::shares(&pool.active_shares,
-            NULL_SHAREHOLDER);
+        total_active_shares = total_active_shares
+            - pool_u64::shares(&pool.active_shares, NULL_SHAREHOLDER);
         if (delegator_address == NULL_SHAREHOLDER) {
             delegator_active_shares = 0
         }
     };
-    active = pool_u64::shares_to_amount_with_total_stats(&pool.active_shares,
+    active = pool_u64::shares_to_amount_with_total_stats(
+        &pool.active_shares,
         delegator_active_shares,
         // exclude operator active rewards not converted to shares yet
-        active - commission_active, total_active_shares);
+        active - commission_active,
+        total_active_shares
+    );
 
     // get state and stake (0 if there is none) of the pending withdrawal
-    let (withdrawal_inactive, withdrawal_stake) = get_pending_withdrawal(pool_address,
-        delegator_address);
+    let (withdrawal_inactive, withdrawal_stake) =
+        get_pending_withdrawal(pool_address, delegator_address);
     // report non-active stakes accordingly to the state of the pending withdrawal
-    let (inactive, pending_inactive) = if (withdrawal_inactive)
-        (withdrawal_stake, 0)
-    else
-        (0, withdrawal_stake);
+    let (inactive, pending_inactive) =
+        if (withdrawal_inactive) (withdrawal_stake, 0)
+        else (0, withdrawal_stake);
 
     // should also include commission rewards in case of the operator account
     // operator rewards are actually used to buy shares which is introducing
@@ -2094,19 +2190,21 @@ extracted-fee = (amount - extracted-fee) * reward-rate% * (100% - operator-commi
 Implementation
 
 
-
public fun get_add_stake_fee(pool_address: address, amount: u64): u64 acquires DelegationPool, NextCommissionPercentage {
+
public fun get_add_stake_fee(
+    pool_address: address, amount: u64
+): u64 acquires DelegationPool, NextCommissionPercentage {
     if (stake::is_current_epoch_validator(pool_address)) {
-        let (rewards_rate, rewards_rate_denominator) = staking_config::get_reward_rate(&staking_config::get());
+        let (rewards_rate, rewards_rate_denominator) =
+            staking_config::get_reward_rate(&staking_config::get());
         if (rewards_rate_denominator > 0) {
             assert_delegation_pool_exists(pool_address);
 
-            rewards_rate = rewards_rate * (MAX_FEE - operator_commission_percentage(
-                    pool_address
-                ));
+            rewards_rate = rewards_rate
+                * (MAX_FEE - operator_commission_percentage(pool_address));
             rewards_rate_denominator = rewards_rate_denominator * MAX_FEE;
             (
-                (((amount as u128) * (rewards_rate as u128)) / ((rewards_rate as u128)
-                            + (rewards_rate_denominator as u128))) as u64
+                (((amount as u128) * (rewards_rate as u128))
+                    / ((rewards_rate as u128) + (rewards_rate_denominator as u128))) as u64
             )
         } else { 0 }
     } else { 0 }
@@ -2137,8 +2235,8 @@ the validator had gone inactive before its lockup expired.
 
 
 
public fun can_withdraw_pending_inactive(pool_address: address): bool {
-    stake::get_validator_state(pool_address) == VALIDATOR_STATUS_INACTIVE && timestamp::now_seconds()
-        >= stake::get_lockup_secs(pool_address)
+    stake::get_validator_state(pool_address) == VALIDATOR_STATUS_INACTIVE
+        && timestamp::now_seconds() >= stake::get_lockup_secs(pool_address)
 }
 
@@ -2164,16 +2262,17 @@ latest state. Implementation -
public fun calculate_and_update_voter_total_voting_power(pool_address: address, voter: address)
-    : u64 acquires DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage {
+
public fun calculate_and_update_voter_total_voting_power(
+    pool_address: address, voter: address
+): u64 acquires DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage {
     assert_partial_governance_voting_enabled(pool_address);
     // Delegation pool need to be synced to explain rewards(which could change the coin amount) and
     // commission(which could cause share transfer).
     synchronize_delegation_pool(pool_address);
     let pool = borrow_global<DelegationPool>(pool_address);
     let governance_records = borrow_global_mut<GovernanceRecords>(pool_address);
-    let latest_delegated_votes = update_and_borrow_mut_delegated_votes(pool,
-        governance_records, voter);
+    let latest_delegated_votes =
+        update_and_borrow_mut_delegated_votes(pool, governance_records, voter);
     calculate_total_voting_power(pool, latest_delegated_votes)
 }
 
@@ -2204,11 +2303,11 @@ latest state. pool_address: address, delegator_address: address ): address acquires DelegationPool, GovernanceRecords { assert_partial_governance_voting_enabled(pool_address); - calculate_and_update_delegator_voter_internal(borrow_global<DelegationPool>( - pool_address - ), + calculate_and_update_delegator_voter_internal( + borrow_global<DelegationPool>(pool_address), borrow_global_mut<GovernanceRecords>(pool_address), - delegator_address) + delegator_address + ) }
@@ -2270,6 +2369,61 @@ Return the minimum remaining time in seconds for commission change, which is one + + + + +## Function `initialize_delegation_pool_with_amount` + +Initialize a delegation pool without actual coin but withdraw from the owner's account. + + +
public fun initialize_delegation_pool_with_amount(owner: &signer, multisig_admin: option::Option<address>, amount: u64, operator_commission_percentage: u64, delegation_pool_creation_seed: vector<u8>, delegator_address: vector<address>, principle_stake: vector<u64>, unlock_numerators: vector<u64>, unlock_denominator: u64, unlock_start_time: u64, unlock_duration: u64)
+
+ + + +
+Implementation + + +
public fun initialize_delegation_pool_with_amount(
+    owner: &signer,
+    multisig_admin: option::Option<address>,
+    amount: u64,
+    operator_commission_percentage: u64,
+    delegation_pool_creation_seed: vector<u8>,
+    delegator_address: vector<address>,
+    principle_stake: vector<u64>,
+    unlock_numerators: vector<u64>,
+    unlock_denominator: u64,
+    unlock_start_time: u64,
+    unlock_duration: u64
+) acquires DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage {
+    assert!(
+        coin::balance<SupraCoin>(signer::address_of(owner)) >= amount,
+        error::invalid_argument(EBALANCE_NOT_SUFFICIENT)
+    );
+    let coin = coin::withdraw<SupraCoin>(owner, amount);
+
+    initialize_delegation_pool(
+        owner,
+        multisig_admin,
+        operator_commission_percentage,
+        delegation_pool_creation_seed,
+        delegator_address,
+        principle_stake,
+        coin,
+        unlock_numerators,
+        unlock_denominator,
+        unlock_start_time,
+        unlock_duration
+    )
+}
+
+ + +
@@ -2302,67 +2456,93 @@ Ownership over setting the operator/voter is granted to owner who h unlock_numerators: vector<u64>, unlock_denominator: u64, unlock_start_time: u64, - unlock_duration: u64, + unlock_duration: u64 ) acquires DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage { //if there is an admin, it must be a multisig if (option::is_some<address>(&multisig_admin)) { // `ms_admin` is guaranteed to be NOT `@0x0` here let ms_admin = option::get_with_default<address>(&multisig_admin, @0x0); - assert!(ms_admin != @0x0, error::invalid_argument(EADMIN_ADDRESS_CANNOT_BE_ZERO)); - assert!(multisig_account::num_signatures_required(ms_admin) >= 2, - EADMIN_NOT_MULTISIG); + assert!( + ms_admin != @0x0, + error::invalid_argument(EADMIN_ADDRESS_CANNOT_BE_ZERO) + ); + assert!( + multisig_account::num_signatures_required(ms_admin) >= 2, + EADMIN_NOT_MULTISIG + ); }; // fail if the length of delegator_address and principle_stake is not the same - assert!(vector::length(&delegator_address) == vector::length(&principle_stake), - error::invalid_argument(EVECTOR_LENGTH_NOT_SAME)); + assert!( + vector::length(&delegator_address) == vector::length(&principle_stake), + error::invalid_argument(EVECTOR_LENGTH_NOT_SAME) + ); //Delegation pool must be enabled - assert!(features::delegation_pools_enabled(), - error::invalid_state(EDELEGATION_POOLS_DISABLED)); - //Unlock start time can not be in the past - assert!(unlock_start_time >= timestamp::now_seconds(), - error::invalid_argument(ESTARTUP_TIME_IN_PAST)); + assert!( + features::delegation_pools_enabled(), + error::invalid_state(EDELEGATION_POOLS_DISABLED) + ); //Unlock duration can not be zero assert!(unlock_duration > 0, error::invalid_argument(EPERIOD_DURATION_IS_ZERO)); //Fraction denominator can not be zero assert!(unlock_denominator != 0, error::invalid_argument(EDENOMINATOR_IS_ZERO)); //Fraction numerators can not be empty - assert!(vector::length(&unlock_numerators) > 0, - error::invalid_argument(EEMPTY_UNLOCK_SCHEDULE)); + assert!( + vector::length(&unlock_numerators) > 0, + error::invalid_argument(EEMPTY_UNLOCK_SCHEDULE) + ); //Fraction numerators can not be zero - assert!(!vector::any(&unlock_numerators, |e| { *e == 0 }), - error::invalid_argument(ESCHEDULE_WITH_ZERO_FRACTION)); + assert!( + !vector::any(&unlock_numerators, |e| { *e == 0 }), + error::invalid_argument(ESCHEDULE_WITH_ZERO_FRACTION) + ); let sum = vector::foldr(unlock_numerators, 0, |e, a| { e + a }); //Sum of numerators can not be greater than denominators - assert!(sum <= unlock_denominator, - error::invalid_argument(ENUMERATORS_GRATER_THAN_DENOMINATOR)); + assert!( + sum <= unlock_denominator, + error::invalid_argument(ENUMERATORS_GRATER_THAN_DENOMINATOR) + ); let owner_address = signer::address_of(owner); - assert!(!owner_cap_exists(owner_address), - error::already_exists(EOWNER_CAP_ALREADY_EXISTS)); - assert!(operator_commission_percentage <= MAX_FEE, - error::invalid_argument(EINVALID_COMMISSION_PERCENTAGE)); + assert!( + !owner_cap_exists(owner_address), + error::already_exists(EOWNER_CAP_ALREADY_EXISTS) + ); + assert!( + operator_commission_percentage <= MAX_FEE, + error::invalid_argument(EINVALID_COMMISSION_PERCENTAGE) + ); let sum = vector::fold(principle_stake, 0, |a, e| { a + e }); // fail if the value of coin and the sum of principle_stake is not the same - assert!(coin::value(&coin) == sum, - error::invalid_state(ECOIN_VALUE_NOT_SAME_AS_PRINCIPAL_STAKE)); + assert!( + coin::value(&coin) == sum, + error::invalid_state(ECOIN_VALUE_NOT_SAME_AS_PRINCIPAL_STAKE) + ); // generate a seed to be used to create the resource account hosting the delegation pool let seed = create_resource_account_seed(delegation_pool_creation_seed); - let (stake_pool_signer, stake_pool_signer_cap) = account::create_resource_account(owner, seed); + let (stake_pool_signer, stake_pool_signer_cap) = + account::create_resource_account(owner, seed); coin::register<SupraCoin>(&stake_pool_signer); // stake_pool_signer will be owner of the stake pool and have its `stake::OwnerCapability` let pool_address = signer::address_of(&stake_pool_signer); - stake::initialize_stake_owner(&stake_pool_signer, 0, owner_address, owner_address); + stake::initialize_stake_owner( + &stake_pool_signer, + 0, + owner_address, + owner_address + ); coin::deposit(pool_address, coin); let inactive_shares = table::new<ObservedLockupCycle, pool_u64::Pool>(); - table::add(&mut inactive_shares, + table::add( + &mut inactive_shares, olc_with_index(0), - pool_u64::create_with_scaling_factor(SHARES_SCALING_FACTOR)); + pool_u64::create_with_scaling_factor(SHARES_SCALING_FACTOR) + ); let delegator_address_copy = copy delegator_address; let principle_stake_copy = copy principle_stake; @@ -2377,14 +2557,19 @@ Ownership over setting the operator/voter is granted to owner who h //Create unlock schedule let schedule = vector::empty(); - vector::for_each_ref(&unlock_numerators, + vector::for_each_ref( + &unlock_numerators, |e| { - let fraction = fixed_point64::create_from_rational((*e as u128), - (unlock_denominator as u128)); + let fraction = + fixed_point64::create_from_rational( + (*e as u128), (unlock_denominator as u128) + ); vector::push_back(&mut schedule, fraction); - }); + } + ); - move_to(&stake_pool_signer, + move_to( + &stake_pool_signer, DelegationPool { multisig_admin: multisig_admin, active_shares: pool_u64::create_with_scaling_factor(SHARES_SCALING_FACTOR), @@ -2399,15 +2584,27 @@ Ownership over setting the operator/voter is granted to owner who h start_timestamp_secs: unlock_start_time, period_duration: unlock_duration, last_unlock_period: 0, - cumulative_unlocked_fraction: fixed_point64::create_from_rational(0, 1), + cumulative_unlocked_fraction: fixed_point64::create_from_rational( + 0, 1 + ) }, principle_stake: principle_stake_table, - add_stake_events: account::new_event_handle<AddStakeEvent>(&stake_pool_signer), - reactivate_stake_events: account::new_event_handle<ReactivateStakeEvent>(&stake_pool_signer), - unlock_stake_events: account::new_event_handle<UnlockStakeEvent>(&stake_pool_signer), - withdraw_stake_events: account::new_event_handle<WithdrawStakeEvent>(&stake_pool_signer), - distribute_commission_events: account::new_event_handle<DistributeCommissionEvent>(&stake_pool_signer), - }); + add_stake_events: account::new_event_handle<AddStakeEvent>( + &stake_pool_signer + ), + reactivate_stake_events: account::new_event_handle<ReactivateStakeEvent>( + &stake_pool_signer + ), + unlock_stake_events: account::new_event_handle<UnlockStakeEvent>( + &stake_pool_signer + ), + withdraw_stake_events: account::new_event_handle<WithdrawStakeEvent>( + &stake_pool_signer + ), + distribute_commission_events: account::new_event_handle< + DistributeCommissionEvent>(&stake_pool_signer) + } + ); // save delegation pool ownership and resource account address (inner stake pool address) on `owner` move_to(owner, DelegationPoolOwnership { pool_address }); @@ -2420,12 +2617,16 @@ Ownership over setting the operator/voter is granted to owner who h }; let (active_stake, _, _, _) = stake::get_stake(pool_address); // fail if coin in StakePool.active does not match with the balance in active_shares pool. - assert!(active_stake == pool_u64::total_coins(&borrow_global<DelegationPool>( - pool_address - ).active_shares), - error::invalid_state(EACTIVE_COIN_VALUE_NOT_SAME_STAKE_DELEGATION_POOL)); + assert!( + active_stake + == pool_u64::total_coins( + &borrow_global<DelegationPool>(pool_address).active_shares + ), + error::invalid_state(EACTIVE_COIN_VALUE_NOT_SAME_STAKE_DELEGATION_POOL) + ); // All delegation pool enable partial governace voting by default once the feature flag is enabled. - if (features::partial_governance_voting_enabled() && features::delegation_pool_partial_governance_voting_enabled()) { + if (features::partial_governance_voting_enabled() + && features::delegation_pool_partial_governance_voting_enabled()) { enable_partial_governance_voting(pool_address); } } @@ -2433,6 +2634,139 @@ Ownership over setting the operator/voter is granted to owner who h + + + + +## Function `fund_delegators_with_locked_stake` + + + +
public entry fun fund_delegators_with_locked_stake(funder: &signer, pool_address: address, delegators: vector<address>, stakes: vector<u64>)
+
+ + + +
+Implementation + + +
public entry fun fund_delegators_with_locked_stake(
+    funder: &signer,
+    pool_address: address,
+    delegators: vector<address>,
+    stakes: vector<u64>
+) acquires DelegationPool, BeneficiaryForOperator, GovernanceRecords, NextCommissionPercentage {
+    {
+        assert!(
+            is_admin(signer::address_of(funder), pool_address),
+            error::permission_denied(ENOT_AUTHORIZED)
+        );
+    };
+    let principle_stake_table =
+        &mut (borrow_global_mut<DelegationPool>(pool_address).principle_stake);
+
+    vector::zip_reverse(
+        delegators,
+        stakes,
+        |delegator, stake| {
+            // Ignore if stake to be added is `0`
+            if (stake > 0) {
+                // Compute the actual stake that would be added, `principle_stake` has to be
+                // populated in the table accordingly
+                if (table::contains(principle_stake_table, delegator)) {
+                    let stake_amount =
+                        table::borrow_mut(principle_stake_table, delegator);
+                    *stake_amount = *stake_amount + stake;
+                } else {
+                    table::add(principle_stake_table, delegator, stake);
+                };
+
+                // Record the details of the lockup event. Note that only the newly locked
+                // amount is reported and not the total locked amount.
+                event::emit(
+                    UnlockScheduleApplied {
+                        pool_address,
+                        delegator,
+                        amount: stake
+                    }
+                );
+            }
+        }
+    );
+
+    fund_delegators_with_stake(funder, pool_address, delegators, stakes);
+}
+
+ + + +
+ + + +## Function `fund_delegators_with_stake` + + + +
public entry fun fund_delegators_with_stake(funder: &signer, pool_address: address, delegators: vector<address>, stakes: vector<u64>)
+
+ + + +
+Implementation + + +
public entry fun fund_delegators_with_stake(
+    funder: &signer,
+    pool_address: address,
+    delegators: vector<address>,
+    stakes: vector<u64>
+) acquires DelegationPool, BeneficiaryForOperator, GovernanceRecords, NextCommissionPercentage {
+    //length equality check is performed by `zip_reverse`
+    vector::zip_reverse(
+        delegators,
+        stakes,
+        |delegator, stake| {
+            fund_delegator_stake(funder, pool_address, delegator, stake);
+        }
+    );
+}
+
+ + + +
+ + + +## Function `is_admin` + + + +
#[view]
+public fun is_admin(user_addr: address, pool_address: address): bool
+
+ + + +
+Implementation + + +
public fun is_admin(user_addr: address, pool_address: address): bool acquires DelegationPool {
+    let option_multisig = get_admin(pool_address);
+    if (!option::is_some(&option_multisig)) {
+        return false
+    } else {
+        user_addr == *option::borrow(&option_multisig)
+    }
+}
+
+ + +
@@ -2480,7 +2814,9 @@ Return the beneficiary address of the operator.
public fun beneficiary_for_operator(operator: address): address acquires BeneficiaryForOperator {
     if (exists<BeneficiaryForOperator>(operator)) {
         return borrow_global<BeneficiaryForOperator>(operator).beneficiary_for_operator
-    } else { operator }
+    } else {
+        operator
+    }
 }
 
@@ -2505,11 +2841,17 @@ THe existing voter will be replaced. The function is permissionless. Implementation -
public entry fun enable_partial_governance_voting(pool_address: address,) acquires DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage {
-    assert!(features::partial_governance_voting_enabled(),
-        error::invalid_state(EDISABLED_FUNCTION));
-    assert!(features::delegation_pool_partial_governance_voting_enabled(),
-        error::invalid_state(EDISABLED_FUNCTION));
+
public entry fun enable_partial_governance_voting(
+    pool_address: address
+) acquires DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage {
+    assert!(
+        features::partial_governance_voting_enabled(),
+        error::invalid_state(EDISABLED_FUNCTION)
+    );
+    assert!(
+        features::delegation_pool_partial_governance_voting_enabled(),
+        error::invalid_state(EDISABLED_FUNCTION)
+    );
     assert_delegation_pool_exists(pool_address);
     // synchronize delegation and stake pools before any user operation.
     synchronize_delegation_pool(pool_address);
@@ -2518,19 +2860,25 @@ THe existing voter will be replaced. The function is permissionless.
     let stake_pool_signer = retrieve_stake_pool_owner(delegation_pool);
     // delegated_voter is managed by the stake pool itself, which signer capability is managed by DelegationPool.
     // So voting power of this stake pool can only be used through this module.
-    stake::set_delegated_voter(&stake_pool_signer,
-        signer::address_of(&stake_pool_signer));
+    stake::set_delegated_voter(
+        &stake_pool_signer, signer::address_of(&stake_pool_signer)
+    );
 
-    move_to(&stake_pool_signer,
+    move_to(
+        &stake_pool_signer,
         GovernanceRecords {
             votes: smart_table::new(),
             votes_per_proposal: smart_table::new(),
             vote_delegation: smart_table::new(),
             delegated_votes: smart_table::new(),
             vote_events: account::new_event_handle<VoteEvent>(&stake_pool_signer),
-            create_proposal_events: account::new_event_handle<CreateProposalEvent>(&stake_pool_signer),
-            delegate_voting_power_events: account::new_event_handle<DelegateVotingPowerEvent>(&stake_pool_signer),
-        });
+            create_proposal_events: account::new_event_handle<CreateProposalEvent>(
+                &stake_pool_signer
+            ),
+            delegate_voting_power_events: account::new_event_handle<
+                DelegateVotingPowerEvent>(&stake_pool_signer)
+        }
+    );
 }
 
@@ -2578,8 +2926,10 @@ THe existing voter will be replaced. The function is permissionless.
fun assert_delegation_pool_exists(pool_address: address) {
-    assert!(delegation_pool_exists(pool_address),
-        error::invalid_argument(EDELEGATION_POOL_DOES_NOT_EXIST));
+    assert!(
+        delegation_pool_exists(pool_address),
+        error::invalid_argument(EDELEGATION_POOL_DOES_NOT_EXIST)
+    );
 }
 
@@ -2602,10 +2952,14 @@ THe existing voter will be replaced. The function is permissionless. Implementation -
fun assert_min_active_balance(pool: &DelegationPool, delegator_address: address) {
+
fun assert_min_active_balance(
+    pool: &DelegationPool, delegator_address: address
+) {
     let balance = pool_u64::balance(&pool.active_shares, delegator_address);
-    assert!(balance >= MIN_COINS_ON_SHARES_POOL,
-        error::invalid_argument(EDELEGATOR_ACTIVE_BALANCE_TOO_LOW));
+    assert!(
+        balance >= MIN_COINS_ON_SHARES_POOL,
+        error::invalid_argument(EDELEGATOR_ACTIVE_BALANCE_TOO_LOW)
+    );
 }
 
@@ -2628,11 +2982,15 @@ THe existing voter will be replaced. The function is permissionless. Implementation -
fun assert_min_pending_inactive_balance(pool: &DelegationPool, delegator_address: address) {
-    let balance = pool_u64::balance(pending_inactive_shares_pool(pool),
-        delegator_address);
-    assert!(balance >= MIN_COINS_ON_SHARES_POOL,
-        error::invalid_argument(EDELEGATOR_PENDING_INACTIVE_BALANCE_TOO_LOW));
+
fun assert_min_pending_inactive_balance(
+    pool: &DelegationPool, delegator_address: address
+) {
+    let balance =
+        pool_u64::balance(pending_inactive_shares_pool(pool), delegator_address);
+    assert!(
+        balance >= MIN_COINS_ON_SHARES_POOL,
+        error::invalid_argument(EDELEGATOR_PENDING_INACTIVE_BALANCE_TOO_LOW)
+    );
 }
 
@@ -2657,8 +3015,10 @@ THe existing voter will be replaced. The function is permissionless.
fun assert_partial_governance_voting_enabled(pool_address: address) {
     assert_delegation_pool_exists(pool_address);
-    assert!(partial_governance_voting_enabled(pool_address),
-        error::invalid_state(EPARTIAL_GOVERNANCE_VOTING_NOT_ENABLED));
+    assert!(
+        partial_governance_voting_enabled(pool_address),
+        error::invalid_state(EPARTIAL_GOVERNANCE_VOTING_NOT_ENABLED)
+    );
 }
 
@@ -2682,11 +3042,14 @@ THe existing voter will be replaced. The function is permissionless.
fun coins_to_redeem_to_ensure_min_stake(
-    src_shares_pool: &pool_u64::Pool, shareholder: address, amount: u64,
+    src_shares_pool: &pool_u64::Pool, shareholder: address, amount: u64
 ): u64 {
     // find how many coins would be redeemed if supplying `amount`
-    let redeemed_coins = pool_u64::shares_to_amount(src_shares_pool,
-        amount_to_shares_to_redeem(src_shares_pool, shareholder, amount));
+    let redeemed_coins =
+        pool_u64::shares_to_amount(
+            src_shares_pool,
+            amount_to_shares_to_redeem(src_shares_pool, shareholder, amount)
+        );
     // if balance drops under threshold then redeem it entirely
     let src_balance = pool_u64::balance(src_shares_pool, shareholder);
     if (src_balance - redeemed_coins < MIN_COINS_ON_SHARES_POOL) {
@@ -2719,11 +3082,14 @@ THe existing voter will be replaced. The function is permissionless.
     src_shares_pool: &pool_u64::Pool,
     dst_shares_pool: &pool_u64::Pool,
     shareholder: address,
-    amount: u64,
+    amount: u64
 ): u64 {
     // find how many coins would be redeemed from source if supplying `amount`
-    let redeemed_coins = pool_u64::shares_to_amount(src_shares_pool,
-        amount_to_shares_to_redeem(src_shares_pool, shareholder, amount));
+    let redeemed_coins =
+        pool_u64::shares_to_amount(
+            src_shares_pool,
+            amount_to_shares_to_redeem(src_shares_pool, shareholder, amount)
+        );
     // if balance on destination would be less than threshold then redeem difference to threshold
     let dst_balance = pool_u64::balance(dst_shares_pool, shareholder);
     if (dst_balance + redeemed_coins < MIN_COINS_ON_SHARES_POOL) {
@@ -2806,7 +3172,9 @@ Get the active share amount of the delegator.
 Implementation
 
 
-
fun get_delegator_active_shares(pool: &DelegationPool, delegator: address): u128 {
+
fun get_delegator_active_shares(
+    pool: &DelegationPool, delegator: address
+): u128 {
     pool_u64::shares(&pool.active_shares, delegator)
 }
 
@@ -2831,7 +3199,9 @@ Get the pending inactive share amount of the delegator. Implementation -
fun get_delegator_pending_inactive_shares(pool: &DelegationPool, delegator: address): u128 {
+
fun get_delegator_pending_inactive_shares(
+    pool: &DelegationPool, delegator: address
+): u128 {
     pool_u64::shares(pending_inactive_shares_pool(pool), delegator)
 }
 
@@ -2860,7 +3230,7 @@ Get the used voting power of a voter on a proposal. governance_records: &GovernanceRecords, voter: address, proposal_id: u64 ): u64 { let votes = &governance_records.votes; - let key = VotingRecordKey { voter, proposal_id, }; + let key = VotingRecordKey { voter, proposal_id }; *smart_table::borrow_with_default(votes, key, &0) }
@@ -2885,7 +3255,9 @@ Create the seed to derive the resource account address. Implementation -
fun create_resource_account_seed(delegation_pool_creation_seed: vector<u8>,): vector<u8> {
+
fun create_resource_account_seed(
+    delegation_pool_creation_seed: vector<u8>
+): vector<u8> {
     let seed = vector::empty<u8>();
     // include module salt (before any subseeds) to avoid conflicts with other modules creating resource accounts
     vector::append(&mut seed, MODULE_SALT);
@@ -2919,7 +3291,7 @@ Borrow the mutable used voting power of a voter on a proposal.
     governance_records: &mut GovernanceRecords, voter: address, proposal_id: u64
 ): &mut u64 {
     let votes = &mut governance_records.votes;
-    let key = VotingRecordKey { proposal_id, voter, };
+    let key = VotingRecordKey { proposal_id, voter };
     smart_table::borrow_mut_with_default(votes, key, 0)
 }
 
@@ -2945,7 +3317,9 @@ Update VoteDelegation of a delegator to up-to-date then borrow_mut it.
fun update_and_borrow_mut_delegator_vote_delegation(
-    pool: &DelegationPool, governance_records: &mut GovernanceRecords, delegator: address
+    pool: &DelegationPool,
+    governance_records: &mut GovernanceRecords,
+    delegator: address
 ): &mut VoteDelegation {
     let pool_address = get_pool_address(pool);
     let locked_until_secs = stake::get_lockup_secs(pool_address);
@@ -2954,20 +3328,21 @@ Update VoteDelegation of a delegator to up-to-date then borrow_mut it.
     // By default, a delegator's delegated voter is itself.
     // TODO: recycle storage when VoteDelegation equals to default value.
     if (!smart_table::contains(vote_delegation_table, delegator)) {
-        return smart_table::borrow_mut_with_default(vote_delegation_table,
+        return smart_table::borrow_mut_with_default(
+            vote_delegation_table,
             delegator,
             VoteDelegation {
                 voter: delegator,
                 last_locked_until_secs: locked_until_secs,
-                pending_voter: delegator,
+                pending_voter: delegator
             }
         )
     };
 
     let vote_delegation = smart_table::borrow_mut(vote_delegation_table, delegator);
     // A lockup period has passed since last time `vote_delegation` was updated. Pending voter takes effect.
-    if (vote_delegation.last_locked_until_secs < locked_until_secs && vote_delegation.voter != vote_delegation
-        .pending_voter) {
+    if (vote_delegation.last_locked_until_secs < locked_until_secs
+        && vote_delegation.voter != vote_delegation.pending_voter) {
         vote_delegation.voter = vote_delegation.pending_voter;
     };
     vote_delegation
@@ -3006,13 +3381,14 @@ Update DelegatedVotes of a voter to up-to-date then borrow_mut it.
     if (!smart_table::contains(delegated_votes_per_voter, voter)) {
         let active_shares = get_delegator_active_shares(pool, voter);
         let inactive_shares = get_delegator_pending_inactive_shares(pool, voter);
-        return smart_table::borrow_mut_with_default(delegated_votes_per_voter,
+        return smart_table::borrow_mut_with_default(
+            delegated_votes_per_voter,
             voter,
             DelegatedVotes {
                 active_shares,
                 pending_inactive_shares: inactive_shares,
                 active_shares_next_lockup: active_shares,
-                last_locked_until_secs: locked_until_secs,
+                last_locked_until_secs: locked_until_secs
             }
         )
     };
@@ -3076,11 +3452,15 @@ power, which equals to the sum of the coin amounts.
 
fun calculate_total_voting_power(
     delegation_pool: &DelegationPool, latest_delegated_votes: &DelegatedVotes
 ): u64 {
-    let active_amount = pool_u64::shares_to_amount(&delegation_pool.active_shares,
-        latest_delegated_votes.active_shares);
-    let pending_inactive_amount = pool_u64::shares_to_amount(
-        pending_inactive_shares_pool(delegation_pool),
-        latest_delegated_votes.pending_inactive_shares);
+    let active_amount =
+        pool_u64::shares_to_amount(
+            &delegation_pool.active_shares, latest_delegated_votes.active_shares
+        );
+    let pending_inactive_amount =
+        pool_u64::shares_to_amount(
+            pending_inactive_shares_pool(delegation_pool),
+            latest_delegated_votes.pending_inactive_shares
+        );
     active_amount + pending_inactive_amount
 }
 
@@ -3106,10 +3486,14 @@ Update VoteDelegation of a delegator to up-to-date then return the latest voter.
fun calculate_and_update_delegator_voter_internal(
-    pool: &DelegationPool, governance_records: &mut GovernanceRecords, delegator: address
+    pool: &DelegationPool,
+    governance_records: &mut GovernanceRecords,
+    delegator: address
 ): address {
-    let vote_delegation = update_and_borrow_mut_delegator_vote_delegation(pool,
-        governance_records, delegator);
+    let vote_delegation =
+        update_and_borrow_mut_delegator_vote_delegation(
+            pool, governance_records, delegator
+        );
     vote_delegation.voter
 }
 
@@ -3137,8 +3521,8 @@ Update DelegatedVotes of a voter to up-to-date then return the total voting powe
fun calculate_and_update_delegated_votes(
     pool: &DelegationPool, governance_records: &mut GovernanceRecords, voter: address
 ): u64 {
-    let delegated_votes = update_and_borrow_mut_delegated_votes(pool, governance_records,
-        voter);
+    let delegated_votes =
+        update_and_borrow_mut_delegated_votes(pool, governance_records, voter);
     calculate_total_voting_power(pool, delegated_votes)
 }
 
@@ -3163,14 +3547,17 @@ Allows an owner to change the operator of the underlying stake pool. Implementation -
public entry fun set_operator(owner: &signer, new_operator: address) acquires DelegationPoolOwnership, DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage {
+
public entry fun set_operator(
+    owner: &signer, new_operator: address
+) acquires DelegationPoolOwnership, DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage {
     let pool_address = get_owned_pool_address(signer::address_of(owner));
     // synchronize delegation and stake pools before any user operation
     // ensure the old operator is paid its uncommitted commission rewards
     synchronize_delegation_pool(pool_address);
-    stake::set_operator(&retrieve_stake_pool_owner(borrow_global<DelegationPool>(
-                pool_address
-            )), new_operator);
+    stake::set_operator(
+        &retrieve_stake_pool_owner(borrow_global<DelegationPool>(pool_address)),
+        new_operator
+    );
 }
 
@@ -3197,27 +3584,34 @@ one for each pool. Implementation -
public entry fun set_beneficiary_for_operator(operator: &signer, new_beneficiary: address) acquires BeneficiaryForOperator {
-    assert!(features::operator_beneficiary_change_enabled(),
-        std::error::invalid_state(EOPERATOR_BENEFICIARY_CHANGE_NOT_SUPPORTED));
+
public entry fun set_beneficiary_for_operator(
+    operator: &signer, new_beneficiary: address
+) acquires BeneficiaryForOperator {
+    assert!(
+        features::operator_beneficiary_change_enabled(),
+        std::error::invalid_state(EOPERATOR_BENEFICIARY_CHANGE_NOT_SUPPORTED)
+    );
     // The beneficiay address of an operator is stored under the operator's address.
     // So, the operator does not need to be validated with respect to a staking pool.
     let operator_addr = signer::address_of(operator);
     let old_beneficiary = beneficiary_for_operator(operator_addr);
     if (exists<BeneficiaryForOperator>(operator_addr)) {
         borrow_global_mut<BeneficiaryForOperator>(operator_addr).beneficiary_for_operator =
-             new_beneficiary;
+            new_beneficiary;
     } else {
-        move_to(operator, BeneficiaryForOperator {
-                beneficiary_for_operator: new_beneficiary
-            });
+        move_to(
+            operator,
+            BeneficiaryForOperator { beneficiary_for_operator: new_beneficiary }
+        );
     };
 
-    emit(SetBeneficiaryForOperator {
+    emit(
+        SetBeneficiaryForOperator {
             operator: operator_addr,
             old_beneficiary,
-            new_beneficiary,
-        });
+            new_beneficiary
+        }
+    );
 }
 
@@ -3241,17 +3635,29 @@ Allows an owner to update the commission percentage for the operator of the unde Implementation -
public entry fun update_commission_percentage(owner: &signer, new_commission_percentage: u64) acquires DelegationPoolOwnership, DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage {
-    assert!(features::commission_change_delegation_pool_enabled(),
-        error::invalid_state(ECOMMISSION_RATE_CHANGE_NOT_SUPPORTED));
-    assert!(new_commission_percentage <= MAX_FEE,
-        error::invalid_argument(EINVALID_COMMISSION_PERCENTAGE));
+
public entry fun update_commission_percentage(
+    owner: &signer, new_commission_percentage: u64
+) acquires DelegationPoolOwnership, DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage {
+    assert!(
+        features::commission_change_delegation_pool_enabled(),
+        error::invalid_state(ECOMMISSION_RATE_CHANGE_NOT_SUPPORTED)
+    );
+    assert!(
+        new_commission_percentage <= MAX_FEE,
+        error::invalid_argument(EINVALID_COMMISSION_PERCENTAGE)
+    );
     let owner_address = signer::address_of(owner);
     let pool_address = get_owned_pool_address(owner_address);
-    assert!(operator_commission_percentage(pool_address) + MAX_COMMISSION_INCREASE >= new_commission_percentage,
-        error::invalid_argument(ETOO_LARGE_COMMISSION_INCREASE));
-    assert!(stake::get_remaining_lockup_secs(pool_address) >= min_remaining_secs_for_commission_change(),
-        error::invalid_state(ETOO_LATE_COMMISSION_CHANGE));
+    assert!(
+        operator_commission_percentage(pool_address) + MAX_COMMISSION_INCREASE
+            >= new_commission_percentage,
+        error::invalid_argument(ETOO_LARGE_COMMISSION_INCREASE)
+    );
+    assert!(
+        stake::get_remaining_lockup_secs(pool_address)
+            >= min_remaining_secs_for_commission_change(),
+        error::invalid_state(ETOO_LATE_COMMISSION_CHANGE)
+    );
 
     // synchronize delegation and stake pools before any user operation. this ensures:
     // (1) the operator is paid its uncommitted commission rewards with the old commission percentage, and
@@ -3259,28 +3665,34 @@ Allows an owner to update the commission percentage for the operator of the unde
     synchronize_delegation_pool(pool_address);
 
     if (exists<NextCommissionPercentage>(pool_address)) {
-        let commission_percentage = borrow_global_mut<NextCommissionPercentage>(
-            pool_address
-        );
+        let commission_percentage =
+            borrow_global_mut<NextCommissionPercentage>(pool_address);
         commission_percentage.commission_percentage_next_lockup_cycle = new_commission_percentage;
         commission_percentage.effective_after_secs = stake::get_lockup_secs(
             pool_address
         );
     } else {
         let delegation_pool = borrow_global<DelegationPool>(pool_address);
-        let pool_signer = account::create_signer_with_capability(&delegation_pool.stake_pool_signer_cap);
-        move_to(&pool_signer,
+        let pool_signer =
+            account::create_signer_with_capability(
+                &delegation_pool.stake_pool_signer_cap
+            );
+        move_to(
+            &pool_signer,
             NextCommissionPercentage {
                 commission_percentage_next_lockup_cycle: new_commission_percentage,
-                effective_after_secs: stake::get_lockup_secs(pool_address),
-            });
+                effective_after_secs: stake::get_lockup_secs(pool_address)
+            }
+        );
     };
 
-    event::emit(CommissionPercentageChange {
+    event::emit(
+        CommissionPercentageChange {
             pool_address,
             owner: owner_address,
-            commission_percentage_next_lockup_cycle: new_commission_percentage,
-        });
+            commission_percentage_next_lockup_cycle: new_commission_percentage
+        }
+    );
 }
 
@@ -3304,16 +3716,21 @@ Allows an owner to change the delegated voter of the underlying stake pool. Implementation -
public entry fun set_delegated_voter(owner: &signer, new_voter: address) acquires DelegationPoolOwnership, DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage {
+
public entry fun set_delegated_voter(
+    owner: &signer, new_voter: address
+) acquires DelegationPoolOwnership, DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage {
     // No one can change delegated_voter once the partial governance voting feature is enabled.
-    assert!(!features::delegation_pool_partial_governance_voting_enabled(),
-        error::invalid_state(EDEPRECATED_FUNCTION));
+    assert!(
+        !features::delegation_pool_partial_governance_voting_enabled(),
+        error::invalid_state(EDEPRECATED_FUNCTION)
+    );
     let pool_address = get_owned_pool_address(signer::address_of(owner));
     // synchronize delegation and stake pools before any user operation
     synchronize_delegation_pool(pool_address);
-    stake::set_delegated_voter(&retrieve_stake_pool_owner(borrow_global<DelegationPool>(
-                pool_address
-            )), new_voter);
+    stake::set_delegated_voter(
+        &retrieve_stake_pool_owner(borrow_global<DelegationPool>(pool_address)),
+        new_voter
+    );
 }
 
@@ -3349,35 +3766,40 @@ this change won't take effects until the next lockup period. let delegator_address = signer::address_of(delegator); let delegation_pool = borrow_global<DelegationPool>(pool_address); let governance_records = borrow_global_mut<GovernanceRecords>(pool_address); - let delegator_vote_delegation = update_and_borrow_mut_delegator_vote_delegation( - delegation_pool, governance_records, delegator_address - ); + let delegator_vote_delegation = + update_and_borrow_mut_delegator_vote_delegation( + delegation_pool, governance_records, delegator_address + ); let pending_voter: address = delegator_vote_delegation.pending_voter; // No need to update if the voter doesn't really change. if (pending_voter != new_voter) { delegator_vote_delegation.pending_voter = new_voter; - let active_shares = get_delegator_active_shares(delegation_pool, - delegator_address); + let active_shares = + get_delegator_active_shares(delegation_pool, delegator_address); // <active shares> of <pending voter of shareholder> -= <active_shares> // <active shares> of <new voter of shareholder> += <active_shares> - let pending_delegated_votes = update_and_borrow_mut_delegated_votes( - delegation_pool, governance_records, pending_voter - ); + let pending_delegated_votes = + update_and_borrow_mut_delegated_votes( + delegation_pool, governance_records, pending_voter + ); pending_delegated_votes.active_shares_next_lockup = pending_delegated_votes.active_shares_next_lockup - active_shares; - let new_delegated_votes = update_and_borrow_mut_delegated_votes(delegation_pool, - governance_records, new_voter); + let new_delegated_votes = + update_and_borrow_mut_delegated_votes( + delegation_pool, governance_records, new_voter + ); new_delegated_votes.active_shares_next_lockup = new_delegated_votes.active_shares_next_lockup + active_shares; }; - event::emit_event(&mut governance_records.delegate_voting_power_events, + event::emit_event( + &mut governance_records.delegate_voting_power_events, DelegateVotingPowerEvent { pool_address, delegator: delegator_address, - voter: new_voter, + voter: new_voter } ); } @@ -3403,7 +3825,9 @@ Add amount of coins to the delegation pool pool_addressImplementation -
fun add_stake_initialization(delegator_address: address, pool_address: address, amount: u64) acquires DelegationPool, GovernanceRecords {
+
fun add_stake_initialization(
+    delegator_address: address, pool_address: address, amount: u64
+) acquires DelegationPool, GovernanceRecords {
     // short-circuit if amount to add is 0 so no event is emitted
     if (amount == 0) { return };
 
@@ -3420,14 +3844,13 @@ Add amount of coins to the delegation pool pool_address
 
-
+
 
-## Function `add_stake`
+## Function `fund_delegator_stake`
 
-Add amount of coins to the delegation pool pool_address.
 
 
-
public entry fun add_stake(delegator: &signer, pool_address: address, amount: u64)
+
fun fund_delegator_stake(funder: &signer, pool_address: address, delegator_address: address, amount: u64)
 
@@ -3436,20 +3859,26 @@ Add amount of coins to the delegation pool pool_addressImplementation -
public entry fun add_stake(delegator: &signer, pool_address: address, amount: u64) acquires DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage {
+
fun fund_delegator_stake(
+    funder: &signer,
+    pool_address: address,
+    delegator_address: address,
+    amount: u64
+) acquires DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage {
     // short-circuit if amount to add is 0 so no event is emitted
     if (amount == 0) { return };
+    // fail unlock of less than `MIN_COINS_ON_SHARES_POOL`
+    assert!(amount >= MIN_COINS_ON_SHARES_POOL, error::invalid_argument(EMINIMUM_UNLOCK_AMOUNT));
     // synchronize delegation and stake pools before any user operation
     synchronize_delegation_pool(pool_address);
 
     // fee to be charged for adding `amount` stake on this delegation pool at this epoch
     let add_stake_fee = get_add_stake_fee(pool_address, amount);
 
+    supra_account::transfer(funder, pool_address, amount);
     let pool = borrow_global_mut<DelegationPool>(pool_address);
-    let delegator_address = signer::address_of(delegator);
 
     // stake the entire amount to the stake pool
-    supra_account::transfer(delegator, pool_address, amount);
     stake::add_stake(&retrieve_stake_pool_owner(pool), amount);
 
     // but buy shares for delegator just for the remaining amount after fee
@@ -3462,14 +3891,48 @@ Add amount of coins to the delegation pool pool_addressto appreciate all shares on the active pool atomically
     buy_in_active_shares(pool, NULL_SHAREHOLDER, add_stake_fee);
 
-    event::emit_event(&mut pool.add_stake_events,
+    event::emit_event(
+        &mut pool.add_stake_events,
         AddStakeEvent {
             pool_address,
             delegator_address,
             amount_added: amount,
-            add_stake_fee,
-        },
+            add_stake_fee
+        }
     );
+
+}
+
+ + + + + + + +## Function `add_stake` + +Add amount of coins to the delegation pool pool_address. + + +
public entry fun add_stake(delegator: &signer, pool_address: address, amount: u64)
+
+ + + +
+Implementation + + +
public entry fun add_stake(
+    delegator: &signer, pool_address: address, amount: u64
+) acquires DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage {
+    fund_delegator_stake(
+        delegator,
+        pool_address,
+        signer::address_of(delegator),
+        amount
+    )
 }
 
@@ -3493,7 +3956,9 @@ Add amount of coins to the delegation pool pool_addressfun replace_in_smart_tables<Key: copy + drop, Val>( - table: &mut SmartTable<Key, Val>, old_entry: Key, new_entry: Key + table: &mut SmartTable<Key, Val>, + old_entry: Key, + new_entry: Key ) { if (smart_table::contains(table, old_entry)) { let val = smart_table::remove(table, old_entry); @@ -3504,6 +3969,238 @@ Add amount of coins to the delegation pool pool_address + + + +## Function `authorized_reactivate_stake` + +Reactivates the pending_inactive stake of delegator. + +This function must remain private because it must only be called by an authorized entity and it is the +callers responsibility to ensure that this is true. Authorized entities currently include the delegator +itself and the multisig admin of the delegation pool, which must be controlled by The Supra Foundation. + +Note that this function is only temporarily intended to work as specified above and exists to enable The +Supra Foundation to ensure that the allocations of all investors are subject to the terms specified in the +corresponding legal contracts. It will be deactivated before the validator set it opened up to external +validator-owners to prevent it from being abused, from which time forward only the delegator will be +authorized to reactivate their own stake. + + +
fun authorized_reactivate_stake(delegator: address, pool_address: address, amount: u64)
+
+ + + +
+Implementation + + +
fun authorized_reactivate_stake(
+    delegator: address, pool_address: address, amount: u64
+) acquires DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage {
+    // short-circuit if amount to reactivate is 0 so no event is emitted
+    if (amount == 0) { return };
+    // fail unlock of less than `MIN_COINS_ON_SHARES_POOL`
+    assert!(amount >= MIN_COINS_ON_SHARES_POOL, error::invalid_argument(EMINIMUM_UNLOCK_AMOUNT));
+    // synchronize delegation and stake pools before any user operation
+    synchronize_delegation_pool(pool_address);
+
+    let pool = borrow_global_mut<DelegationPool>(pool_address);
+
+    amount = coins_to_transfer_to_ensure_min_stake(
+        pending_inactive_shares_pool(pool),
+        &pool.active_shares,
+        delegator,
+        amount
+    );
+    let observed_lockup_cycle = pool.observed_lockup_cycle;
+    amount = redeem_inactive_shares(
+        pool,
+        delegator,
+        amount,
+        observed_lockup_cycle
+    );
+
+    stake::reactivate_stake(&retrieve_stake_pool_owner(pool), amount);
+
+    buy_in_active_shares(pool, delegator, amount);
+    assert_min_active_balance(pool, delegator);
+
+    event::emit_event(
+        &mut pool.reactivate_stake_events,
+        ReactivateStakeEvent {
+            pool_address,
+            delegator_address: delegator,
+            amount_reactivated: amount
+        }
+    );
+}
+
+ + + +
+ + + +## Function `admin_withdraw` + +Withdraws the specified amount from the inactive stake belonging to the given delegator_address +to the address of the DelegationPool's multisig_admin, if available. + +Note that this function is only temporarily intended to work as specified above and exists to enable The +Supra Foundation to ensure that the allocations of all investors are subject to the terms specified in the +corresponding legal contracts. It will be deactivated before the validator set it opened up to external +validator-owners to prevent it from being abused. + + +
fun admin_withdraw(multisig_admin: &signer, pool_address: address, delegator_address: address, amount: u64)
+
+ + + +
+Implementation + + +
fun admin_withdraw(
+    multisig_admin: &signer, pool_address: address, delegator_address: address, amount: u64
+) acquires DelegationPool, GovernanceRecords {
+    // Ensure that the caller is the admin of the delegation pool.
+    {
+        assert!(
+            is_admin(signer::address_of(multisig_admin), pool_address),
+            error::permission_denied(ENOT_AUTHORIZED)
+        );
+    };
+    assert!(amount > 0, error::invalid_argument(EWITHDRAW_ZERO_STAKE));
+    withdraw_internal(
+        borrow_global_mut<DelegationPool>(pool_address),
+        delegator_address,
+        amount,
+        signer::address_of(multisig_admin),
+    );
+}
+
+ + + +
+ + + +## Function `lock_delegators_stakes` + +Updates the principle_stake of each delegator in delegators according to the amount specified +at the corresponding index of new_principle_stakes. Also ensures that the delegator's active stake +is as close to the specified amount as possible. The locked amount is subject to the vesting schedule +specified when the delegation pool corresponding to pool_address was created. + +Note that this function is only temporarily intended to work as specified above and exists to enable The +Supra Foundation to ensure that the allocations of all investors are subject to the terms specified in the +corresponding legal contracts. It will be deactivated before the validator set it opened up to external +validator-owners to prevent it from being abused. + + +
public entry fun lock_delegators_stakes(multisig_admin: &signer, pool_address: address, delegators: vector<address>, new_principle_stakes: vector<u64>)
+
+ + + +
+Implementation + + +
public entry fun lock_delegators_stakes(
+    multisig_admin: &signer,
+    pool_address: address,
+    delegators: vector<address>,
+    new_principle_stakes: vector<u64>
+) acquires DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage {
+    // Ensure that the caller is the admin of the delegation pool.
+    {
+        assert!(
+            is_admin(signer::address_of(multisig_admin), pool_address),
+            error::permission_denied(ENOT_AUTHORIZED)
+        );
+    };
+
+    // Synchronize the delegation and stake pools before any user operation.
+    synchronize_delegation_pool(pool_address);
+
+    // Ensure that each `delegator` has an `active` stake balance that is as close to
+    // `principle_stake`  as possible.
+    vector::zip_reverse(
+        delegators,
+        new_principle_stakes,
+        |delegator, principle_stake| {
+            let (active, inactive, pending_inactive) = get_stake(pool_address, delegator);
+
+            // Ensure that all stake to be locked is made `active`.
+            if (active < principle_stake) {
+                // The amount to lock can be covered by reactivating some previously unlocked stake.
+                // Only reactivate the required amount to avoid unnecessarily interfering with
+                // in-progress withdrawals.
+                let amount_to_reactivate = principle_stake - active;
+
+                // Ensure that we do not try to reactivate more than the available `pending_inactive` stake.
+                // This should be enforced by functions within `authorized_reactivate_stake`, but checking
+                // again here makes the correctness of this function easier to reason about.
+                if (amount_to_reactivate > pending_inactive) {
+                    amount_to_reactivate = pending_inactive;
+                };
+
+                if (amount_to_reactivate > MIN_COINS_ON_SHARES_POOL) {
+                    // Reactivate the required amount of `pending_inactive` stake first.
+                    authorized_reactivate_stake(delegator, pool_address, amount_to_reactivate);
+                };
+
+                let active_and_pending_inactive = active + pending_inactive;
+
+                if (active_and_pending_inactive < principle_stake) {
+                    // Need to reactivate some of the `inactive` stake.
+                    let amount_to_withdraw = principle_stake - active_and_pending_inactive;
+
+                    // Ensure that we do not try to withdraw more stake than the `inactive` stake.
+                    if (amount_to_withdraw > inactive) {
+                        amount_to_withdraw = inactive;
+                    };
+
+                    if (amount_to_withdraw > MIN_COINS_ON_SHARES_POOL) {
+                        // Withdraw the minimum required amount to the admin's address.
+                        admin_withdraw(
+                            multisig_admin,
+                            pool_address,
+                            delegator,
+                            amount_to_withdraw
+                        );
+                        // Then allocate it to the delegator again.
+                        fund_delegator_stake(multisig_admin, pool_address, delegator, amount_to_withdraw);
+                    }
+                }
+            };
+            // else: The amount to lock can be covered by the currently `active` stake.
+
+            // Update the delegator's principle stake and record the details of the lockup event.
+            let principle_stake_table =
+                &mut (borrow_global_mut<DelegationPool>(pool_address).principle_stake);
+            table::upsert(principle_stake_table, delegator, principle_stake);
+            event::emit(
+                UnlockScheduleApplied {
+                    pool_address,
+                    delegator,
+                    amount: principle_stake
+                }
+            );
+        }
+    );
+}
+
+ + +
@@ -3515,6 +4212,11 @@ rightful owner of old_delegator but has lost access and the delegat owner of new_delegator , Only for those stakeholders which were added at the time of creation This does not apply to anyone who added stake later or operator +Note that this function is only temporarily intended to work as specified above and exists to enable The +Supra Foundation to ensure that the allocations of all investors are subject to the terms specified in the +corresponding legal contracts. It will be deactivated before the validator set it opened up to external +validator-owners to prevent it from being abused. +
public entry fun replace_delegator(multisig_admin: &signer, pool_address: address, old_delegator: address, new_delegator: address)
 
@@ -3526,43 +4228,61 @@ This does not apply to anyone who added stake later or operator
public entry fun replace_delegator(
-    multisig_admin: &signer, pool_address: address, old_delegator: address, new_delegator: address
+    multisig_admin: &signer,
+    pool_address: address,
+    old_delegator: address,
+    new_delegator: address
 ) acquires DelegationPool, GovernanceRecords {
 
+    //Ensure that authorized admin is calling
+    let admin_addr = signer::address_of(multisig_admin);
+    assert!(
+        is_admin(admin_addr, pool_address),
+        error::permission_denied(ENOT_AUTHORIZED)
+    );
     //Ensure replacement address is different
-    assert!(old_delegator != new_delegator,
-        error::invalid_argument(ENEW_IS_SAME_AS_OLD_DELEGATOR));
+    assert!(
+        old_delegator != new_delegator,
+        error::invalid_argument(ENEW_IS_SAME_AS_OLD_DELEGATOR)
+    );
     //Ensure it is a valid `pool_addres`
-    assert!(exists<DelegationPool>(pool_address),
-        error::invalid_argument(EDELEGATION_POOL_DOES_NOT_EXIST));
+    assert!(
+        exists<DelegationPool>(pool_address),
+        error::invalid_argument(EDELEGATION_POOL_DOES_NOT_EXIST)
+    );
 
     let pool: &mut DelegationPool = borrow_global_mut<DelegationPool>(pool_address);
-    let admin_addr = signer::address_of(multisig_admin);
-    //Ensure that authorized admin is calling
-    assert!(admin_addr != @0x0, error::invalid_argument(EADMIN_ADDRESS_CANNOT_BE_ZERO));
-    assert!(admin_addr == option::get_with_default(&pool.multisig_admin, @0x0),
-        error::permission_denied(ENOT_AUTHORIZED));
-
     //Ensure `old_delegator` is part of original principle stakers before commencing the replacement
-    assert!(table::contains(&pool.principle_stake, old_delegator),
-        error::unavailable(EDELEGATOR_DOES_NOT_EXIST));
+    assert!(
+        table::contains(&pool.principle_stake, old_delegator),
+        error::unavailable(EDELEGATOR_DOES_NOT_EXIST)
+    );
 
     //replace in `active_shares` pool
     {
         let active_pool = &mut pool.active_shares;
         let active_shares = pool_u64::shares(active_pool, old_delegator);
-        pool_u64::transfer_shares(active_pool, old_delegator, new_delegator,
-            active_shares);
+        pool_u64::transfer_shares(
+            active_pool,
+            old_delegator,
+            new_delegator,
+            active_shares
+        );
     };
 
     //replace in `inactive_shares` pool
-    let (withdrawal_exists, withdrawal_olc) = pending_withdrawal_exists(pool,
-        old_delegator);
+    let (withdrawal_exists, withdrawal_olc) =
+        pending_withdrawal_exists(pool, old_delegator);
     if (withdrawal_exists) {
-        let inactive_pool = table::borrow_mut(&mut pool.inactive_shares, withdrawal_olc);
+        let inactive_pool =
+            table::borrow_mut(&mut pool.inactive_shares, withdrawal_olc);
         let inactive_shares = pool_u64::shares(inactive_pool, old_delegator);
-        pool_u64::transfer_shares(inactive_pool, old_delegator, new_delegator,
-            inactive_shares);
+        pool_u64::transfer_shares(
+            inactive_pool,
+            old_delegator,
+            new_delegator,
+            inactive_shares
+        );
 
         //replace in `pending_withdrawals`
         {
@@ -3577,28 +4297,39 @@ This does not apply to anyone who added stake later or operator
     {
         if (features::partial_governance_voting_enabled()) {
             let grecords = borrow_global_mut<GovernanceRecords>(pool_address);
-            replace_in_smart_tables(&mut grecords.vote_delegation, old_delegator,
-                new_delegator);
-            replace_in_smart_tables(&mut grecords.delegated_votes, old_delegator,
-                new_delegator);
+            replace_in_smart_tables(
+                &mut grecords.vote_delegation, old_delegator, new_delegator
+            );
+            replace_in_smart_tables(
+                &mut grecords.delegated_votes, old_delegator, new_delegator
+            );
             let old_keys: vector<VotingRecordKey> = vector::empty();
             let new_keys: vector<VotingRecordKey> = vector::empty();
-            smart_table::for_each_ref<VotingRecordKey, u64>(&grecords.votes,
+            smart_table::for_each_ref<VotingRecordKey, u64>(
+                &grecords.votes,
                 |key, _val| {
                     let VotingRecordKey { voter, proposal_id } = *key;
                     if (voter == old_delegator) {
-                        vector::push_back(&mut new_keys, VotingRecordKey {
+                        vector::push_back(
+                            &mut new_keys,
+                            VotingRecordKey {
                                 voter: new_delegator,
                                 proposal_id: proposal_id
-                            });
+                            }
+                        );
                         vector::push_back(&mut old_keys, *key);
                     };
 
-                });
+                }
+            );
 
-            vector::zip_ref(&old_keys, &new_keys, |old, new| {
+            vector::zip_ref(
+                &old_keys,
+                &new_keys,
+                |old, new| {
                     replace_in_smart_tables(&mut grecords.votes, *old, *new);
-                });
+                }
+            );
         }
     };
     // replace in principle_stake table
@@ -3607,8 +4338,69 @@ This does not apply to anyone who added stake later or operator
         table::add(&mut pool.principle_stake, new_delegator, val);
     };
 
-    event::emit(DelegatorReplacemendEvent { pool_address, old_delegator, new_delegator },);
+    event::emit(
+        DelegatorReplacemendEvent { pool_address, old_delegator, new_delegator }
+    );
+
+}
+
+ + + +
+ + + +## Function `is_principle_stakeholder` + + + +
#[view]
+public fun is_principle_stakeholder(delegator_addr: address, pool_addr: address): bool
+
+ + + +
+Implementation + +
public fun is_principle_stakeholder(
+    delegator_addr: address, pool_addr: address
+): bool acquires DelegationPool {
+    let pool = borrow_global<DelegationPool>(pool_addr);
+    table::contains(&pool.principle_stake, delegator_addr)
+}
+
+ + + +
+ + + +## Function `get_principle_stake` + + + +
#[view]
+public fun get_principle_stake(delegator_addr: address, pool_addr: address): u64
+
+ + + +
+Implementation + + +
public fun get_principle_stake(
+    delegator_addr: address, pool_addr: address
+): u64 acquires DelegationPool {
+    let pool = borrow_global<DelegationPool>(pool_addr);
+    if (!table::contains(&pool.principle_stake, delegator_addr)) { 0 }
+    else {
+        *table::borrow(&pool.principle_stake, delegator_addr)
+    }
 }
 
@@ -3635,23 +4427,37 @@ accurate as time passes Implementation -
public fun cached_unlockable_balance(delegator_addr: address, pool_addr: address): u64 acquires DelegationPool {
-    assert!(exists<DelegationPool>(pool_addr),
-        error::invalid_argument(EDELEGATION_POOL_DOES_NOT_EXIST));
+
public fun cached_unlockable_balance(
+    delegator_addr: address, pool_addr: address
+): u64 acquires DelegationPool {
+    assert!(
+        exists<DelegationPool>(pool_addr),
+        error::invalid_argument(EDELEGATION_POOL_DOES_NOT_EXIST)
+    );
     let pool = borrow_global<DelegationPool>(pool_addr);
-    let delegator_active_balance = pool_u64::balance(&pool.active_shares, delegator_addr);
-    let unlockable_fraction = pool.principle_unlock_schedule.cumulative_unlocked_fraction;
-    let delegator_principle_stake = *table::borrow(&pool.principle_stake, delegator_addr);
+    let delegator_active_balance =
+        pool_u64::balance(&pool.active_shares, delegator_addr);
+    let unlockable_fraction =
+        pool.principle_unlock_schedule.cumulative_unlocked_fraction;
+    let delegator_principle_stake =
+        *table::borrow(&pool.principle_stake, delegator_addr);
 
     //To avoid problem even if fraction is slightly above 1
-    let unlockable_principle_stake = (math128::min(fixed_point64::multiply_u128(
-                (delegator_principle_stake as u128), unlockable_fraction
-            ),
-            (delegator_principle_stake as u128)) as u64);
+    let unlockable_principle_stake =
+        (
+            math128::min(
+                fixed_point64::multiply_u128(
+                    (delegator_principle_stake as u128), unlockable_fraction
+                ),
+                (delegator_principle_stake as u128)
+            ) as u64
+        );
     let locked_amount = delegator_principle_stake - unlockable_principle_stake;
 
-    assert!(delegator_active_balance >= locked_amount,
-        error::invalid_state(EDELEGATOR_ACTIVE_BALANCE_TOO_LOW));
+    assert!(
+        delegator_active_balance >= locked_amount,
+        error::invalid_state(EDELEGATOR_ACTIVE_BALANCE_TOO_LOW)
+    );
     delegator_active_balance - locked_amount
 
 }
@@ -3665,6 +4471,7 @@ accurate as time passes
 
 ## Function `can_principle_unlock`
 
+Note: this does not synchronize with stake pool, therefore the answer may be conservative
 
 
 
public fun can_principle_unlock(delegator_addr: address, pool_address: address, amount: u64): bool
@@ -3676,47 +4483,54 @@ accurate as time passes
 Implementation
 
 
-
public fun can_principle_unlock(delegator_addr: address, pool_address: address, amount: u64)
-    : bool acquires DelegationPool {
+
public fun can_principle_unlock(
+    delegator_addr: address, pool_address: address, amount: u64
+): bool acquires DelegationPool {
 
-    let principle_stake_table = &borrow_global<DelegationPool>(pool_address).principle_stake;
+    let principle_stake_table =
+        &borrow_global<DelegationPool>(pool_address).principle_stake;
 
     if (!table::contains(principle_stake_table, delegator_addr)) {
-        return true
+        return false
     };
 
-    let unlock_schedule = &mut borrow_global_mut<DelegationPool>(pool_address).principle_unlock_schedule;
+    let unlock_schedule =
+        &mut borrow_global_mut<DelegationPool>(pool_address).principle_unlock_schedule;
     let one = fixed_point64::create_from_rational(1, 1);
-    if (fixed_point64::greater_or_equal(unlock_schedule.cumulative_unlocked_fraction, one)) {
+    if (fixed_point64::greater_or_equal(
+        unlock_schedule.cumulative_unlocked_fraction, one
+    )) {
         return true
     };
     if (unlock_schedule.start_timestamp_secs > timestamp::now_seconds()) {
-        let unlockable_amount = cached_unlockable_balance(delegator_addr, pool_address);
+        let unlockable_amount =
+            cached_unlockable_balance(delegator_addr, pool_address);
         return amount <= unlockable_amount
     };
 
     //subtraction safety due to check above
-    let unlock_periods_passed = (timestamp::now_seconds() - unlock_schedule.start_timestamp_secs)
-        / unlock_schedule.period_duration;
+    let unlock_periods_passed =
+        (timestamp::now_seconds() - unlock_schedule.start_timestamp_secs)
+            / unlock_schedule.period_duration;
     let last_unlocked_period = unlock_schedule.last_unlock_period;
     let schedule_length = vector::length(&unlock_schedule.schedule);
     let cfraction = unlock_schedule.cumulative_unlocked_fraction;
-    while (last_unlocked_period < unlock_periods_passed && fixed_point64::less(
-            cfraction, one
-        )) {
-        let next_fraction = if (schedule_length <= last_unlocked_period) {
-            *vector::borrow(&unlock_schedule.schedule, schedule_length - 1)
-        } else { *vector::borrow(&unlock_schedule.schedule, last_unlocked_period) };
+    while (last_unlocked_period < unlock_periods_passed && fixed_point64::less(cfraction, one)
+            && last_unlocked_period < schedule_length) {
+        let next_fraction = *vector::borrow(&unlock_schedule.schedule, last_unlocked_period);
         cfraction = fixed_point64::add(cfraction, next_fraction);
-
         last_unlocked_period = last_unlocked_period + 1;
     };
-
+    if (last_unlocked_period < unlock_periods_passed && fixed_point64::less(cfraction, one)) {
+        let final_fraction= *vector::borrow(&unlock_schedule.schedule, schedule_length - 1);
+        // Acclerate calculation to current period and don't update last_unlocked_period since it is not used anymore
+        cfraction = fixed_point64::add(cfraction, fixed_point64::multiply_u128_return_fixpoint64((unlock_periods_passed - last_unlocked_period as u128), final_fraction));
+        cfraction = fixed_point64::min(cfraction, one);
+    };
     unlock_schedule.cumulative_unlocked_fraction = cfraction;
     unlock_schedule.last_unlock_period = unlock_periods_passed;
     let unlockable_amount = cached_unlockable_balance(delegator_addr, pool_address);
     amount <= unlockable_amount
-
 }
 
@@ -3741,36 +4555,53 @@ at most how much active stake there is on the stake pool. Implementation -
public entry fun unlock(delegator: &signer, pool_address: address, amount: u64) acquires DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage {
+
public entry fun unlock(
+    delegator: &signer, pool_address: address, amount: u64
+) acquires DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage {
     // short-circuit if amount to unlock is 0 so no event is emitted
     if (amount == 0) { return };
+    // fail unlock of less than `MIN_COINS_ON_SHARES_POOL`
+    assert!(amount >= MIN_COINS_ON_SHARES_POOL, error::invalid_argument(EMINIMUM_UNLOCK_AMOUNT));
     // fail unlock of more stake than `active` on the stake pool
     let (active, _, _, _) = stake::get_stake(pool_address);
-    assert!(amount <= active,
-        error::invalid_argument(ENOT_ENOUGH_ACTIVE_STAKE_TO_UNLOCK));
+    assert!(
+        amount <= active,
+        error::invalid_argument(ENOT_ENOUGH_ACTIVE_STAKE_TO_UNLOCK)
+    );
 
     // synchronize delegation and stake pools before any user operation
     synchronize_delegation_pool(pool_address);
 
     let delegator_address = signer::address_of(delegator);
     // fail if the amount after withdraw is less than the principle stake and the lockup time is not expired
-    assert!(can_principle_unlock(delegator_address, pool_address, amount),
-        error::invalid_argument(EAMOUNT_REQUESTED_NOT_UNLOCKABLE));
+    if (is_principle_stakeholder(delegator_address, pool_address)) {
+        assert!(
+            can_principle_unlock(delegator_address, pool_address, amount),
+            error::invalid_argument(EAMOUNT_REQUESTED_NOT_UNLOCKABLE)
+        );
+    };
     let pool = borrow_global_mut<DelegationPool>(pool_address);
-    amount = coins_to_transfer_to_ensure_min_stake(&pool.active_shares,
-        pending_inactive_shares_pool(pool), delegator_address, amount,);
+    amount = coins_to_transfer_to_ensure_min_stake(
+        &pool.active_shares,
+        pending_inactive_shares_pool(pool),
+        delegator_address,
+        amount
+    );
     amount = redeem_active_shares(pool, delegator_address, amount);
     stake::unlock(&retrieve_stake_pool_owner(pool), amount);
 
     buy_in_pending_inactive_shares(pool, delegator_address, amount);
     assert_min_pending_inactive_balance(pool, delegator_address);
 
-    event::emit_event(&mut pool.unlock_stake_events,
-        UnlockStakeEvent { pool_address, delegator_address, amount_unlocked: amount, },
+    event::emit_event(
+        &mut pool.unlock_stake_events,
+        UnlockStakeEvent { pool_address, delegator_address, amount_unlocked: amount }
     );
     let (active_stake, _, pending_active, _) = stake::get_stake(pool_address);
-    assert!(active_stake + pending_active == pool_u64::total_coins(&pool.active_shares),
-        error::invalid_state(EACTIVE_COIN_VALUE_NOT_SAME_STAKE_DELEGATION_POOL));
+    assert!(
+        active_stake + pending_active == pool_u64::total_coins(&pool.active_shares),
+        error::invalid_state(EACTIVE_COIN_VALUE_NOT_SAME_STAKE_DELEGATION_POOL)
+    );
 }
 
@@ -3794,29 +4625,11 @@ Move amount of coins from pending_inactive to active. Implementation -
public entry fun reactivate_stake(delegator: &signer, pool_address: address, amount: u64) acquires DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage {
-    // short-circuit if amount to reactivate is 0 so no event is emitted
-    if (amount == 0) { return };
-    // synchronize delegation and stake pools before any user operation
-    synchronize_delegation_pool(pool_address);
-
-    let pool = borrow_global_mut<DelegationPool>(pool_address);
+
public entry fun reactivate_stake(
+    delegator: &signer, pool_address: address, amount: u64
+) acquires DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage {
     let delegator_address = signer::address_of(delegator);
-
-    amount = coins_to_transfer_to_ensure_min_stake(pending_inactive_shares_pool(pool), &pool
-        .active_shares, delegator_address, amount,);
-    let observed_lockup_cycle = pool.observed_lockup_cycle;
-    amount = redeem_inactive_shares(pool, delegator_address, amount,
-        observed_lockup_cycle);
-
-    stake::reactivate_stake(&retrieve_stake_pool_owner(pool), amount);
-
-    buy_in_active_shares(pool, delegator_address, amount);
-    assert_min_active_balance(pool, delegator_address);
-
-    event::emit_event(&mut pool.reactivate_stake_events,
-        ReactivateStakeEvent { pool_address, delegator_address, amount_reactivated: amount, },
-    );
+    authorized_reactivate_stake(delegator_address, pool_address, amount)
 }
 
@@ -3840,12 +4653,19 @@ Withdraw amount of owned inactive stake from the delegation pool at Implementation -
public entry fun withdraw(delegator: &signer, pool_address: address, amount: u64) acquires DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage {
+
public entry fun withdraw(
+    delegator: &signer, pool_address: address, amount: u64
+) acquires DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage {
     assert!(amount > 0, error::invalid_argument(EWITHDRAW_ZERO_STAKE));
-    // synchronize delegation and stake pools before any user operation
+    // Synchronize the delegation and stake pools before any user operation.
     synchronize_delegation_pool(pool_address);
-    withdraw_internal(borrow_global_mut<DelegationPool>(pool_address),
-        signer::address_of(delegator), amount);
+    let delegator_address = signer::address_of(delegator);
+    withdraw_internal(
+        borrow_global_mut<DelegationPool>(pool_address),
+        delegator_address,
+        amount,
+        delegator_address,
+    );
 }
 
@@ -3859,7 +4679,7 @@ Withdraw amount of owned inactive stake from the delegation pool at -
fun withdraw_internal(pool: &mut pbo_delegation_pool::DelegationPool, delegator_address: address, amount: u64)
+
fun withdraw_internal(pool: &mut pbo_delegation_pool::DelegationPool, delegator_address: address, amount: u64, recipient_address: address)
 
@@ -3868,25 +4688,38 @@ Withdraw amount of owned inactive stake from the delegation pool at Implementation -
fun withdraw_internal(pool: &mut DelegationPool, delegator_address: address, amount: u64) acquires GovernanceRecords {
+
fun withdraw_internal(
+    pool: &mut DelegationPool, delegator_address: address, amount: u64, recipient_address: address
+) acquires GovernanceRecords {
     // TODO: recycle storage when a delegator fully exits the delegation pool.
     // short-circuit if amount to withdraw is 0 so no event is emitted
     if (amount == 0) { return };
 
     let pool_address = get_pool_address(pool);
-    let (withdrawal_exists, withdrawal_olc) = pending_withdrawal_exists(pool,
-        delegator_address);
+    let (withdrawal_exists, withdrawal_olc) =
+        pending_withdrawal_exists(pool, delegator_address);
     // exit if no withdrawal or (it is pending and cannot withdraw pending_inactive stake from stake pool)
-    if (!(withdrawal_exists
-            && (withdrawal_olc.index < pool.observed_lockup_cycle.index || can_withdraw_pending_inactive(
-                    pool_address
-                )))) { return };
+    if (!(
+        withdrawal_exists
+            && (
+                withdrawal_olc.index < pool.observed_lockup_cycle.index
+                    || can_withdraw_pending_inactive(pool_address)
+            )
+    )) { return };
 
     if (withdrawal_olc.index == pool.observed_lockup_cycle.index) {
-        amount = coins_to_redeem_to_ensure_min_stake(pending_inactive_shares_pool(pool),
-            delegator_address, amount,)
+        amount = coins_to_redeem_to_ensure_min_stake(
+            pending_inactive_shares_pool(pool),
+            delegator_address,
+            amount
+        )
     };
-    amount = redeem_inactive_shares(pool, delegator_address, amount, withdrawal_olc);
+    amount = redeem_inactive_shares(
+        pool,
+        delegator_address,
+        amount,
+        withdrawal_olc
+    );
 
     let stake_pool_owner = &retrieve_stake_pool_owner(pool);
     // stake pool will inactivate entire pending_inactive stake at `stake::withdraw` to make it withdrawable
@@ -3908,15 +4741,16 @@ Withdraw amount of owned inactive stake from the delegation pool at
         // no excess stake if `stake::withdraw` does not inactivate at all
         stake::withdraw(stake_pool_owner, amount);
     };
-    supra_account::transfer(stake_pool_owner, delegator_address, amount);
+    supra_account::transfer(stake_pool_owner, recipient_address, amount);
 
     // commit withdrawal of possibly inactive stake to the `total_coins_inactive`
     // known by the delegation pool in order to not mistake it for slashing at next synchronization
     let (_, inactive, _, _) = stake::get_stake(pool_address);
     pool.total_coins_inactive = inactive;
 
-    event::emit_event(&mut pool.withdraw_stake_events,
-        WithdrawStakeEvent { pool_address, delegator_address, amount_withdrawn: amount, },
+    event::emit_event(
+        &mut pool.withdraw_stake_events,
+        WithdrawStakeEvent { pool_address, delegator_address, amount_withdrawn: amount }
     );
 }
 
@@ -3943,12 +4777,14 @@ A bool is returned to signal if a pending withdrawal exists at all. Implementation -
fun pending_withdrawal_exists(pool: &DelegationPool, delegator_address: address)
-    : (bool,
-    ObservedLockupCycle) {
+
fun pending_withdrawal_exists(
+    pool: &DelegationPool, delegator_address: address
+): (bool, ObservedLockupCycle) {
     if (table::contains(&pool.pending_withdrawals, delegator_address)) {
         (true, *table::borrow(&pool.pending_withdrawals, delegator_address))
-    } else { (false, olc_with_index(0)) }
+    } else {
+        (false, olc_with_index(0))
+    }
 }
 
@@ -4026,11 +4862,14 @@ be explicitly withdrawn by delegator Implementation -
fun execute_pending_withdrawal(pool: &mut DelegationPool, delegator_address: address) acquires GovernanceRecords {
-    let (withdrawal_exists, withdrawal_olc) = pending_withdrawal_exists(pool,
-        delegator_address);
-    if (withdrawal_exists && withdrawal_olc.index < pool.observed_lockup_cycle.index) {
-        withdraw_internal(pool, delegator_address, MAX_U64);
+
fun execute_pending_withdrawal(
+    pool: &mut DelegationPool, delegator_address: address
+) acquires GovernanceRecords {
+    let (withdrawal_exists, withdrawal_olc) =
+        pending_withdrawal_exists(pool, delegator_address);
+    if (withdrawal_exists
+        && withdrawal_olc.index < pool.observed_lockup_cycle.index) {
+        withdraw_internal(pool, delegator_address, MAX_U64, delegator_address);
     }
 }
 
@@ -4056,8 +4895,9 @@ deposited coins_amount. This function doesn't make any coin transfe Implementation -
fun buy_in_active_shares(pool: &mut DelegationPool, shareholder: address, coins_amount: u64,)
-    : u128 acquires GovernanceRecords {
+
fun buy_in_active_shares(
+    pool: &mut DelegationPool, shareholder: address, coins_amount: u64
+): u128 acquires GovernanceRecords {
     let new_shares = pool_u64::amount_to_shares(&pool.active_shares, coins_amount);
     // No need to buy 0 shares.
     if (new_shares == 0) {
@@ -4067,8 +4907,9 @@ deposited coins_amount. This function doesn't make any coin transfe
     // Always update governance records before any change to the shares pool.
     let pool_address = get_pool_address(pool);
     if (partial_governance_voting_enabled(pool_address)) {
-        update_governance_records_for_buy_in_active_shares(pool, pool_address,
-            new_shares, shareholder);
+        update_governance_records_for_buy_in_active_shares(
+            pool, pool_address, new_shares, shareholder
+        );
     };
 
     pool_u64::buy_in(&mut pool.active_shares, shareholder, coins_amount);
@@ -4100,10 +4941,12 @@ to ensure there is always only one withdrawal request.
 
 
 
fun buy_in_pending_inactive_shares(
-    pool: &mut DelegationPool, shareholder: address, coins_amount: u64,
+    pool: &mut DelegationPool, shareholder: address, coins_amount: u64
 ): u128 acquires GovernanceRecords {
-    let new_shares = pool_u64::amount_to_shares(pending_inactive_shares_pool(pool),
-        coins_amount);
+    let new_shares =
+        pool_u64::amount_to_shares(
+            pending_inactive_shares_pool(pool), coins_amount
+        );
     // never create a new pending withdrawal unless delegator owns some pending_inactive shares
     if (new_shares == 0) {
         return 0
@@ -4112,21 +4955,27 @@ to ensure there is always only one withdrawal request.
     // Always update governance records before any change to the shares pool.
     let pool_address = get_pool_address(pool);
     if (partial_governance_voting_enabled(pool_address)) {
-        update_governance_records_for_buy_in_pending_inactive_shares(pool, pool_address,
-            new_shares, shareholder);
+        update_governance_records_for_buy_in_pending_inactive_shares(
+            pool, pool_address, new_shares, shareholder
+        );
     };
 
     // cannot buy inactive shares, only pending_inactive at current lockup cycle
-    pool_u64::buy_in(pending_inactive_shares_pool_mut(pool), shareholder, coins_amount);
+    pool_u64::buy_in(
+        pending_inactive_shares_pool_mut(pool), shareholder, coins_amount
+    );
 
     // execute the pending withdrawal if exists and is inactive before creating a new one
     execute_pending_withdrawal(pool, shareholder);
 
     // save observed lockup cycle for the new pending withdrawal
     let observed_lockup_cycle = pool.observed_lockup_cycle;
-    assert!(*table::borrow_mut_with_default(&mut pool.pending_withdrawals, shareholder,
-            observed_lockup_cycle) == observed_lockup_cycle,
-        error::invalid_state(EPENDING_WITHDRAWAL_EXISTS));
+    assert!(
+        *table::borrow_mut_with_default(
+            &mut pool.pending_withdrawals, shareholder, observed_lockup_cycle
+        ) == observed_lockup_cycle,
+        error::invalid_state(EPENDING_WITHDRAWAL_EXISTS)
+    );
 
     new_shares
 }
@@ -4154,7 +5003,7 @@ to the exact number of shares to redeem in order to achieve this.
 
 
 
fun amount_to_shares_to_redeem(
-    shares_pool: &pool_u64::Pool, shareholder: address, coins_amount: u64,
+    shares_pool: &pool_u64::Pool, shareholder: address, coins_amount: u64
 ): u128 {
     if (coins_amount >= pool_u64::balance(shares_pool, shareholder)) {
         // cap result at total shares of shareholder to pass `EINSUFFICIENT_SHARES` on subsequent redeem
@@ -4188,18 +5037,23 @@ be available for withdrawal when current OLC ends.
 Implementation
 
 
-
fun redeem_active_shares(pool: &mut DelegationPool, shareholder: address, coins_amount: u64,)
-    : u64 acquires GovernanceRecords {
-    let shares_to_redeem = amount_to_shares_to_redeem(&pool.active_shares, shareholder,
-        coins_amount);
+
fun redeem_active_shares(
+    pool: &mut DelegationPool, shareholder: address, coins_amount: u64
+): u64 acquires GovernanceRecords {
+    let shares_to_redeem =
+        amount_to_shares_to_redeem(&pool.active_shares, shareholder, coins_amount);
     // silently exit if not a shareholder otherwise redeem would fail with `ESHAREHOLDER_NOT_FOUND`
     if (shares_to_redeem == 0) return 0;
 
     // Always update governance records before any change to the shares pool.
     let pool_address = get_pool_address(pool);
     if (partial_governance_voting_enabled(pool_address)) {
-        update_governanace_records_for_redeem_active_shares(pool, pool_address,
-            shares_to_redeem, shareholder);
+        update_governanace_records_for_redeem_active_shares(
+            pool,
+            pool_address,
+            shares_to_redeem,
+            shareholder
+        );
     };
 
     pool_u64::redeem_shares(&mut pool.active_shares, shareholder, shares_to_redeem)
@@ -4235,26 +5089,34 @@ escape inactivation when current lockup ends.
     pool: &mut DelegationPool,
     shareholder: address,
     coins_amount: u64,
-    lockup_cycle: ObservedLockupCycle,
+    lockup_cycle: ObservedLockupCycle
 ): u64 acquires GovernanceRecords {
-    let shares_to_redeem = amount_to_shares_to_redeem(table::borrow(&pool.inactive_shares,
-            lockup_cycle), shareholder, coins_amount);
+    let shares_to_redeem =
+        amount_to_shares_to_redeem(
+            table::borrow(&pool.inactive_shares, lockup_cycle),
+            shareholder,
+            coins_amount
+        );
     // silently exit if not a shareholder otherwise redeem would fail with `ESHAREHOLDER_NOT_FOUND`
     if (shares_to_redeem == 0) return 0;
 
     // Always update governance records before any change to the shares pool.
     let pool_address = get_pool_address(pool);
     // Only redeem shares from the pending_inactive pool at `lockup_cycle` == current OLC.
-    if (partial_governance_voting_enabled(pool_address) && lockup_cycle.index == pool.observed_lockup_cycle
-        .index) {
-        update_governanace_records_for_redeem_pending_inactive_shares(pool, pool_address,
-            shares_to_redeem, shareholder);
+    if (partial_governance_voting_enabled(pool_address)
+        && lockup_cycle.index == pool.observed_lockup_cycle.index) {
+        update_governanace_records_for_redeem_pending_inactive_shares(
+            pool,
+            pool_address,
+            shares_to_redeem,
+            shareholder
+        );
     };
 
     let inactive_shares = table::borrow_mut(&mut pool.inactive_shares, lockup_cycle);
     // 1. reaching here means delegator owns inactive/pending_inactive shares at OLC `lockup_cycle`
-    let redeemed_coins = pool_u64::redeem_shares(inactive_shares, shareholder,
-        shares_to_redeem);
+    let redeemed_coins =
+        pool_u64::redeem_shares(inactive_shares, shareholder, shares_to_redeem);
 
     // if entirely reactivated pending_inactive stake or withdrawn inactive one,
     // re-enable unlocking for delegator by deleting this pending withdrawal
@@ -4264,8 +5126,11 @@ escape inactivation when current lockup ends.
         table::remove(&mut pool.pending_withdrawals, shareholder);
     };
     // destroy inactive shares pool of past OLC if all its stake has been withdrawn
-    if (lockup_cycle.index < pool.observed_lockup_cycle.index && total_coins(inactive_shares) == 0) {
-        pool_u64::destroy_empty(table::remove(&mut pool.inactive_shares, lockup_cycle));
+    if (lockup_cycle.index < pool.observed_lockup_cycle.index
+        && total_coins(inactive_shares) == 0) {
+        pool_u64::destroy_empty(
+            table::remove(&mut pool.inactive_shares, lockup_cycle)
+        );
     };
 
     redeemed_coins
@@ -4295,10 +5160,12 @@ whether the lockup expired on the stake pool.
 
 
 
fun calculate_stake_pool_drift(pool: &DelegationPool): (bool, u64, u64, u64, u64) {
-    let (active, inactive, pending_active, pending_inactive) = stake::get_stake(
-        get_pool_address(pool));
-    assert!(inactive >= pool.total_coins_inactive,
-        error::invalid_state(ESLASHED_INACTIVE_STAKE_ON_PAST_OLC));
+    let (active, inactive, pending_active, pending_inactive) =
+        stake::get_stake(get_pool_address(pool));
+    assert!(
+        inactive >= pool.total_coins_inactive,
+        error::invalid_state(ESLASHED_INACTIVE_STAKE_ON_PAST_OLC)
+    );
     // determine whether a new lockup cycle has been ended on the stake pool and
     // inactivated SOME `pending_inactive` stake which should stop earning rewards now,
     // thus requiring separation of the `pending_inactive` stake on current observed lockup
@@ -4321,23 +5188,36 @@ whether the lockup expired on the stake pool.
 
     // operator `active` rewards not persisted yet to the active shares pool
     let pool_active = total_coins(&pool.active_shares);
-    let commission_active = if (active > pool_active) {
-        math64::mul_div(active - pool_active, pool.operator_commission_percentage,
-            MAX_FEE)
-    } else {
-        // handle any slashing applied to `active` stake
-        0 };
+    let commission_active =
+        if (active > pool_active) {
+            math64::mul_div(
+                active - pool_active, pool.operator_commission_percentage, MAX_FEE
+            )
+        } else {
+            // handle any slashing applied to `active` stake
+            0
+        };
     // operator `pending_inactive` rewards not persisted yet to the pending_inactive shares pool
     let pool_pending_inactive = total_coins(pending_inactive_shares_pool(pool));
-    let commission_pending_inactive = if (pending_inactive > pool_pending_inactive) {
-        math64::mul_div(pending_inactive - pool_pending_inactive, pool.operator_commission_percentage,
-            MAX_FEE)
-    } else {
-        // handle any slashing applied to `pending_inactive` stake
-        0 };
+    let commission_pending_inactive =
+        if (pending_inactive > pool_pending_inactive) {
+            math64::mul_div(
+                pending_inactive - pool_pending_inactive,
+                pool.operator_commission_percentage,
+                MAX_FEE
+            )
+        } else {
+            // handle any slashing applied to `pending_inactive` stake
+            0
+        };
 
-    (lockup_cycle_ended, active, pending_inactive, commission_active,
-        commission_pending_inactive)
+    (
+        lockup_cycle_ended,
+        active,
+        pending_inactive,
+        commission_active,
+        commission_pending_inactive
+    )
 }
 
@@ -4362,11 +5242,18 @@ shares pools, assign commission to operator and eventually prepare delegation po Implementation -
public entry fun synchronize_delegation_pool(pool_address: address) acquires DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage {
+
public entry fun synchronize_delegation_pool(
+    pool_address: address
+) acquires DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage {
     assert_delegation_pool_exists(pool_address);
     let pool = borrow_global_mut<DelegationPool>(pool_address);
-    let (lockup_cycle_ended, active, pending_inactive, commission_active,
-        commission_pending_inactive) = calculate_stake_pool_drift(pool);
+    let (
+        lockup_cycle_ended,
+        active,
+        pending_inactive,
+        commission_active,
+        commission_pending_inactive
+    ) = calculate_stake_pool_drift(pool);
 
     // zero `pending_active` stake indicates that either there are no `add_stake` fees or
     // previous epoch has ended and should release the shares owning the existing fees
@@ -4383,35 +5270,49 @@ shares pools, assign commission to operator and eventually prepare delegation po
 
     // update total coins accumulated by `active` + `pending_active` shares
     // redeemed `add_stake` fees are restored and distributed to the rest of the pool as rewards
-    pool_u64::update_total_coins(&mut pool.active_shares, active - commission_active);
+    pool_u64::update_total_coins(&mut pool.active_shares, active
+        - commission_active);
     // update total coins accumulated by `pending_inactive` shares at current observed lockup cycle
-    pool_u64::update_total_coins(pending_inactive_shares_pool_mut(pool),
-        pending_inactive - commission_pending_inactive);
+    pool_u64::update_total_coins(
+        pending_inactive_shares_pool_mut(pool),
+        pending_inactive - commission_pending_inactive
+    );
 
     // reward operator its commission out of uncommitted active rewards (`add_stake` fees already excluded)
-    buy_in_active_shares(pool,
-        beneficiary_for_operator(stake::get_operator(pool_address)), commission_active);
+    buy_in_active_shares(
+        pool,
+        beneficiary_for_operator(stake::get_operator(pool_address)),
+        commission_active
+    );
     // reward operator its commission out of uncommitted pending_inactive rewards
-    buy_in_pending_inactive_shares(pool,
+    buy_in_pending_inactive_shares(
+        pool,
         beneficiary_for_operator(stake::get_operator(pool_address)),
-        commission_pending_inactive);
+        commission_pending_inactive
+    );
 
-    event::emit_event(&mut pool.distribute_commission_events,
+    event::emit_event(
+        &mut pool.distribute_commission_events,
         DistributeCommissionEvent {
             pool_address,
             operator: stake::get_operator(pool_address),
             commission_active,
-            commission_pending_inactive,
-        },);
+            commission_pending_inactive
+        }
+    );
 
     if (features::operator_beneficiary_change_enabled()) {
-        emit(DistributeCommission {
+        emit(
+            DistributeCommission {
                 pool_address,
                 operator: stake::get_operator(pool_address),
-                beneficiary: beneficiary_for_operator(stake::get_operator(pool_address)),
+                beneficiary: beneficiary_for_operator(
+                    stake::get_operator(pool_address)
+                ),
                 commission_active,
-                commission_pending_inactive,
-            })
+                commission_pending_inactive
+            }
+        )
     };
 
     // advance lockup cycle on delegation pool if already ended on stake pool (AND stake explicitly inactivated)
@@ -4423,9 +5324,11 @@ shares pools, assign commission to operator and eventually prepare delegation po
         // advance lockup cycle on the delegation pool
         pool.observed_lockup_cycle.index = pool.observed_lockup_cycle.index + 1;
         // start new lockup cycle with a fresh shares pool for `pending_inactive` stake
-        table::add(&mut pool.inactive_shares,
+        table::add(
+            &mut pool.inactive_shares,
             pool.observed_lockup_cycle,
-            pool_u64::create_with_scaling_factor(SHARES_SCALING_FACTOR));
+            pool_u64::create_with_scaling_factor(SHARES_SCALING_FACTOR)
+        );
     };
 
     if (is_next_commission_percentage_effective(pool_address)) {
@@ -4456,25 +5359,35 @@ shares pools, assign commission to operator and eventually prepare delegation po
 
 
 
fun update_governance_records_for_buy_in_active_shares(
-    pool: &DelegationPool, pool_address: address, new_shares: u128, shareholder: address
+    pool: &DelegationPool,
+    pool_address: address,
+    new_shares: u128,
+    shareholder: address
 ) acquires GovernanceRecords {
     // <active shares> of <shareholder> += <new_shares> ---->
     // <active shares> of <current voter of shareholder> += <new_shares>
     // <active shares> of <next voter of shareholder> += <new_shares>
     let governance_records = borrow_global_mut<GovernanceRecords>(pool_address);
-    let vote_delegation = update_and_borrow_mut_delegator_vote_delegation(pool,
-        governance_records, shareholder);
+    let vote_delegation =
+        update_and_borrow_mut_delegator_vote_delegation(
+            pool, governance_records, shareholder
+        );
     let current_voter = vote_delegation.voter;
     let pending_voter = vote_delegation.pending_voter;
-    let current_delegated_votes = update_and_borrow_mut_delegated_votes(pool,
-        governance_records, current_voter);
-    current_delegated_votes.active_shares = current_delegated_votes.active_shares + new_shares;
+    let current_delegated_votes =
+        update_and_borrow_mut_delegated_votes(
+            pool, governance_records, current_voter
+        );
+    current_delegated_votes.active_shares = current_delegated_votes.active_shares
+        + new_shares;
     if (pending_voter == current_voter) {
         current_delegated_votes.active_shares_next_lockup = current_delegated_votes.active_shares_next_lockup
             + new_shares;
     } else {
-        let pending_delegated_votes = update_and_borrow_mut_delegated_votes(pool,
-            governance_records, pending_voter);
+        let pending_delegated_votes =
+            update_and_borrow_mut_delegated_votes(
+                pool, governance_records, pending_voter
+            );
         pending_delegated_votes.active_shares_next_lockup = pending_delegated_votes.active_shares_next_lockup
             + new_shares;
     };
@@ -4501,16 +5414,23 @@ shares pools, assign commission to operator and eventually prepare delegation po
 
 
 
fun update_governance_records_for_buy_in_pending_inactive_shares(
-    pool: &DelegationPool, pool_address: address, new_shares: u128, shareholder: address
+    pool: &DelegationPool,
+    pool_address: address,
+    new_shares: u128,
+    shareholder: address
 ) acquires GovernanceRecords {
     // <pending inactive shares> of <shareholder> += <new_shares>   ---->
     // <pending inactive shares> of <current voter of shareholder> += <new_shares>
     // no impact on <pending inactive shares> of <next voter of shareholder>
     let governance_records = borrow_global_mut<GovernanceRecords>(pool_address);
-    let current_voter = calculate_and_update_delegator_voter_internal(pool,
-        governance_records, shareholder);
-    let current_delegated_votes = update_and_borrow_mut_delegated_votes(pool,
-        governance_records, current_voter);
+    let current_voter =
+        calculate_and_update_delegator_voter_internal(
+            pool, governance_records, shareholder
+        );
+    let current_delegated_votes =
+        update_and_borrow_mut_delegated_votes(
+            pool, governance_records, current_voter
+        );
     current_delegated_votes.pending_inactive_shares = current_delegated_votes.pending_inactive_shares
         + new_shares;
 }
@@ -4536,25 +5456,35 @@ shares pools, assign commission to operator and eventually prepare delegation po
 
 
 
fun update_governanace_records_for_redeem_active_shares(
-    pool: &DelegationPool, pool_address: address, shares_to_redeem: u128, shareholder: address
+    pool: &DelegationPool,
+    pool_address: address,
+    shares_to_redeem: u128,
+    shareholder: address
 ) acquires GovernanceRecords {
     // <active shares> of <shareholder> -= <shares_to_redeem> ---->
     // <active shares> of <current voter of shareholder> -= <shares_to_redeem>
     // <active shares> of <next voter of shareholder> -= <shares_to_redeem>
     let governance_records = borrow_global_mut<GovernanceRecords>(pool_address);
-    let vote_delegation = update_and_borrow_mut_delegator_vote_delegation(pool,
-        governance_records, shareholder);
+    let vote_delegation =
+        update_and_borrow_mut_delegator_vote_delegation(
+            pool, governance_records, shareholder
+        );
     let current_voter = vote_delegation.voter;
     let pending_voter = vote_delegation.pending_voter;
-    let current_delegated_votes = update_and_borrow_mut_delegated_votes(pool,
-        governance_records, current_voter);
-    current_delegated_votes.active_shares = current_delegated_votes.active_shares - shares_to_redeem;
+    let current_delegated_votes =
+        update_and_borrow_mut_delegated_votes(
+            pool, governance_records, current_voter
+        );
+    current_delegated_votes.active_shares = current_delegated_votes.active_shares
+        - shares_to_redeem;
     if (current_voter == pending_voter) {
         current_delegated_votes.active_shares_next_lockup = current_delegated_votes.active_shares_next_lockup
             - shares_to_redeem;
     } else {
-        let pending_delegated_votes = update_and_borrow_mut_delegated_votes(pool,
-            governance_records, pending_voter);
+        let pending_delegated_votes =
+            update_and_borrow_mut_delegated_votes(
+                pool, governance_records, pending_voter
+            );
         pending_delegated_votes.active_shares_next_lockup = pending_delegated_votes.active_shares_next_lockup
             - shares_to_redeem;
     };
@@ -4581,16 +5511,23 @@ shares pools, assign commission to operator and eventually prepare delegation po
 
 
 
fun update_governanace_records_for_redeem_pending_inactive_shares(
-    pool: &DelegationPool, pool_address: address, shares_to_redeem: u128, shareholder: address
+    pool: &DelegationPool,
+    pool_address: address,
+    shares_to_redeem: u128,
+    shareholder: address
 ) acquires GovernanceRecords {
     // <pending inactive shares> of <shareholder> -= <shares_to_redeem>  ---->
     // <pending inactive shares> of <current voter of shareholder> -= <shares_to_redeem>
     // no impact on <pending inactive shares> of <next voter of shareholder>
     let governance_records = borrow_global_mut<GovernanceRecords>(pool_address);
-    let current_voter = calculate_and_update_delegator_voter_internal(pool,
-        governance_records, shareholder);
-    let current_delegated_votes = update_and_borrow_mut_delegated_votes(pool,
-        governance_records, current_voter);
+    let current_voter =
+        calculate_and_update_delegator_voter_internal(
+            pool, governance_records, shareholder
+        );
+    let current_delegated_votes =
+        update_and_borrow_mut_delegated_votes(
+            pool, governance_records, current_voter
+        );
     current_delegated_votes.pending_inactive_shares = current_delegated_votes.pending_inactive_shares
         - shares_to_redeem;
 }
diff --git a/aptos-move/framework/supra-framework/sources/pbo_delegation_pool.move b/aptos-move/framework/supra-framework/sources/pbo_delegation_pool.move
index efe43a60339ae..1bb029044aee3 100644
--- a/aptos-move/framework/supra-framework/sources/pbo_delegation_pool.move
+++ b/aptos-move/framework/supra-framework/sources/pbo_delegation_pool.move
@@ -248,10 +248,14 @@ module supra_framework::pbo_delegation_pool {
 
     /// Minimum amount of coins to be unlocked.
     const EMINIMUM_UNLOCK_AMOUNT: u64 = 38;
-    
+
     /// Balance is not enough.
     const EBALANCE_NOT_SUFFICIENT: u64 = 39;
 
+    /// Thrown by `lock_delegators_stakes` when a given delegator has less than the specified
+    /// amount of stake available in the specified stake pool.
+    const EINSUFFICIENT_STAKE_TO_LOCK: u64 = 40;
+
     const MAX_U64: u64 = 18446744073709551615;
 
     /// Maximum operator percentage fee(of double digit precision): 22.85% is represented as 2285
@@ -480,6 +484,13 @@ module supra_framework::pbo_delegation_pool {
         commission_percentage_next_lockup_cycle: u64
     }
 
+    #[event]
+    struct UnlockScheduleApplied has drop, store {
+        pool_address: address,
+        delegator: address,
+        amount: u64
+    }
+
     #[view]
     /// Return whether supplied address `addr` is owner of a delegation pool.
     public fun owner_cap_exists(addr: address): bool {
@@ -821,11 +832,6 @@ module supra_framework::pbo_delegation_pool {
             features::delegation_pools_enabled(),
             error::invalid_state(EDELEGATION_POOLS_DISABLED)
         );
-        //Unlock start time can not be in the past
-        assert!(
-            unlock_start_time >= timestamp::now_seconds(),
-            error::invalid_argument(ESTARTUP_TIME_IN_PAST)
-        );
         //Unlock duration can not be zero
         assert!(unlock_duration > 0, error::invalid_argument(EPERIOD_DURATION_IS_ZERO));
         //Fraction denominator can not be zero
@@ -994,17 +1000,23 @@ module supra_framework::pbo_delegation_pool {
             delegators,
             stakes,
             |delegator, stake| {
-                //Ignore if stake to be added is `0`
+                // Ignore if stake to be added is `0`
                 if (stake > 0) {
-                    //Compute the actual stake that would be added, `principle_stake` has to be
-                    //populated in the table accordingly
+                    // Compute the actual stake that would be added, `principle_stake` has to be
+                    // populated in the table accordingly
                     if (table::contains(principle_stake_table, delegator)) {
                         let stake_amount =
                             table::borrow_mut(principle_stake_table, delegator);
                         *stake_amount = *stake_amount + stake;
                     } else {
                         table::add(principle_stake_table, delegator, stake);
-                    }
+                    };
+
+                    // Record the details of the lockup event. Note that only the newly locked
+                    // amount is reported and not the total locked amount.
+                    event::emit(
+                        UnlockScheduleApplied { pool_address, delegator, amount: stake }
+                    );
                 }
             }
         );
@@ -1026,7 +1038,6 @@ module supra_framework::pbo_delegation_pool {
                 fund_delegator_stake(funder, pool_address, delegator, stake);
             }
         );
-
     }
 
     #[view]
@@ -1543,7 +1554,10 @@ module supra_framework::pbo_delegation_pool {
         // short-circuit if amount to add is 0 so no event is emitted
         if (amount == 0) { return };
         // fail unlock of less than `MIN_COINS_ON_SHARES_POOL`
-        assert!(amount >= MIN_COINS_ON_SHARES_POOL, error::invalid_argument(EMINIMUM_UNLOCK_AMOUNT));
+        assert!(
+            amount >= MIN_COINS_ON_SHARES_POOL,
+            error::invalid_argument(EMINIMUM_UNLOCK_AMOUNT)
+        );
         // synchronize delegation and stake pools before any user operation
         synchronize_delegation_pool(pool_address);
 
@@ -1601,10 +1615,197 @@ module supra_framework::pbo_delegation_pool {
         }
     }
 
+    /// Reactivates the `pending_inactive` stake of `delegator`.
+    ///
+    /// This function must remain private because it must only be called by an authorized entity and it is the
+    /// callers responsibility to ensure that this is true. Authorized entities currently include the delegator
+    /// itself and the multisig admin of the delegation pool, which must be controlled by The Supra Foundation.
+    ///
+    /// Note that this function is only temporarily intended to work as specified above and exists to enable The
+    /// Supra Foundation to ensure that the allocations of all investors are subject to the terms specified in the
+    /// corresponding legal contracts. It will be deactivated before the validator set it opened up to external
+    /// validator-owners to prevent it from being abused, from which time forward only the delegator will be
+    /// authorized to reactivate their own stake.
+    fun authorized_reactivate_stake(
+        delegator: address, pool_address: address, amount: u64
+    ) acquires DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage {
+        // short-circuit if amount to reactivate is 0 so no event is emitted
+        if (amount == 0) { return };
+        // fail unlock of less than `MIN_COINS_ON_SHARES_POOL`
+        assert!(
+            amount >= MIN_COINS_ON_SHARES_POOL,
+            error::invalid_argument(EMINIMUM_UNLOCK_AMOUNT)
+        );
+        // synchronize delegation and stake pools before any user operation
+        synchronize_delegation_pool(pool_address);
+
+        let pool = borrow_global_mut(pool_address);
+
+        amount = coins_to_transfer_to_ensure_min_stake(
+            pending_inactive_shares_pool(pool),
+            &pool.active_shares,
+            delegator,
+            amount
+        );
+        let observed_lockup_cycle = pool.observed_lockup_cycle;
+        amount = redeem_inactive_shares(pool, delegator, amount, observed_lockup_cycle);
+
+        stake::reactivate_stake(&retrieve_stake_pool_owner(pool), amount);
+
+        buy_in_active_shares(pool, delegator, amount);
+        assert_min_active_balance(pool, delegator);
+
+        event::emit_event(
+            &mut pool.reactivate_stake_events,
+            ReactivateStakeEvent {
+                pool_address,
+                delegator_address: delegator,
+                amount_reactivated: amount
+            }
+        );
+    }
+
+    /// Withdraws the specified `amount` from the `inactive` stake belonging to the given `delegator_address`
+    /// to the address of the `DelegationPool`'s `multisig_admin`, if available.
+    ///
+    /// Note that this function is only temporarily intended to work as specified above and exists to enable The
+    /// Supra Foundation to ensure that the allocations of all investors are subject to the terms specified in the
+    /// corresponding legal contracts. It will be deactivated before the validator set it opened up to external
+    /// validator-owners to prevent it from being abused.
+    fun admin_withdraw(
+        multisig_admin: &signer,
+        pool_address: address,
+        delegator_address: address,
+        amount: u64
+    ) acquires DelegationPool, GovernanceRecords {
+        // Ensure that the caller is the admin of the delegation pool.
+        {
+            assert!(
+                is_admin(signer::address_of(multisig_admin), pool_address),
+                error::permission_denied(ENOT_AUTHORIZED)
+            );
+        };
+        assert!(amount > 0, error::invalid_argument(EWITHDRAW_ZERO_STAKE));
+        withdraw_internal(
+            borrow_global_mut(pool_address),
+            delegator_address,
+            amount,
+            signer::address_of(multisig_admin)
+        );
+    }
+
+    /// Updates the `principle_stake` of each `delegator` in `delegators` according to the amount specified
+    /// at the corresponding index of `new_principle_stakes`. Also ensures that the `delegator`'s `active` stake
+    /// is as close to the specified amount as possible. The locked amount is subject to the vesting schedule
+    /// specified when the delegation pool corresponding to `pool_address` was created.
+    ///
+    /// Note that this function is only temporarily intended to work as specified above and exists to enable The
+    /// Supra Foundation to ensure that the allocations of all investors are subject to the terms specified in the
+    /// corresponding legal contracts. It will be deactivated before the validator set it opened up to external
+    /// validator-owners to prevent it from being abused.
+    public entry fun lock_delegators_stakes(
+        multisig_admin: &signer,
+        pool_address: address,
+        delegators: vector
, + new_principle_stakes: vector + ) acquires DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage { + // Ensure that the caller is the admin of the delegation pool. + { + assert!( + is_admin(signer::address_of(multisig_admin), pool_address), + error::permission_denied(ENOT_AUTHORIZED) + ); + }; + + // Synchronize the delegation and stake pools before any user operation. + synchronize_delegation_pool(pool_address); + + // Ensure that each `delegator` has an `active` stake balance that is as close to + // `principle_stake` as possible. + vector::zip_reverse( + delegators, + new_principle_stakes, + |delegator, principle_stake| { + let (active, inactive, pending_inactive) = + get_stake(pool_address, delegator); + + // Ensure that all stake to be locked is made `active`. + if (active < principle_stake) { + // The amount to lock can be covered by reactivating some previously unlocked stake. + // Only reactivate the required amount to avoid unnecessarily interfering with + // in-progress withdrawals. + let amount_to_reactivate = principle_stake - active; + + // Ensure that we do not try to reactivate more than the available `pending_inactive` stake. + // This should be enforced by functions within `authorized_reactivate_stake`, but checking + // again here makes the correctness of this function easier to reason about. + if (amount_to_reactivate > pending_inactive) { + amount_to_reactivate = pending_inactive; + }; + + if (amount_to_reactivate > MIN_COINS_ON_SHARES_POOL) { + // Reactivate the required amount of `pending_inactive` stake first. + authorized_reactivate_stake( + delegator, pool_address, amount_to_reactivate + ); + }; + + let active_and_pending_inactive = active + pending_inactive; + + if (active_and_pending_inactive < principle_stake) { + // Need to reactivate some of the `inactive` stake. + let amount_to_withdraw = + principle_stake - active_and_pending_inactive; + + // Ensure that we do not try to withdraw more stake than the `inactive` stake. + if (amount_to_withdraw > inactive) { + amount_to_withdraw = inactive; + }; + + if (amount_to_withdraw > MIN_COINS_ON_SHARES_POOL) { + // Withdraw the minimum required amount to the admin's address. + admin_withdraw( + multisig_admin, + pool_address, + delegator, + amount_to_withdraw + ); + // Then allocate it to the delegator again. + fund_delegator_stake( + multisig_admin, + pool_address, + delegator, + amount_to_withdraw + ); + } + } + }; + // else: The amount to lock can be covered by the currently `active` stake. + + // Update the delegator's principle stake and record the details of the lockup event. + let principle_stake_table = + &mut (borrow_global_mut(pool_address).principle_stake); + table::upsert(principle_stake_table, delegator, principle_stake); + event::emit( + UnlockScheduleApplied { + pool_address, + delegator, + amount: principle_stake + } + ); + } + ); + } + ///CAUTION: This is to be used only in the rare circumstances where multisig_admin is convinced that a delegator was the /// rightful owner of `old_delegator` but has lost access and the delegator is also the rightful /// owner of `new_delegator` , Only for those stakeholders which were added at the time of creation /// This does not apply to anyone who added stake later or operator + /// + /// Note that this function is only temporarily intended to work as specified above and exists to enable The + /// Supra Foundation to ensure that the allocations of all investors are subject to the terms specified in the + /// corresponding legal contracts. It will be deactivated before the validator set it opened up to external + /// validator-owners to prevent it from being abused. public entry fun replace_delegator( multisig_admin: &signer, pool_address: address, @@ -1814,16 +2015,26 @@ module supra_framework::pbo_delegation_pool { let last_unlocked_period = unlock_schedule.last_unlock_period; let schedule_length = vector::length(&unlock_schedule.schedule); let cfraction = unlock_schedule.cumulative_unlocked_fraction; - while (last_unlocked_period < unlock_periods_passed && fixed_point64::less(cfraction, one) - && last_unlocked_period < schedule_length) { - let next_fraction = *vector::borrow(&unlock_schedule.schedule, last_unlocked_period); + while (last_unlocked_period < unlock_periods_passed + && fixed_point64::less(cfraction, one) + && last_unlocked_period < schedule_length) { + let next_fraction = + *vector::borrow(&unlock_schedule.schedule, last_unlocked_period); cfraction = fixed_point64::add(cfraction, next_fraction); last_unlocked_period = last_unlocked_period + 1; }; - if (last_unlocked_period < unlock_periods_passed && fixed_point64::less(cfraction, one)) { - let final_fraction= *vector::borrow(&unlock_schedule.schedule, schedule_length - 1); + if (last_unlocked_period < unlock_periods_passed + && fixed_point64::less(cfraction, one)) { + let final_fraction = + *vector::borrow(&unlock_schedule.schedule, schedule_length - 1); // Acclerate calculation to current period and don't update last_unlocked_period since it is not used anymore - cfraction = fixed_point64::add(cfraction, fixed_point64::multiply_u128_return_fixpoint64((unlock_periods_passed - last_unlocked_period as u128), final_fraction)); + cfraction = fixed_point64::add( + cfraction, + fixed_point64::multiply_u128_return_fixpoint64( + (unlock_periods_passed - last_unlocked_period as u128), + final_fraction + ) + ); cfraction = fixed_point64::min(cfraction, one); }; unlock_schedule.cumulative_unlocked_fraction = cfraction; @@ -1840,7 +2051,10 @@ module supra_framework::pbo_delegation_pool { // short-circuit if amount to unlock is 0 so no event is emitted if (amount == 0) { return }; // fail unlock of less than `MIN_COINS_ON_SHARES_POOL` - assert!(amount >= MIN_COINS_ON_SHARES_POOL, error::invalid_argument(EMINIMUM_UNLOCK_AMOUNT)); + assert!( + amount >= MIN_COINS_ON_SHARES_POOL, + error::invalid_argument(EMINIMUM_UNLOCK_AMOUNT) + ); // fail unlock of more stake than `active` on the stake pool let (active, _, _, _) = stake::get_stake(pool_address); assert!( @@ -1887,43 +2101,8 @@ module supra_framework::pbo_delegation_pool { public entry fun reactivate_stake( delegator: &signer, pool_address: address, amount: u64 ) acquires DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage { - // short-circuit if amount to reactivate is 0 so no event is emitted - if (amount == 0) { return }; - // fail unlock of less than `MIN_COINS_ON_SHARES_POOL` - assert!(amount >= MIN_COINS_ON_SHARES_POOL, error::invalid_argument(EMINIMUM_UNLOCK_AMOUNT)); - // synchronize delegation and stake pools before any user operation - synchronize_delegation_pool(pool_address); - - let pool = borrow_global_mut(pool_address); let delegator_address = signer::address_of(delegator); - - amount = coins_to_transfer_to_ensure_min_stake( - pending_inactive_shares_pool(pool), - &pool.active_shares, - delegator_address, - amount - ); - let observed_lockup_cycle = pool.observed_lockup_cycle; - amount = redeem_inactive_shares( - pool, - delegator_address, - amount, - observed_lockup_cycle - ); - - stake::reactivate_stake(&retrieve_stake_pool_owner(pool), amount); - - buy_in_active_shares(pool, delegator_address, amount); - assert_min_active_balance(pool, delegator_address); - - event::emit_event( - &mut pool.reactivate_stake_events, - ReactivateStakeEvent { - pool_address, - delegator_address, - amount_reactivated: amount - } - ); + authorized_reactivate_stake(delegator_address, pool_address, amount) } /// Withdraw `amount` of owned inactive stake from the delegation pool at `pool_address`. @@ -1931,17 +2110,24 @@ module supra_framework::pbo_delegation_pool { delegator: &signer, pool_address: address, amount: u64 ) acquires DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage { assert!(amount > 0, error::invalid_argument(EWITHDRAW_ZERO_STAKE)); - // synchronize delegation and stake pools before any user operation + // Synchronize the delegation and stake pools before any user operation. synchronize_delegation_pool(pool_address); + let delegator_address = signer::address_of(delegator); withdraw_internal( borrow_global_mut(pool_address), - signer::address_of(delegator), - amount + delegator_address, + amount, + delegator_address ); } + // TODO: `recipient_address` must be removed and replaced with `delegator_address` before the + // validator set is opened to non-Foundation validator-owners. fun withdraw_internal( - pool: &mut DelegationPool, delegator_address: address, amount: u64 + pool: &mut DelegationPool, + delegator_address: address, + amount: u64, + recipient_address: address ) acquires GovernanceRecords { // TODO: recycle storage when a delegator fully exits the delegation pool. // short-circuit if amount to withdraw is 0 so no event is emitted @@ -1993,7 +2179,7 @@ module supra_framework::pbo_delegation_pool { // no excess stake if `stake::withdraw` does not inactivate at all stake::withdraw(stake_pool_owner, amount); }; - supra_account::transfer(stake_pool_owner, delegator_address, amount); + supra_account::transfer(stake_pool_owner, recipient_address, amount); // commit withdrawal of possibly inactive stake to the `total_coins_inactive` // known by the delegation pool in order to not mistake it for slashing at next synchronization @@ -2041,7 +2227,12 @@ module supra_framework::pbo_delegation_pool { pending_withdrawal_exists(pool, delegator_address); if (withdrawal_exists && withdrawal_olc.index < pool.observed_lockup_cycle.index) { - withdraw_internal(pool, delegator_address, MAX_U64); + withdraw_internal( + pool, + delegator_address, + MAX_U64, + delegator_address + ); } } @@ -9558,7 +9749,7 @@ module supra_framework::pbo_delegation_pool { can_principle_unlock( delegator_address, pool_address, - (500 * ONE_SUPRA)-1 + (500 * ONE_SUPRA) - 1 ); assert!(unlock_coin, 11); } @@ -9658,4 +9849,264 @@ module supra_framework::pbo_delegation_pool { ); assert!(unlock_coin, 11); } + + #[test(supra_framework = @supra_framework, validator = @0x123, delegator = @0x010)] + public entry fun test_lock_delegator_stake_after_allocation( + supra_framework: &signer, validator: &signer, delegator: &signer + ) acquires DelegationPoolOwnership, DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage { + initialize_for_test(supra_framework); + account::create_account_for_test(signer::address_of(validator)); + let delegator_address = signer::address_of(delegator); + let delegator_address_vec = vector[delegator_address, @0x020]; + let principle_stake = vector[300 * ONE_SUPRA, 200 * ONE_SUPRA]; + let coin = stake::mint_coins(500 * ONE_SUPRA); + let principle_lockup_time = 7776000; + let multisig = generate_multisig_account(validator, vector[@0x12134], 2); + + initialize_test_validator( + validator, + 0, + true, + true, + 0, + delegator_address_vec, + principle_stake, + coin, + option::some(multisig), + vector[2, 2, 3], + 10, + principle_lockup_time, + LOCKUP_CYCLE_SECONDS + ); + + let validator_address = signer::address_of(validator); + let pool_address = get_owned_pool_address(validator_address); + + let new_delegator_address = @0x0215; + let new_delegator_address_signer = + account::create_account_for_test(new_delegator_address); + let funder_signer = account::create_signer_for_test(multisig); + let funder = signer::address_of(&funder_signer); + stake::mint(&funder_signer, 100 * ONE_SUPRA); + stake::mint(&new_delegator_address_signer, 100 * ONE_SUPRA); + assert!( + coin::balance(funder) == (100 * ONE_SUPRA), + 0 + ); + + assert!( + coin::balance(new_delegator_address) == (100 * ONE_SUPRA), + 0 + ); + + add_stake(&new_delegator_address_signer, pool_address, 100 * ONE_SUPRA); + + // + // Ensure that `lock_delegators_stakes` can lock newly-allocated stakes. + // + + // Fund a new delegator. + fund_delegators_with_stake( + &funder_signer, + pool_address, + vector[new_delegator_address], + vector[1 * ONE_SUPRA] + ); + // Ensure that its stake is not subject to the pool vesting schedule. + assert!(!is_principle_stakeholder(new_delegator_address, pool_address), 1); + + // Lock its stake. + lock_delegators_stakes( + &funder_signer, + pool_address, + vector[new_delegator_address], + vector[1 * ONE_SUPRA] + ); + // Ensure that its stake is now subject to the pool vesting schedule. + assert!(is_principle_stakeholder(new_delegator_address, pool_address), 0); + + // + // Ensure that `lock_delegators_stakes` reactivates `pending_inactive` stake. + // + + let delegator = @0x0216; + let delegator_signer = account::create_signer_for_test(delegator); + let delegator_allocation = 10 * ONE_SUPRA; + let half_delegator_allocation = delegator_allocation / 2; + // A rounding error of 1 Quant is introduced by `unlock`. + let half_delegator_allocation_with_rounding_error = half_delegator_allocation + - 1; + let delegator_allocation_after_rounding_error = + half_delegator_allocation + half_delegator_allocation_with_rounding_error; + + // Fund another delegator. + fund_delegators_with_stake( + &funder_signer, + pool_address, + vector[delegator], + vector[delegator_allocation] + ); + + // End the current lockup cycle to ensure that the stake fee that is deducted when the stake + // is first added has been returned. + fast_forward_to_unlock(pool_address); + + // Ensure that the entire allocation is marked as active. + let (active, inactive, pending_inactive) = get_stake(pool_address, delegator); + assert!(active == delegator_allocation, active); + assert!(inactive == 0, inactive); + assert!(pending_inactive == 0, pending_inactive); + + // Unlock half of the initial allocation (i.e. move it to `pending_inactive`). + unlock(&delegator_signer, pool_address, half_delegator_allocation); + + // Ensure that half of the allocation is marked as `active` and the other half as `pending_inactive`. + let (active, inactive, pending_inactive) = get_stake(pool_address, delegator); + assert!(active == half_delegator_allocation, active); + assert!(inactive == 0, inactive); + assert!( + pending_inactive == half_delegator_allocation_with_rounding_error, + pending_inactive + ); + + // Attempt to lock the full allocation, which should cause the `pending_inactive` allocation + // to become `active` again. + lock_delegators_stakes( + &funder_signer, + pool_address, + vector[delegator], + vector[delegator_allocation_after_rounding_error] + ); + + // Ensure that the entire allocation is marked as active again. + let (active, inactive, pending_inactive) = get_stake(pool_address, delegator); + assert!(active == delegator_allocation_after_rounding_error, active); + assert!(inactive == 0, inactive); + assert!(pending_inactive == 0, pending_inactive); + + // Ensure that the delegator's stake is now subject to the pool vesting schedule. + assert!(is_principle_stakeholder(delegator, pool_address), 0); + + // + // Ensure that `lock_delegators_stakes` reactivates `inactive` stake. + // + + delegator = @0x0217; + delegator_signer = account::create_signer_for_test(delegator); + // The amount of staking rewards earned each epoch. See `initialize_for_test_custom`. + let epoch_reward = delegator_allocation / 100; + let half_epoch_reward = epoch_reward / 2; + let delegator_stake = delegator_allocation_after_rounding_error + epoch_reward; + // The amount of stake withheld due to the withdrawal and restaking process used to + // recover `inactive` stake. + let add_stake_fee = + get_add_stake_fee( + pool_address, half_delegator_allocation + half_epoch_reward + ); + + // Fund another delegator. + fund_delegators_with_stake( + &funder_signer, + pool_address, + vector[delegator], + vector[delegator_allocation] + ); + + // End the current lockup cycle to ensure that the stake fee that is deducted when the stake + // is first added has been returned. + fast_forward_to_unlock(pool_address); + + // Ensure that the entire allocation is marked as active. + let (active, inactive, pending_inactive) = get_stake(pool_address, delegator); + assert!(active == delegator_allocation, active); + assert!(inactive == 0, inactive); + assert!(pending_inactive == 0, pending_inactive); + + // Unlock half of the initial allocation (i.e. move it to `pending_inactive`). + unlock(&delegator_signer, pool_address, half_delegator_allocation); + + // End the current lockup cycle to move the `pending_inactive` stake to `inactive`. + // This will also distribute staking rewards for the epoch. + fast_forward_to_unlock(pool_address); + + // Ensure that half of the allocation is marked as `active` and the other half as `inactive`. + let (active, inactive, pending_inactive) = get_stake(pool_address, delegator); + assert!( + active == half_delegator_allocation + half_epoch_reward, + active + ); + // Another rounding error is introduced by the second `unlock`. + assert!( + inactive + == half_delegator_allocation_with_rounding_error + half_epoch_reward + - 1, + inactive + ); + assert!(pending_inactive == 0, pending_inactive); + + // Attempt to lock the full allocation, which should cause the `inactive` allocation + // to become `active` again. + lock_delegators_stakes( + &funder_signer, + pool_address, + vector[delegator], + vector[delegator_stake] + ); + + // Ensure that the entire allocation is marked as active again. The fee for adding stake + // needs to be subtracted from the `active` amount because we've not entered the next epoch yet. + let (active, inactive, pending_inactive) = get_stake(pool_address, delegator); + let expected_active_stake = delegator_stake - add_stake_fee; + assert!(active == expected_active_stake, active); + assert!(inactive == 0, inactive); + assert!(pending_inactive == 0, pending_inactive); + + // Ensure that the delegator's stake is now subject to the pool vesting schedule. + let pool: &mut DelegationPool = borrow_global_mut(pool_address); + let delegator_principle_stake = *table::borrow(&pool.principle_stake, delegator); + assert!(delegator_principle_stake == delegator_stake, delegator_principle_stake); + + // + // Ensure that `lock_delegators_stakes` locks the maximum available stake when the amount + // requested to be locked exceeds the available stake. Also ensure that the same delegator + // can be funded and its new allocation locked, multiple times, and that the principle stake + // specified in the most recent call to `lock_delegators_stakes` is applied correctly. + // + + // Fund the same delegator to ensure that we can lock additional amounts. + fund_delegators_with_stake( + &funder_signer, + pool_address, + vector[delegator], + vector[delegator_allocation] + ); + + // Calculate the fee for the newly added amount. + let add_stake_fee = get_add_stake_fee(pool_address, delegator_allocation); + let expected_total_stake = + expected_active_stake + delegator_allocation - add_stake_fee; + + // Ensure that the entire allocation is marked as active. + let (active, inactive, pending_inactive) = get_stake(pool_address, delegator); + assert!(active == expected_total_stake, active); + assert!(inactive == 0, inactive); + assert!(pending_inactive == 0, pending_inactive); + + // Attempt to lock more than the full allocation. + let more_than_allocated_stake = delegator_allocation * 2; + lock_delegators_stakes( + &funder_signer, + pool_address, + vector[delegator], + vector[more_than_allocated_stake] + ); + + // Ensure that the delegator's `principle_stake` has been updated. + let pool: &mut DelegationPool = borrow_global_mut(pool_address); + let delegator_principle_stake = *table::borrow(&pool.principle_stake, delegator); + assert!( + delegator_principle_stake == more_than_allocated_stake, + delegator_principle_stake + ); + } }