From 05c44ff8feb5c7bc5591a7f433195d52fcaf90d0 Mon Sep 17 00:00:00 2001 From: Isaac Doidge Date: Sun, 29 Dec 2024 19:51:42 +1000 Subject: [PATCH 01/12] [#150] Added a function to pbo_delegation_pool.move to enable the stakes of existing delegators to be made subject to the delegation pool's vesting schedule. --- .../sources/pbo_delegation_pool.move | 120 ++++++++++++++---- 1 file changed, 97 insertions(+), 23 deletions(-) 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 0a94b098908d1..40d5c500160bb 100644 --- a/aptos-move/framework/supra-framework/sources/pbo_delegation_pool.move +++ b/aptos-move/framework/supra-framework/sources/pbo_delegation_pool.move @@ -249,6 +249,10 @@ module supra_framework::pbo_delegation_pool { const ENEW_IS_SAME_AS_OLD_DELEGATOR: u64 = 37; + /// 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 = 38; + const MAX_U64: u64 = 18446744073709551615; /// Maximum operator percentage fee(of double digit precision): 22.85% is represented as 2285 @@ -607,7 +611,6 @@ module supra_framework::pbo_delegation_pool { assert_delegation_pool_exists(pool_address); let pool = borrow_global(pool_address); let (lockup_cycle_ended, active, _, commission_active, commission_pending_inactive) = - calculate_stake_pool_drift(pool); let total_active_shares = pool_u64::total_shares(&pool.active_shares); @@ -943,35 +946,16 @@ module supra_framework::pbo_delegation_pool { delegators: vector
, stakes: vector ) acquires DelegationPool, BeneficiaryForOperator, GovernanceRecords, NextCommissionPercentage { + // Ensure that the caller is the admin of the delegation pool. { assert!( is_admin(signer::address_of(funder), pool_address), error::permission_denied(ENOT_AUTHORIZED) ); }; - let principle_stake_table = - &mut (borrow_global_mut(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); - } - } - } - ); fund_delegators_with_stake(funder, pool_address, delegators, stakes); + lock_existing_delegators_stakes(funder, pool_address, delegators, stakes); } public entry fun fund_delegators_with_stake( @@ -988,7 +972,6 @@ module supra_framework::pbo_delegation_pool { fund_delegator_stake(funder, pool_address, delegator, stake); } ); - } #[view] @@ -1561,10 +1544,101 @@ module supra_framework::pbo_delegation_pool { } } + /// For each delegator in `delegators`, locks the amount of stake specified in the same index of `stakes`. + /// The locked amount is subject to the vesting schedule specified when the delegation pool corresponding + /// to `pool_address` was created. + /// + /// This function does not actually add the `stakes` to the stake pool, so must remain private and must + /// only be called either after ensuring that all `stakes` already exist in the `active` stake pool, or + /// will do so before the end of the transaction. + /// + /// This function also assumes that `multisig_admin` is the admin of the `DelegationPool` at `pool_address`. + /// The caller must ensure that this is true. + fun lock_existing_delegators_stakes( + multisig_admin: &signer, + pool_address: address, + delegators: vector
, + stakes: vector + ) acquires DelegationPool { + let principle_stake_table = + &mut (borrow_global_mut(pool_address).principle_stake); + + vector::zip_reverse( + delegators, + stakes, + |delegator, stake| { + if (stake > 0) { + // Compute the stake to be added. `principle_stake` must be added to 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); + } + } + // else: The stake to be added is `0`. Do nothing. + } + ); + } + + /// For each `delegator` in `delegators`, locks the amount of stake specified in the same index of `stakes_to_lock`. + /// The locked amount is subject to the vesting schedule specified when the delegation pool corresponding + /// to `pool_address` was created. Terminates with an error if any `stake_to_lock` exceeds the stake allocated to + /// the corresponding `delegator` in the `DelegationPool` located at `pool_address`. + /// + /// 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
, + stakes_to_lock: vector + ) acquires DelegationPool { + // 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) + ); + }; + + // Ensure that each `delegator` has enough active stake to cover the corresponding `stake_to_lock`. + vector::zip_reverse( + delegators, + stakes_to_lock, + |delegator, stake_to_lock| { + (active, inactive, pending_inactive) = get_stake(pool_address, delegator); + + // Ensure that the amount to lock is greater than the stake owned by `delegator`. + assert!( + active + inactive + pending_inactive >= stake_to_lock, + error::invalid_state(EINSUFFICIENT_STAKE_TO_LOCK) + ); + + // Either the amount to lock can be covered by the `active` stake, in which case + // `reactivate_stake` will short-circuit, or 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. + amount_to_reactivate = stake_to_lock - active; + reactivate_stake(delegator, pool_address, amount_to_reactivate); + } + ); + + lock_existing_delegators_stakes(funder, pool_address, delegators, stakes_to_lock); + } + ///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, From cd2acecb8a133b5f2d33f0877bc02a7a48febcf7 Mon Sep 17 00:00:00 2001 From: Isaac Doidge Date: Sun, 29 Dec 2024 20:37:36 +1000 Subject: [PATCH 02/12] Fixed errors in last commit. Existing unit tests now passing. --- .../sources/pbo_delegation_pool.move | 100 ++++++++++-------- 1 file changed, 58 insertions(+), 42 deletions(-) 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 40d5c500160bb..878089d2ef53a 100644 --- a/aptos-move/framework/supra-framework/sources/pbo_delegation_pool.move +++ b/aptos-move/framework/supra-framework/sources/pbo_delegation_pool.move @@ -955,7 +955,7 @@ module supra_framework::pbo_delegation_pool { }; fund_delegators_with_stake(funder, pool_address, delegators, stakes); - lock_existing_delegators_stakes(funder, pool_address, delegators, stakes); + lock_existing_delegators_stakes(pool_address, delegators, stakes); } public entry fun fund_delegators_with_stake( @@ -1544,6 +1544,56 @@ module supra_framework::pbo_delegation_pool { } } + /// Reactivates the `pending_inactive` and `inactive` stakes 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 }; + // 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 + } + ); + } + /// For each delegator in `delegators`, locks the amount of stake specified in the same index of `stakes`. /// The locked amount is subject to the vesting schedule specified when the delegation pool corresponding /// to `pool_address` was created. @@ -1555,7 +1605,6 @@ module supra_framework::pbo_delegation_pool { /// This function also assumes that `multisig_admin` is the admin of the `DelegationPool` at `pool_address`. /// The caller must ensure that this is true. fun lock_existing_delegators_stakes( - multisig_admin: &signer, pool_address: address, delegators: vector
, stakes: vector @@ -1596,7 +1645,7 @@ module supra_framework::pbo_delegation_pool { pool_address: address, delegators: vector
, stakes_to_lock: vector - ) acquires DelegationPool { + ) acquires DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage { // Ensure that the caller is the admin of the delegation pool. { assert!( @@ -1610,7 +1659,7 @@ module supra_framework::pbo_delegation_pool { delegators, stakes_to_lock, |delegator, stake_to_lock| { - (active, inactive, pending_inactive) = get_stake(pool_address, delegator); + let (active, inactive, pending_inactive) = get_stake(pool_address, delegator); // Ensure that the amount to lock is greater than the stake owned by `delegator`. assert!( @@ -1619,15 +1668,15 @@ module supra_framework::pbo_delegation_pool { ); // Either the amount to lock can be covered by the `active` stake, in which case - // `reactivate_stake` will short-circuit, or the amount to lock can be covered + // `authorized_reactivate_stake` will short-circuit, or 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. - amount_to_reactivate = stake_to_lock - active; - reactivate_stake(delegator, pool_address, amount_to_reactivate); + let amount_to_reactivate = stake_to_lock - active; + authorized_reactivate_stake(delegator, pool_address, amount_to_reactivate); } ); - lock_existing_delegators_stakes(funder, pool_address, delegators, stakes_to_lock); + lock_existing_delegators_stakes(pool_address, delegators, stakes_to_lock); } ///CAUTION: This is to be used only in the rare circumstances where multisig_admin is convinced that a delegator was the @@ -1921,41 +1970,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 }; - // 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`. From e79ad04e708b1c454a415cdc9e237496f549583d Mon Sep 17 00:00:00 2001 From: Isaac Doidge Date: Sun, 29 Dec 2024 20:39:17 +1000 Subject: [PATCH 03/12] [#151] Removed requirement that delegation pool vesting start time be in the future. --- .../supra-framework/sources/pbo_delegation_pool.move | 5 ----- 1 file changed, 5 deletions(-) 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 878089d2ef53a..f773532abff26 100644 --- a/aptos-move/framework/supra-framework/sources/pbo_delegation_pool.move +++ b/aptos-move/framework/supra-framework/sources/pbo_delegation_pool.move @@ -786,11 +786,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 From d12df60bd4bc19c1e57f6bfbeac0c48bc53f1e34 Mon Sep 17 00:00:00 2001 From: Isaac Doidge Date: Mon, 30 Dec 2024 00:15:12 +1000 Subject: [PATCH 04/12] Fixed logic for locking up inactive stake. Added a unit test to cover the new functionality. --- .../sources/pbo_delegation_pool.move | 288 ++++++++++++++++-- 1 file changed, 267 insertions(+), 21 deletions(-) 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 f773532abff26..411676dd63c80 100644 --- a/aptos-move/framework/supra-framework/sources/pbo_delegation_pool.move +++ b/aptos-move/framework/supra-framework/sources/pbo_delegation_pool.move @@ -133,8 +133,6 @@ module supra_framework::pbo_delegation_pool { use supra_framework::staking_config; use supra_framework::timestamp; use supra_framework::multisig_account; - #[test_only] - use aptos_std::debug; const MODULE_SALT: vector = b"supra_framework::pbo_delegation_pool"; @@ -1539,7 +1537,7 @@ module supra_framework::pbo_delegation_pool { } } - /// Reactivates the `pending_inactive` and `inactive` stakes of `delegator`. + /// 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 @@ -1626,6 +1624,32 @@ module supra_framework::pbo_delegation_pool { ); } + /// 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), + ); + } + /// For each `delegator` in `delegators`, locks the amount of stake specified in the same index of `stakes_to_lock`. /// The locked amount is subject to the vesting schedule specified when the delegation pool corresponding /// to `pool_address` was created. Terminates with an error if any `stake_to_lock` exceeds the stake allocated to @@ -1649,6 +1673,9 @@ module supra_framework::pbo_delegation_pool { ); }; + // Synchronize the delegation and stake pools before any user operation. + synchronize_delegation_pool(pool_address); + // Ensure that each `delegator` has enough active stake to cover the corresponding `stake_to_lock`. vector::zip_reverse( delegators, @@ -1656,18 +1683,36 @@ module supra_framework::pbo_delegation_pool { |delegator, stake_to_lock| { let (active, inactive, pending_inactive) = get_stake(pool_address, delegator); - // Ensure that the amount to lock is greater than the stake owned by `delegator`. - assert!( - active + inactive + pending_inactive >= stake_to_lock, - error::invalid_state(EINSUFFICIENT_STAKE_TO_LOCK) - ); - - // Either the amount to lock can be covered by the `active` stake, in which case - // `authorized_reactivate_stake` will short-circuit, or 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 = stake_to_lock - active; - authorized_reactivate_stake(delegator, pool_address, amount_to_reactivate); + if (active < stake_to_lock) { + // Ensure that the amount to lock is greater than the stake owned by `delegator`. + assert!( + active + inactive + pending_inactive >= stake_to_lock, + error::invalid_state(EINSUFFICIENT_STAKE_TO_LOCK) + ); + + // 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 = stake_to_lock - active; + // Reactivate the required amount of `pending_inactive` stake first, if there is any. + authorized_reactivate_stake(delegator, pool_address, amount_to_reactivate); + let active_and_pending_inactive = active + pending_inactive; + + if (active_and_pending_inactive < stake_to_lock) { + // Also need to reactivate some of the `inactive` stake. + let amount_to_withdraw = stake_to_lock - active_and_pending_inactive; + // 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. } ); @@ -1974,17 +2019,21 @@ 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 @@ -2036,7 +2085,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 @@ -2084,7 +2133,7 @@ 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); } } @@ -9398,4 +9447,201 @@ module supra_framework::pbo_delegation_pool { ); assert!(unlock_coin, 20); } + + #[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 = 2 * 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_with_rounding_error, active); + assert!(inactive == 0, inactive); + assert!(pending_inactive == half_delegator_allocation, 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 delegators 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_with_rounding_error + half_epoch_reward, active); + assert!(inactive == half_delegator_allocation + half_epoch_reward, 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); + assert!(active == delegator_stake - add_stake_fee, active); + assert!(inactive == 0, inactive); + assert!(pending_inactive == 0, pending_inactive); + + // Ensure that the delegators stake is now subject to the pool vesting schedule. + assert!(is_principle_stakeholder(delegator, pool_address), 0); + } } From 9f789b768de63eb296a87eab4ac64f3c144e6b0d Mon Sep 17 00:00:00 2001 From: Isaac Doidge Date: Mon, 30 Dec 2024 00:19:27 +1000 Subject: [PATCH 05/12] Minor test improvement. --- .../supra-framework/sources/pbo_delegation_pool.move | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) 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 411676dd63c80..49e62655d19b3 100644 --- a/aptos-move/framework/supra-framework/sources/pbo_delegation_pool.move +++ b/aptos-move/framework/supra-framework/sources/pbo_delegation_pool.move @@ -9577,7 +9577,7 @@ module supra_framework::pbo_delegation_pool { assert!(inactive == 0, inactive); assert!(pending_inactive == 0, pending_inactive); - // Ensure that the delegators stake is now subject to the pool vesting schedule. + // Ensure that the delegator's stake is now subject to the pool vesting schedule. assert!(is_principle_stakeholder(delegator, pool_address), 0); // @@ -9641,7 +9641,9 @@ module supra_framework::pbo_delegation_pool { assert!(inactive == 0, inactive); assert!(pending_inactive == 0, pending_inactive); - // Ensure that the delegators stake is now subject to the pool vesting schedule. - assert!(is_principle_stakeholder(delegator, pool_address), 0); + // 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); } } From bfdaad9ed96e8d41be2c6e5abc65976484fe4972 Mon Sep 17 00:00:00 2001 From: Isaac Doidge Date: Tue, 31 Dec 2024 03:05:20 +1000 Subject: [PATCH 06/12] Changed lock_delegators_stakes so that the input stake amounts are now upper bounds on the total amount locked for the related delegator. Updated the tests accordingly. --- .../sources/pbo_delegation_pool.move | 163 +++++++++++++----- 1 file changed, 124 insertions(+), 39 deletions(-) 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 8af4bf41e93ea..0e6e61a756177 100644 --- a/aptos-move/framework/supra-framework/sources/pbo_delegation_pool.move +++ b/aptos-move/framework/supra-framework/sources/pbo_delegation_pool.move @@ -484,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 { @@ -1661,7 +1668,17 @@ module supra_framework::pbo_delegation_pool { *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 + } + ); } // else: The stake to be added is `0`. Do nothing. } @@ -1694,10 +1711,10 @@ module supra_framework::pbo_delegation_pool { ); } - /// For each `delegator` in `delegators`, locks the amount of stake specified in the same index of `stakes_to_lock`. - /// The locked amount is subject to the vesting schedule specified when the delegation pool corresponding - /// to `pool_address` was created. Terminates with an error if any `stake_to_lock` exceeds the stake allocated to - /// the corresponding `delegator` in the `DelegationPool` located at `pool_address`. + /// For each `delegator` in `delegators`, locks up to the amount of stake specified in the same index of + /// `max_stakes_to_lock`, which represents the upper bound on the total amount of locked stake that the + /// delegator should have when the function terminates. 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 @@ -1707,7 +1724,7 @@ module supra_framework::pbo_delegation_pool { multisig_admin: &signer, pool_address: address, delegators: vector
, - stakes_to_lock: vector + max_stakes_to_lock: vector ) acquires DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage { // Ensure that the caller is the admin of the delegation pool. { @@ -1721,46 +1738,74 @@ module supra_framework::pbo_delegation_pool { synchronize_delegation_pool(pool_address); // Ensure that each `delegator` has enough active stake to cover the corresponding `stake_to_lock`. - vector::zip_reverse( + let new_amounts_to_lock = vector::zip_map( delegators, - stakes_to_lock, - |delegator, stake_to_lock| { + max_stakes_to_lock, + |delegator, max_stake_to_lock| { let (active, inactive, pending_inactive) = get_stake(pool_address, delegator); + let already_locked = 0; + let pool: &mut DelegationPool = borrow_global_mut(pool_address); - if (active < stake_to_lock) { - // Ensure that the amount to lock is greater than the stake owned by `delegator`. - assert!( - active + inactive + pending_inactive >= stake_to_lock, - error::invalid_state(EINSUFFICIENT_STAKE_TO_LOCK) - ); + // See if the delegator already has a locked allocation. + if (table::contains(&pool.principle_stake, delegator)) { + already_locked = *table::borrow(&pool.principle_stake, delegator); + }; - // 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 = stake_to_lock - active; - // Reactivate the required amount of `pending_inactive` stake first, if there is any. - authorized_reactivate_stake(delegator, pool_address, amount_to_reactivate); - let active_and_pending_inactive = active + pending_inactive; - - if (active_and_pending_inactive < stake_to_lock) { - // Also need to reactivate some of the `inactive` stake. - let amount_to_withdraw = stake_to_lock - active_and_pending_inactive; - // 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); - } + if (max_stake_to_lock > already_locked) { + let max_new_stake_to_lock = max_stake_to_lock - already_locked; + // All `already_locked` stake must be `active`. + let lockable_active_stake = active - already_locked; + + // Ensure that all stake to be locked is made `active`. + if (lockable_active_stake < max_new_stake_to_lock) { + // 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 = max_new_stake_to_lock - lockable_active_stake; + + // 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; + }; + + // Reactivate the required amount of `pending_inactive` stake first, if there is any. + authorized_reactivate_stake(delegator, pool_address, amount_to_reactivate); + let active_and_pending_inactive = lockable_active_stake + pending_inactive; + + if (active_and_pending_inactive < max_new_stake_to_lock) { + // Need to reactivate some of the `inactive` stake. + let amount_to_withdraw = max_new_stake_to_lock - active_and_pending_inactive; + // 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. + + let lockable_total_stake = lockable_active_stake + inactive + pending_inactive; + + if (lockable_total_stake < max_new_stake_to_lock) { + // Not enough stake to meet the requested amount. Lock all available stake. + lockable_total_stake + } else { + max_new_stake_to_lock + } + } else { + // `max_stake_to_lock` has already been locked. + 0 } - // else: The amount to lock can be covered by the currently `active` stake. } ); - lock_existing_delegators_stakes(pool_address, delegators, stakes_to_lock); + lock_existing_delegators_stakes(pool_address, delegators, new_amounts_to_lock); } ///CAUTION: This is to be used only in the rare circumstances where multisig_admin is convinced that a delegator was the @@ -9684,7 +9729,8 @@ module supra_framework::pbo_delegation_pool { // 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); - assert!(active == delegator_stake - add_stake_fee, active); + 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); @@ -9692,5 +9738,44 @@ module supra_framework::pbo_delegation_pool { 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. + // + + // 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 * 10; + lock_delegators_stakes( + &funder_signer, + pool_address, + vector[delegator], + vector[more_than_allocated_stake] + ); + + // 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 == expected_total_stake, delegator_principle_stake); } } From fbc7760f068122dfadef1f09c55af391e7b107f1 Mon Sep 17 00:00:00 2001 From: Isaac Doidge Date: Tue, 31 Dec 2024 18:34:35 +1000 Subject: [PATCH 07/12] Changed the behaviour of so that the specified amount is set as the new principle stake for the account. Added additional checks to the stake reactivation logic to address Dr Joshi's PR comments. --- .../sources/pbo_delegation_pool.move | 193 ++++++++---------- 1 file changed, 85 insertions(+), 108 deletions(-) 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 0e6e61a756177..5a1b34b4b4e09 100644 --- a/aptos-move/framework/supra-framework/sources/pbo_delegation_pool.move +++ b/aptos-move/framework/supra-framework/sources/pbo_delegation_pool.move @@ -986,16 +986,45 @@ module supra_framework::pbo_delegation_pool { delegators: vector
, stakes: vector ) acquires DelegationPool, BeneficiaryForOperator, GovernanceRecords, NextCommissionPercentage { - // Ensure that the caller is the admin of the delegation pool. { assert!( is_admin(signer::address_of(funder), pool_address), error::permission_denied(ENOT_AUTHORIZED) ); }; + let principle_stake_table = + &mut (borrow_global_mut(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); - lock_existing_delegators_stakes(pool_address, delegators, stakes); } public entry fun fund_delegators_with_stake( @@ -1638,53 +1667,6 @@ module supra_framework::pbo_delegation_pool { ); } - /// For each delegator in `delegators`, locks the amount of stake specified in the same index of `stakes`. - /// The locked amount is subject to the vesting schedule specified when the delegation pool corresponding - /// to `pool_address` was created. - /// - /// This function does not actually add the `stakes` to the stake pool, so must remain private and must - /// only be called either after ensuring that all `stakes` already exist in the `active` stake pool, or - /// will do so before the end of the transaction. - /// - /// This function also assumes that `multisig_admin` is the admin of the `DelegationPool` at `pool_address`. - /// The caller must ensure that this is true. - fun lock_existing_delegators_stakes( - pool_address: address, - delegators: vector
, - stakes: vector - ) acquires DelegationPool { - let principle_stake_table = - &mut (borrow_global_mut(pool_address).principle_stake); - - vector::zip_reverse( - delegators, - stakes, - |delegator, stake| { - if (stake > 0) { - // Compute the stake to be added. `principle_stake` must be added to 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 - } - ); - } - // else: The stake to be added is `0`. Do nothing. - } - ); - } - /// 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. /// @@ -1711,10 +1693,10 @@ module supra_framework::pbo_delegation_pool { ); } - /// For each `delegator` in `delegators`, locks up to the amount of stake specified in the same index of - /// `max_stakes_to_lock`, which represents the upper bound on the total amount of locked stake that the - /// delegator should have when the function terminates. The locked amount is subject to the vesting - /// schedule specified when the delegation pool corresponding to `pool_address` was created. + /// 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 @@ -1724,7 +1706,7 @@ module supra_framework::pbo_delegation_pool { multisig_admin: &signer, pool_address: address, delegators: vector
, - max_stakes_to_lock: vector + new_principle_stakes: vector ) acquires DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage { // Ensure that the caller is the admin of the delegation pool. { @@ -1737,46 +1719,42 @@ module supra_framework::pbo_delegation_pool { // Synchronize the delegation and stake pools before any user operation. synchronize_delegation_pool(pool_address); - // Ensure that each `delegator` has enough active stake to cover the corresponding `stake_to_lock`. - let new_amounts_to_lock = vector::zip_map( + // Ensure that each `delegator` has an `active` stake balance that is as close to + // `principle_stake` as possible. + vector::zip_reverse( delegators, - max_stakes_to_lock, - |delegator, max_stake_to_lock| { + new_principle_stakes, + |delegator, principle_stake| { let (active, inactive, pending_inactive) = get_stake(pool_address, delegator); - let already_locked = 0; - let pool: &mut DelegationPool = borrow_global_mut(pool_address); - // See if the delegator already has a locked allocation. - if (table::contains(&pool.principle_stake, delegator)) { - already_locked = *table::borrow(&pool.principle_stake, 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 (max_stake_to_lock > already_locked) { - let max_new_stake_to_lock = max_stake_to_lock - already_locked; - // All `already_locked` stake must be `active`. - let lockable_active_stake = active - already_locked; - - // Ensure that all stake to be locked is made `active`. - if (lockable_active_stake < max_new_stake_to_lock) { - // 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 = max_new_stake_to_lock - lockable_active_stake; - - // 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; + // Reactivate the required amount of `pending_inactive` stake first, if there is any. + 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; }; - // Reactivate the required amount of `pending_inactive` stake first, if there is any. - authorized_reactivate_stake(delegator, pool_address, amount_to_reactivate); - let active_and_pending_inactive = lockable_active_stake + pending_inactive; - - if (active_and_pending_inactive < max_new_stake_to_lock) { - // Need to reactivate some of the `inactive` stake. - let amount_to_withdraw = max_new_stake_to_lock - active_and_pending_inactive; + if (amount_to_withdraw > 0) { // Withdraw the minimum required amount to the admin's address. admin_withdraw( multisig_admin, @@ -1786,26 +1764,24 @@ module supra_framework::pbo_delegation_pool { ); // 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. - - let lockable_total_stake = lockable_active_stake + inactive + pending_inactive; - - if (lockable_total_stake < max_new_stake_to_lock) { - // Not enough stake to meet the requested amount. Lock all available stake. - lockable_total_stake - } else { - max_new_stake_to_lock + } + } + }; + // 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 } - } else { - // `max_stake_to_lock` has already been locked. - 0 - } + ); } ); - - lock_existing_delegators_stakes(pool_address, delegators, new_amounts_to_lock); } ///CAUTION: This is to be used only in the rare circumstances where multisig_admin is convinced that a delegator was the @@ -9743,7 +9719,8 @@ module supra_framework::pbo_delegation_pool { // // 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. + // 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. @@ -9773,9 +9750,9 @@ module supra_framework::pbo_delegation_pool { vector[more_than_allocated_stake] ); - // Ensure that the delegator's stake is now subject to the pool vesting schedule. + // 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 == expected_total_stake, delegator_principle_stake); + assert!(delegator_principle_stake == more_than_allocated_stake, delegator_principle_stake); } } From a748a3befc7c4f8184339f24381c101dc3028370 Mon Sep 17 00:00:00 2001 From: Isaac Doidge Date: Tue, 31 Dec 2024 18:55:55 +1000 Subject: [PATCH 08/12] Generated file. --- .../src/aptos_framework_sdk_builder.rs | 34 ++++++++++--------- 1 file changed, 18 insertions(+), 16 deletions(-) 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 d71bcb435a783..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,10 +550,10 @@ pub enum EntryFunctionCall { stakes: Vec, }, - /// For each `delegator` in `delegators`, locks the amount of stake specified in the same index of `stakes_to_lock`. - /// The locked amount is subject to the vesting schedule specified when the delegation pool corresponding - /// to `pool_address` was created. Terminates with an error if any `stake_to_lock` exceeds the stake allocated to - /// the corresponding `delegator` in the `DelegationPool` located at `pool_address`. + /// 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 @@ -562,7 +562,7 @@ pub enum EntryFunctionCall { PboDelegationPoolLockDelegatorsStakes { pool_address: AccountAddress, delegators: Vec, - stakes_to_lock: Vec, + new_principle_stakes: Vec, }, /// Move `amount` of coins from pending_inactive to active. @@ -1533,10 +1533,12 @@ impl EntryFunctionCall { PboDelegationPoolLockDelegatorsStakes { pool_address, delegators, - stakes_to_lock, - } => { - pbo_delegation_pool_lock_delegators_stakes(pool_address, delegators, stakes_to_lock) - }, + new_principle_stakes, + } => pbo_delegation_pool_lock_delegators_stakes( + pool_address, + delegators, + new_principle_stakes, + ), PboDelegationPoolReactivateStake { pool_address, amount, @@ -3334,10 +3336,10 @@ pub fn pbo_delegation_pool_fund_delegators_with_stake( )) } -/// For each `delegator` in `delegators`, locks the amount of stake specified in the same index of `stakes_to_lock`. -/// The locked amount is subject to the vesting schedule specified when the delegation pool corresponding -/// to `pool_address` was created. Terminates with an error if any `stake_to_lock` exceeds the stake allocated to -/// the corresponding `delegator` in the `DelegationPool` located at `pool_address`. +/// 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 @@ -3346,7 +3348,7 @@ pub fn pbo_delegation_pool_fund_delegators_with_stake( pub fn pbo_delegation_pool_lock_delegators_stakes( pool_address: AccountAddress, delegators: Vec, - stakes_to_lock: Vec, + new_principle_stakes: Vec, ) -> TransactionPayload { TransactionPayload::EntryFunction(EntryFunction::new( ModuleId::new( @@ -3361,7 +3363,7 @@ pub fn pbo_delegation_pool_lock_delegators_stakes( vec![ bcs::to_bytes(&pool_address).unwrap(), bcs::to_bytes(&delegators).unwrap(), - bcs::to_bytes(&stakes_to_lock).unwrap(), + bcs::to_bytes(&new_principle_stakes).unwrap(), ], )) } @@ -6045,7 +6047,7 @@ mod decoder { Some(EntryFunctionCall::PboDelegationPoolLockDelegatorsStakes { pool_address: bcs::from_bytes(script.args().get(0)?).ok()?, delegators: bcs::from_bytes(script.args().get(1)?).ok()?, - stakes_to_lock: bcs::from_bytes(script.args().get(2)?).ok()?, + new_principle_stakes: bcs::from_bytes(script.args().get(2)?).ok()?, }) } else { None From 287da914ff2d81b85eabf79570cf05094b5e0fcc Mon Sep 17 00:00:00 2001 From: Isaac Doidge Date: Tue, 31 Dec 2024 20:47:51 +1000 Subject: [PATCH 09/12] Addresses PR comments. --- .../sources/pbo_delegation_pool.move | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) 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 5a1b34b4b4e09..a4b20f88f56f1 100644 --- a/aptos-move/framework/supra-framework/sources/pbo_delegation_pool.move +++ b/aptos-move/framework/supra-framework/sources/pbo_delegation_pool.move @@ -1741,8 +1741,11 @@ module supra_framework::pbo_delegation_pool { amount_to_reactivate = pending_inactive; }; - // Reactivate the required amount of `pending_inactive` stake first, if there is any. - authorized_reactivate_stake(delegator, pool_address, amount_to_reactivate); + 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) { @@ -1754,7 +1757,7 @@ module supra_framework::pbo_delegation_pool { amount_to_withdraw = inactive; }; - if (amount_to_withdraw > 0) { + if (amount_to_withdraw > MIN_COINS_ON_SHARES_POOL) { // Withdraw the minimum required amount to the admin's address. admin_withdraw( multisig_admin, @@ -9597,7 +9600,7 @@ module supra_framework::pbo_delegation_pool { let delegator = @0x0216; let delegator_signer = account::create_signer_for_test(delegator); - let delegator_allocation = 2 * ONE_SUPRA; + 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; @@ -9626,9 +9629,9 @@ module supra_framework::pbo_delegation_pool { // 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_with_rounding_error, active); + assert!(active == half_delegator_allocation, active); assert!(inactive == 0, inactive); - assert!(pending_inactive == half_delegator_allocation, pending_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. @@ -9689,8 +9692,9 @@ module supra_framework::pbo_delegation_pool { // 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_with_rounding_error + half_epoch_reward, active); - assert!(inactive == half_delegator_allocation + half_epoch_reward, inactive); + 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 @@ -9742,7 +9746,7 @@ module supra_framework::pbo_delegation_pool { assert!(pending_inactive == 0, pending_inactive); // Attempt to lock more than the full allocation. - let more_than_allocated_stake = delegator_allocation * 10; + let more_than_allocated_stake = delegator_allocation * 2; lock_delegators_stakes( &funder_signer, pool_address, From ec7e9de370c5fceda2fbfe3ff15230afcc7a02cc Mon Sep 17 00:00:00 2001 From: Isaac Doidge Date: Tue, 31 Dec 2024 22:01:11 +1000 Subject: [PATCH 10/12] Restored test removed when resolving merge conflicts. --- .../sources/pbo_delegation_pool.move | 241 ++++++++++++++++++ 1 file changed, 241 insertions(+) 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 28716bd34b81b..bce4090fcb560 100644 --- a/aptos-move/framework/supra-framework/sources/pbo_delegation_pool.move +++ b/aptos-move/framework/supra-framework/sources/pbo_delegation_pool.move @@ -9818,4 +9818,245 @@ 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); + } } From 76faa3e48771f2f18983c908df191fff76cac451 Mon Sep 17 00:00:00 2001 From: Isaac Doidge Date: Tue, 31 Dec 2024 22:09:27 +1000 Subject: [PATCH 11/12] Updated generated docs. --- .../doc/pbo_delegation_pool.md | 1785 +++++++++++++---- 1 file changed, 1361 insertions(+), 424 deletions(-) diff --git a/aptos-move/framework/supra-framework/doc/pbo_delegation_pool.md b/aptos-move/framework/supra-framework/doc/pbo_delegation_pool.md index f46534674a0f6..0929cbb5b22bd 100644 --- a/aptos-move/framework/supra-framework/doc/pbo_delegation_pool.md +++ b/aptos-move/framework/supra-framework/doc/pbo_delegation_pool.md @@ -138,6 +138,7 @@ transferred to A - [Struct `DelegateVotingPowerEvent`](#0x1_pbo_delegation_pool_DelegateVotingPowerEvent) - [Struct `SetBeneficiaryForOperator`](#0x1_pbo_delegation_pool_SetBeneficiaryForOperator) - [Struct `CommissionPercentageChange`](#0x1_pbo_delegation_pool_CommissionPercentageChange) +- [Struct `UnlockScheduleApplied`](#0x1_pbo_delegation_pool_UnlockScheduleApplied) - [Constants](#@Constants_0) - [Function `owner_cap_exists`](#0x1_pbo_delegation_pool_owner_cap_exists) - [Function `get_owned_pool_address`](#0x1_pbo_delegation_pool_get_owned_pool_address) @@ -157,7 +158,11 @@ transferred to A - [Function `calculate_and_update_delegator_voter`](#0x1_pbo_delegation_pool_calculate_and_update_delegator_voter) - [Function `get_expected_stake_pool_address`](#0x1_pbo_delegation_pool_get_expected_stake_pool_address) - [Function `min_remaining_secs_for_commission_change`](#0x1_pbo_delegation_pool_min_remaining_secs_for_commission_change) +- [Function `initialize_delegation_pool_with_amount`](#0x1_pbo_delegation_pool_initialize_delegation_pool_with_amount) - [Function `initialize_delegation_pool`](#0x1_pbo_delegation_pool_initialize_delegation_pool) +- [Function `fund_delegators_with_locked_stake`](#0x1_pbo_delegation_pool_fund_delegators_with_locked_stake) +- [Function `fund_delegators_with_stake`](#0x1_pbo_delegation_pool_fund_delegators_with_stake) +- [Function `is_admin`](#0x1_pbo_delegation_pool_is_admin) - [Function `get_admin`](#0x1_pbo_delegation_pool_get_admin) - [Function `beneficiary_for_operator`](#0x1_pbo_delegation_pool_beneficiary_for_operator) - [Function `enable_partial_governance_voting`](#0x1_pbo_delegation_pool_enable_partial_governance_voting) @@ -187,9 +192,15 @@ transferred to A - [Function `set_delegated_voter`](#0x1_pbo_delegation_pool_set_delegated_voter) - [Function `delegate_voting_power`](#0x1_pbo_delegation_pool_delegate_voting_power) - [Function `add_stake_initialization`](#0x1_pbo_delegation_pool_add_stake_initialization) +- [Function `fund_delegator_stake`](#0x1_pbo_delegation_pool_fund_delegator_stake) - [Function `add_stake`](#0x1_pbo_delegation_pool_add_stake) - [Function `replace_in_smart_tables`](#0x1_pbo_delegation_pool_replace_in_smart_tables) +- [Function `authorized_reactivate_stake`](#0x1_pbo_delegation_pool_authorized_reactivate_stake) +- [Function `admin_withdraw`](#0x1_pbo_delegation_pool_admin_withdraw) +- [Function `lock_delegators_stakes`](#0x1_pbo_delegation_pool_lock_delegators_stakes) - [Function `replace_delegator`](#0x1_pbo_delegation_pool_replace_delegator) +- [Function `is_principle_stakeholder`](#0x1_pbo_delegation_pool_is_principle_stakeholder) +- [Function `get_principle_stake`](#0x1_pbo_delegation_pool_get_principle_stake) - [Function `cached_unlockable_balance`](#0x1_pbo_delegation_pool_cached_unlockable_balance) - [Function `can_principle_unlock`](#0x1_pbo_delegation_pool_can_principle_unlock) - [Function `unlock`](#0x1_pbo_delegation_pool_unlock) @@ -1208,6 +1219,46 @@ This struct should be stored in the delegation pool resource account. + + + + +## 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;
 }

From 650bebe98700356173d8bbddff2ddffcfedabc6f Mon Sep 17 00:00:00 2001
From: Isaac Doidge 
Date: Tue, 31 Dec 2024 22:30:40 +1000
Subject: [PATCH 12/12] Formatting.

---
 .../sources/pbo_delegation_pool.move          | 158 ++++++++++++------
 1 file changed, 104 insertions(+), 54 deletions(-)

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 bce4090fcb560..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,7 +248,7 @@ 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;
 
@@ -621,6 +621,7 @@ module supra_framework::pbo_delegation_pool {
         assert_delegation_pool_exists(pool_address);
         let pool = borrow_global(pool_address);
         let (lockup_cycle_ended, active, _, commission_active, commission_pending_inactive) =
+
             calculate_stake_pool_drift(pool);
 
         let total_active_shares = pool_u64::total_shares(&pool.active_shares);
@@ -1014,11 +1015,7 @@ module supra_framework::pbo_delegation_pool {
                     // 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
-                        }
+                        UnlockScheduleApplied { pool_address, delegator, amount: stake }
                     );
                 }
             }
@@ -1557,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);
 
@@ -1616,23 +1616,26 @@ 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 
+    /// 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. 
+    /// 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));
+        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);
 
@@ -1645,12 +1648,7 @@ module supra_framework::pbo_delegation_pool {
             amount
         );
         let observed_lockup_cycle = pool.observed_lockup_cycle;
-        amount = redeem_inactive_shares(
-            pool,
-            delegator,
-            amount,
-            observed_lockup_cycle
-        );
+        amount = redeem_inactive_shares(pool, delegator, amount, observed_lockup_cycle);
 
         stake::reactivate_stake(&retrieve_stake_pool_owner(pool), amount);
 
@@ -1669,13 +1667,16 @@ module supra_framework::pbo_delegation_pool {
 
     /// 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 
+    /// 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
+        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.
         {
@@ -1689,7 +1690,7 @@ module supra_framework::pbo_delegation_pool {
             borrow_global_mut(pool_address),
             delegator_address,
             amount,
-            signer::address_of(multisig_admin),
+            signer::address_of(multisig_admin)
         );
     }
 
@@ -1697,10 +1698,10 @@ module supra_framework::pbo_delegation_pool {
     /// 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 
+    /// 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,
@@ -1719,18 +1720,19 @@ module supra_framework::pbo_delegation_pool {
         // 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 
+        // 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);
+                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 
+                    // Only reactivate the required amount to avoid unnecessarily interfering with
                     // in-progress withdrawals.
                     let amount_to_reactivate = principle_stake - active;
 
@@ -1743,14 +1745,17 @@ module supra_framework::pbo_delegation_pool {
 
                     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);
+                        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;
+                        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) {
@@ -1766,9 +1771,14 @@ module supra_framework::pbo_delegation_pool {
                                 amount_to_withdraw
                             );
                             // Then allocate it to the delegator again.
-                            fund_delegator_stake(multisig_admin, pool_address, delegator, amount_to_withdraw);
+                            fund_delegator_stake(
+                                multisig_admin,
+                                pool_address,
+                                delegator,
+                                amount_to_withdraw
+                            );
                         }
-                    }                    
+                    }
                 };
                 // else: The amount to lock can be covered by the currently `active` stake.
 
@@ -1791,10 +1801,10 @@ module supra_framework::pbo_delegation_pool {
     /// 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 
+    /// 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,
@@ -2005,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;
@@ -2031,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!(
@@ -2094,14 +2117,17 @@ module supra_framework::pbo_delegation_pool {
             borrow_global_mut(pool_address),
             delegator_address,
             amount,
-            delegator_address,
+            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, recipient_address: address
+        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
@@ -2201,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, delegator_address);
+            withdraw_internal(
+                pool,
+                delegator_address,
+                MAX_U64,
+                delegator_address
+            );
         }
     }
 
@@ -9718,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);
     }
@@ -9903,8 +9934,10 @@ module supra_framework::pbo_delegation_pool {
         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;
+        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(
@@ -9931,7 +9964,10 @@ module supra_framework::pbo_delegation_pool {
         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);
+        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.
@@ -9963,7 +9999,10 @@ module supra_framework::pbo_delegation_pool {
         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);
+        let add_stake_fee =
+            get_add_stake_fee(
+                pool_address, half_delegator_allocation + half_epoch_reward
+            );
 
         // Fund another delegator.
         fund_delegators_with_stake(
@@ -9992,9 +10031,17 @@ module supra_framework::pbo_delegation_pool {
 
         // 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);
+        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!(
+            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
@@ -10019,7 +10066,6 @@ module supra_framework::pbo_delegation_pool {
         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
@@ -10037,7 +10083,8 @@ module supra_framework::pbo_delegation_pool {
 
         // 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;
+        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);
@@ -10057,6 +10104,9 @@ module supra_framework::pbo_delegation_pool {
         // 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);
+        assert!(
+            delegator_principle_stake == more_than_allocated_stake,
+            delegator_principle_stake
+        );
     }
 }