code/pallets/subtensor/src/staking/remove_stake.rs

remove_stake.rs

981 lines · 38,348 bytes · 19a6485969RawGitHub
use super::*;
use crate::subnets::dissolution::DissolveCleanupStatus;
use frame_support::weights::WeightMeter;
use num_traits::ToPrimitive;
use sp_std::collections::btree_map::BTreeMap;
use sp_std::collections::btree_set::BTreeSet;
use substrate_fixed::types::U96F32;
use subtensor_runtime_common::{AlphaBalance, NetUid, TaoBalance, Token};
use subtensor_swap_interface::{Order, SwapHandler};

impl<T: Config> Pallet<T> {
    /// The implementation for the extrinsic remove_stake: Removes stake from a hotkey account and adds it onto a coldkey.
    ///
    /// # Arguments
    /// * `origin`: The signature of the caller's coldkey.
    ///
    /// * `hotkey`: The associated hotkey account.
    ///
    /// * `netuid`: Subnetwork UID.
    ///
    /// * `alpha_unstaked`: The amount of stake to be removed from the staking account.
    ///
    /// # Events
    /// * `StakeRemoved`: On the successfully removing stake from the hotkey account.
    ///
    /// # Errors
    /// * `NotRegistered`: Thrown if the account we are attempting to unstake from is non existent.
    ///
    /// * `NonAssociatedColdKey`: Thrown if the coldkey does not own the hotkey we are unstaking from.
    ///
    /// * `NotEnoughStakeToWithdraw`: Thrown if there is not enough stake on the hotkey to withdwraw this amount.
    ///
    /// * `TxRateLimitExceeded`: Thrown if key has hit transaction rate limit.
    ///
    pub fn do_remove_stake(
        origin: OriginFor<T>,
        hotkey: T::AccountId,
        netuid: NetUid,
        alpha_unstaked: AlphaBalance,
    ) -> dispatch::DispatchResult {
        // 1. We check the transaction is signed by the caller and retrieve the T::AccountId coldkey information.
        let coldkey = ensure_signed(origin)?;
        log::debug!(
            "do_remove_stake( origin:{coldkey:?} hotkey:{hotkey:?}, netuid: {netuid:?}, alpha_unstaked:{alpha_unstaked:?} )"
        );

        Self::ensure_subtoken_enabled(netuid)?;

        // 1.1. Cap the alpha_unstaked at available Alpha because user might be paying transaxtion fees
        // in Alpha and their total is already reduced by now.
        let alpha_available =
            Self::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid);
        let alpha_unstaked = alpha_unstaked.min(alpha_available);
        Self::ensure_remove_stake_input_within_swap_limit(netuid, alpha_unstaked)?;

        // 2. Validate the user input
        Self::validate_remove_stake(
            &coldkey,
            &hotkey,
            netuid,
            alpha_unstaked,
            alpha_unstaked,
            false,
        )?;

        // 3. Swap the alpba to tao and update counters for this subnet.
        Self::unstake_from_subnet(
            &hotkey,
            &coldkey,
            &coldkey,
            netuid,
            alpha_unstaked,
            T::SwapInterface::min_price(),
            false,
        )?;

        // 5. If the stake is below the minimum, we clear the nomination from storage.
        Self::clear_small_nomination_if_required(&hotkey, &coldkey, netuid);

        // 6. Check if stake lowered below MinStake and remove Pending children if it did
        if Self::get_total_stake_for_hotkey(&hotkey) < StakeThreshold::<T>::get().into() {
            Self::get_all_subnet_netuids().iter().for_each(|netuid| {
                PendingChildKeys::<T>::remove(netuid, &hotkey);
            })
        }

        // Done and ok.
        Ok(())
    }

    /// The implementation for the extrinsic unstake_all: Removes all stake from a hotkey account across all subnets and adds it onto a coldkey.
    ///
    /// # Arguments
    /// * `origin`: The signature of the caller's coldkey.
    ///
    /// * `hotkey`: The associated hotkey account.
    ///
    /// # Events
    /// * `StakeRemoved`: On the successfully removing stake from the hotkey account.
    ///
    /// # Errors
    /// * `NotRegistered`: Thrown if the account we are attempting to unstake from is non existent.
    ///
    /// * `NonAssociatedColdKey`: Thrown if the coldkey does not own the hotkey we are unstaking from.
    ///
    /// * `NotEnoughStakeToWithdraw`: Thrown if there is not enough stake on the hotkey to withdraw this amount.
    ///
    /// * `TxRateLimitExceeded`: Thrown if key has hit transaction rate limit.
    ///
    pub fn do_unstake_all(origin: OriginFor<T>, hotkey: T::AccountId) -> dispatch::DispatchResult {
        // 1. We check the transaction is signed by the caller and retrieve the T::AccountId coldkey information.
        let coldkey = ensure_signed(origin)?;
        log::debug!("do_unstake_all( origin:{coldkey:?} hotkey:{hotkey:?} )");

        // 2. Ensure that the hotkey account exists this is only possible through registration.
        ensure!(
            Self::hotkey_account_exists(&hotkey),
            Error::<T>::HotKeyAccountNotExists
        );

        // 3. Get all netuids.
        let netuids = Self::get_all_subnet_netuids();
        log::debug!("All subnet netuids: {netuids:?}");

        // 4. Iterate through all subnets and remove stake.
        for netuid in netuids.into_iter() {
            if !SubtokenEnabled::<T>::get(netuid) {
                continue;
            }
            // Ensure that the hotkey has enough stake to withdraw.
            let alpha_unstaked =
                Self::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid);

            if Self::validate_remove_stake(
                &coldkey,
                &hotkey,
                netuid,
                alpha_unstaked,
                alpha_unstaked,
                false,
            )
            .is_err()
            {
                // Don't unstake from this netuid
                continue;
            }

            if !alpha_unstaked.is_zero() {
                // Swap the alpha to tao and update counters for this subnet.
                Self::unstake_from_subnet(
                    &hotkey,
                    &coldkey,
                    &coldkey,
                    netuid,
                    alpha_unstaked,
                    T::SwapInterface::min_price(),
                    false,
                )?;

                // If the stake is below the minimum, we clear the nomination from storage.
                Self::clear_small_nomination_if_required(&hotkey, &coldkey, netuid);
            }
        }

        // 5. Done and ok.
        Ok(())
    }

    /// The implementation for the extrinsic unstake_all: Removes all stake from a hotkey account across all subnets and adds it onto a coldkey.
    ///
    /// # Arguments
    /// * `origin`: The signature of the caller's coldkey.
    ///
    /// * `hotkey`: The associated hotkey account.
    ///
    /// # Events
    /// * `StakeRemoved`: On the successfully removing stake from the hotkey account.
    ///
    /// # Errors
    /// * `NotRegistered`: Thrown if the account we are attempting to unstake from is non existent.
    ///
    /// * `NonAssociatedColdKey`: Thrown if the coldkey does not own the hotkey we are unstaking from.
    ///
    /// * `NotEnoughStakeToWithdraw`: Thrown if there is not enough stake on the hotkey to withdraw this amount.
    ///
    /// * `TxRateLimitExceeded`: Thrown if key has hit transaction rate limit.
    ///
    pub fn do_unstake_all_alpha(
        origin: OriginFor<T>,
        hotkey: T::AccountId,
    ) -> dispatch::DispatchResult {
        // 1. We check the transaction is signed by the caller and retrieve the T::AccountId coldkey information.
        let coldkey = ensure_signed(origin)?;
        log::debug!("do_unstake_all( origin:{coldkey:?} hotkey:{hotkey:?} )");

        // 2. Ensure that the hotkey account exists this is only possible through registration.
        ensure!(
            Self::hotkey_account_exists(&hotkey),
            Error::<T>::HotKeyAccountNotExists
        );

        // 3. Get all netuids.
        let netuids = Self::get_all_subnet_netuids();
        log::debug!("All subnet netuids: {netuids:?}");

        // 4. Iterate through all subnets and remove stake.
        let mut total_tao_unstaked = TaoBalance::ZERO;
        for netuid in netuids.into_iter() {
            if !SubtokenEnabled::<T>::get(netuid) {
                continue;
            }
            // If not Root network.
            if !netuid.is_root() {
                // Ensure that the hotkey has enough stake to withdraw.
                let alpha_unstaked =
                    Self::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid);

                if Self::validate_remove_stake(
                    &coldkey,
                    &hotkey,
                    netuid,
                    alpha_unstaked,
                    alpha_unstaked,
                    false,
                )
                .is_err()
                {
                    // Don't unstake from this netuid
                    continue;
                }

                if !alpha_unstaked.is_zero() {
                    // Swap the alpha to tao and update counters for this subnet.
                    let tao_unstaked = Self::unstake_from_subnet(
                        &hotkey,
                        &coldkey,
                        &coldkey,
                        netuid,
                        alpha_unstaked,
                        T::SwapInterface::min_price(),
                        false,
                    )?;

                    // Increment total
                    total_tao_unstaked = total_tao_unstaked.saturating_add(tao_unstaked);

                    // If the stake is below the minimum, we clear the nomination from storage.
                    Self::clear_small_nomination_if_required(&hotkey, &coldkey, netuid);
                }
            }
        }

        // Stake into root.
        Self::stake_into_subnet(
            &hotkey,
            &coldkey,
            NetUid::ROOT,
            total_tao_unstaked,
            T::SwapInterface::max_price(),
            false,
        )?;

        // 5. Done and ok.
        Ok(())
    }

    /// The implementation for the extrinsic remove_stake_limit: Removes stake from
    /// a hotkey on a subnet with a price limit.
    ///
    /// In case if slippage occurs and the price shall move beyond the limit
    /// price, the staking order may execute only partially or not execute
    /// at all.
    ///
    /// # Arguments
    /// * `origin`: The signature of the caller's coldkey.
    ///
    /// * `hotkey`: The associated hotkey account.
    ///
    /// * `netuid`: Subnetwork UID.
    ///
    /// * `amount_unstaked`: The amount of stake to be added to the hotkey staking account.
    ///
    /// * `limit_price`: The limit price expressed in units of RAO per one Alpha.
    ///
    /// * `allow_partial`: Allows partial execution of the amount. If set to false, this becomes
    ///   fill or kill type of order.
    ///
    /// # Events
    /// * `StakeRemoved`: On the successfully removing stake from the hotkey account.
    ///
    /// # Errors
    /// * `NotRegistered`: Thrown if the account we are attempting to unstake from is non existent.
    ///
    /// * `NonAssociatedColdKey`: Thrown if the coldkey does not own the hotkey we are unstaking from.
    ///
    /// * `NotEnoughStakeToWithdraw`: Thrown if there is not enough stake on the hotkey to withdwraw this amount.
    ///
    pub fn do_remove_stake_limit(
        origin: OriginFor<T>,
        hotkey: T::AccountId,
        netuid: NetUid,
        alpha_unstaked: AlphaBalance,
        limit_price: TaoBalance,
        allow_partial: bool,
    ) -> dispatch::DispatchResult {
        // 1. We check the transaction is signed by the caller and retrieve the T::AccountId coldkey information.
        let coldkey = ensure_signed(origin)?;
        log::debug!(
            "do_remove_stake( origin:{coldkey:?} hotkey:{hotkey:?}, netuid: {netuid:?}, alpha_unstaked:{alpha_unstaked:?} )"
        );

        Self::ensure_remove_stake_input_within_swap_limit(netuid, alpha_unstaked)?;

        // 2. Calculate the maximum amount that can be executed with price limit
        let max_amount = Self::get_max_amount_remove(netuid, limit_price)?;
        let mut possible_alpha = alpha_unstaked;
        if possible_alpha > max_amount {
            possible_alpha = max_amount;
        }

        // 3. Validate the user input
        Self::validate_remove_stake(
            &coldkey,
            &hotkey,
            netuid,
            alpha_unstaked,
            max_amount,
            allow_partial,
        )?;

        // 4. Swap the alpha to tao and update counters for this subnet.
        Self::unstake_from_subnet(
            &hotkey,
            &coldkey,
            &coldkey,
            netuid,
            possible_alpha,
            limit_price,
            false,
        )?;

        // 5. If the stake is below the minimum, we clear the nomination from storage.
        Self::clear_small_nomination_if_required(&hotkey, &coldkey, netuid);

        // 6. Check if stake lowered below MinStake and remove Pending children if it did
        if Self::get_total_stake_for_hotkey(&hotkey) < StakeThreshold::<T>::get().into() {
            Self::get_all_subnet_netuids().iter().for_each(|netuid| {
                PendingChildKeys::<T>::remove(netuid, &hotkey);
            })
        }

        // Done and ok.
        Ok(())
    }

    // Returns the maximum amount of RAO that can be executed with price limit
    pub fn get_max_amount_remove(
        netuid: NetUid,
        limit_price: TaoBalance,
    ) -> Result<AlphaBalance, DispatchError> {
        // Corner case: root and stao
        // There's no slippage for root or stable subnets, so if limit price is 1e9 rao or
        // lower, then max_amount equals u64::MAX, otherwise it is 0.
        if netuid.is_root() || SubnetMechanism::<T>::get(netuid) == 0 {
            if limit_price <= 1_000_000_000.into() {
                return Ok(AlphaBalance::MAX);
            } else {
                return Ok(AlphaBalance::ZERO);
            }
        }

        // Use the largest supported input instead of probing the swap path with u64::MAX.
        let max_supported_input = SubnetAlphaIn::<T>::get(netuid).saturating_mul(1_000.into());
        let order = GetTaoForAlpha::<T>::with_amount(max_supported_input);
        let result = T::SwapInterface::swap(netuid.into(), order, limit_price.into(), false, true)
            .map(|r| r.amount_paid_in.saturating_add(r.fee_paid))?;

        Ok(result)
    }

    fn ensure_remove_stake_input_within_swap_limit(
        netuid: NetUid,
        amount: AlphaBalance,
    ) -> Result<(), Error<T>> {
        if !netuid.is_root() && SubnetMechanism::<T>::get(netuid) == 1 {
            let max_supported_input = SubnetAlphaIn::<T>::get(netuid).saturating_mul(1_000.into());
            ensure!(
                amount <= max_supported_input,
                Error::<T>::InsufficientLiquidity
            );
        }

        Ok(())
    }

    pub fn do_remove_stake_full_limit(
        origin: OriginFor<T>,
        hotkey: T::AccountId,
        netuid: NetUid,
        limit_price: Option<TaoBalance>,
    ) -> DispatchResult {
        ensure!(Self::if_subnet_exist(netuid), Error::<T>::SubnetNotExists);
        let coldkey = ensure_signed(origin.clone())?;

        let alpha_unstaked =
            Self::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid);

        if let Some(limit_price) = limit_price {
            Self::do_remove_stake_limit(origin, hotkey, netuid, alpha_unstaked, limit_price, false)
        } else {
            Self::do_remove_stake(origin, hotkey, netuid, alpha_unstaked)
        }
    }

    pub fn destroy_alpha_in_out_stakes(
        netuid: NetUid,
        weight_meter: &mut WeightMeter,
        status: &mut DissolveCleanupStatus,
    ) -> bool {
        let Some(total_alpha_value_u128) = status.subnet_total_alpha_value else {
            log::warn!("DissolveCleanupStatus.subnet_total_alpha_value not set");
            return false;
        };

        let Some(mut distributed_tao_value_u128) = status.subnet_distributed_tao else {
            log::warn!("DissolveCleanupStatus.subnet_distributed_tao not set");
            return false;
        };

        // Check if there is enought weight to complete all the operations in this function
        // It is the maximum weight that can be consumed by the function. including all potential reads and writes.
        let max_weight = T::DbWeight::get().reads_writes(20, 12);
        if !weight_meter.can_consume(max_weight) {
            return false;
        }
        weight_meter.consume(max_weight);
        let owner_coldkey: T::AccountId = SubnetOwner::<T>::get(netuid);
        let lock_cost: TaoBalance = Self::get_subnet_locked_balance(netuid);

        // Determine if this subnet is eligible for a lock refund (legacy).
        let reg_at: u64 = NetworkRegisteredAt::<T>::get(netuid);

        let start_block: u64 = NetworkRegistrationStartBlock::<T>::get();
        let should_refund_owner: bool = reg_at < start_block;

        let protocol_alpha_value_u128: u128 =
            SubnetProtocolAlpha::<T>::get(netuid).to_u64() as u128;

        let pot_tao: TaoBalance = SubnetTAO::<T>::get(netuid);
        let pot_u128: u128 = pot_tao.into();

        // Compute owner's received emission in TAO at current price (ONLY if we may refund).
        // We:
        //      - get the current alpha issuance,
        //      - apply owner fraction to get owner α,
        //      - price that α using a *simulated* AMM swap.
        let mut owner_emission_tao = TaoBalance::ZERO;
        if should_refund_owner && !lock_cost.is_zero() {
            let total_emitted_alpha_u128: u128 = Self::get_alpha_issuance(netuid).to_u64() as u128;

            if total_emitted_alpha_u128 > 0 {
                let owner_fraction: U96F32 = Self::get_float_subnet_owner_cut();
                let owner_alpha_u64 = U96F32::from_num(total_emitted_alpha_u128)
                    .saturating_mul(owner_fraction)
                    .floor()
                    .saturating_to_num::<u64>();

                owner_emission_tao = if owner_alpha_u64 > 0 {
                    let cur_price: U96F32 = U96F32::saturating_from_num(
                        T::SwapInterface::current_alpha_price(netuid.into()),
                    );
                    let val_u64 = U96F32::from_num(owner_alpha_u64)
                        .saturating_mul(cur_price)
                        .floor()
                        .saturating_to_num::<u64>();
                    val_u64.into()
                } else {
                    TaoBalance::ZERO
                };
            }
        }

        let mut protocol_tao_share = TaoBalance::ZERO;
        if protocol_alpha_value_u128 > 0 {
            let prod: u128 = pot_u128.saturating_mul(protocol_alpha_value_u128);
            let share_u128: u128 = prod.checked_div(total_alpha_value_u128).unwrap_or_default();
            protocol_tao_share = (share_u128.min(u128::from(u64::MAX)) as u64).into();
        }

        // Remove α‑in/α‑out counters (fully destroyed).
        SubnetAlphaIn::<T>::remove(netuid);
        SubnetAlphaOut::<T>::remove(netuid);
        SubnetProtocolAlpha::<T>::remove(netuid);

        // Clear the locked balance on the subnet.
        Self::set_subnet_locked_balance(netuid, TaoBalance::ZERO);

        // Finalize lock handling:
        //    - Legacy subnets (registered before NetworkRegistrationStartBlock) receive:
        //        refund = max(0, lock_cost(τ) − owner_received_emission_in_τ).
        //    - New subnets: no refund.
        let mut refund: TaoBalance = if should_refund_owner {
            lock_cost.saturating_sub(owner_emission_tao)
        } else {
            TaoBalance::ZERO
        };

        if !refund.is_zero()
            && let Some(subnet_account) = Self::get_subnet_account_id(netuid)
        {
            // Transfer maximum transferrable up to refund to owner
            let transferrable =
                Self::get_coldkey_balance(&subnet_account).saturating_sub(protocol_tao_share);

            distributed_tao_value_u128 = distributed_tao_value_u128.saturating_add(refund.into());

            if distributed_tao_value_u128 < pot_u128 {
                let final_leftover: u128 = pot_u128.saturating_sub(distributed_tao_value_u128);

                refund = refund.saturating_add(final_leftover.into());
            }
            // We do our best effort to refund owner to as full amount of refund as possible, but
            // we cannot fail new subnet registration, so the result is ignored.
            let _ = Self::transfer_tao(&subnet_account, &owner_coldkey, refund.min(transferrable));
        }

        // 9) Recycle TAO remaining on the subnet account, forgive errors.
        if let Some(subnet_account) = Self::get_subnet_account_id(netuid) {
            let remaining_subnet_balance = Self::get_keep_alive_balance(&subnet_account);
            if Self::recycle_tao(&subnet_account, remaining_subnet_balance).is_ok() {
                RAORecycledForRegistration::<T>::insert(netuid, remaining_subnet_balance);
            }
        }

        status.subnet_total_alpha_value = None;
        status.subnet_distributed_tao = None;
        SubnetTAO::<T>::remove(netuid);

        true
    }

    /// This function calculates the total alpha value for a subnet.
    /// It iterates through all hotkeys in the subnet and calculates the total alpha value.
    /// It returns true if all hotkeys are iterated, otherwise false.
    ///
    /// # Arguments
    /// * `netuid`: The subnet to calculate the total alpha value for.
    ///
    /// * `weight_meter`: The weight meter to consume the weight for the operation.
    ///
    /// # Returns
    /// * `bool`: True if all hotkeys are iterated, otherwise false.
    ///
    pub fn destroy_alpha_in_out_stakes_get_total_alpha_value(
        netuid: NetUid,
        weight_meter: &mut WeightMeter,
        last_key: Option<Vec<u8>>,
        status: &mut DissolveCleanupStatus,
    ) -> (bool, Option<Vec<u8>>) {
        let r = T::DbWeight::get().reads(1);
        let mut read_all = true;

        let mut total_alpha_value_u128: u128;

        if let Some(value) = status.subnet_total_alpha_value {
            total_alpha_value_u128 = value;
        } else {
            let reg_at: u64 = NetworkRegisteredAt::<T>::get(netuid);
            let tao_in_refund_deployment_block: u64 = TaoInRefundDeploymentBlock::<T>::get();

            // Legacy subnets keep the old dereg behavior: ignore SubnetAlphaIn.
            // New subnets include SubnetAlphaIn.
            let protocol_alpha_value_u128: u128 = if reg_at > tao_in_refund_deployment_block {
                SubnetAlphaIn::<T>::get(netuid)
                    .saturating_add(SubnetProtocolAlpha::<T>::get(netuid))
                    .to_u64() as u128
            } else {
                SubnetProtocolAlpha::<T>::get(netuid).to_u64() as u128
            };
            total_alpha_value_u128 = protocol_alpha_value_u128;
        }

        // Preserve the inbound cursor so a pass that only skips other-netuid rows (or
        // exhausts weight before finishing another matching hotkey) cannot return None
        // and restart from the beginning — that would double-count into
        // status.subnet_total_alpha_value.
        let mut last_completed_key = last_key.clone();
        // Once a hotkey's Alpha/AlphaV2 prefix scan is started, finish it even if the
        // remaining on_idle budget is exhausted. A single hotkey can have more prefix
        // entries across all subnets than one block's leftover weight; aborting mid-hotkey
        // and retrying forever livelocks dissolution.
        let mut exhausted = false;

        let iter = match last_key {
            Some(key) => TotalHotkeyAlpha::<T>::iter_from(key),
            None => TotalHotkeyAlpha::<T>::iter(),
        };

        for (hot, this_netuid, _) in iter {
            if exhausted || !weight_meter.can_consume(r) {
                read_all = false;
                break;
            }
            weight_meter.consume(r);

            if this_netuid != netuid {
                continue;
            }

            for (cold, this_netuid, share_u64f64) in Self::alpha_iter_single_prefix(&hot) {
                if weight_meter.can_consume(r) {
                    weight_meter.consume(r);
                } else {
                    exhausted = true;
                }

                if this_netuid != netuid {
                    continue;
                }

                // Primary: actual α value via share pool.
                let pool = Self::get_alpha_share_pool(hot.clone(), netuid);
                let actual_val_u64 = pool.try_get_value(&cold).unwrap_or(0);

                // Fallback: if pool uninitialized, treat raw Alpha share as value.
                let val_u64 = if actual_val_u64 == 0 {
                    u64::from(share_u64f64)
                } else {
                    actual_val_u64
                };

                if val_u64 > 0 {
                    let val_u128 = val_u64 as u128;
                    total_alpha_value_u128 = total_alpha_value_u128.saturating_add(val_u128);
                }
            }

            last_completed_key = Some(TotalHotkeyAlpha::<T>::hashed_key_for(&hot, netuid));
            if exhausted {
                read_all = false;
                break;
            }
        }

        status.subnet_total_alpha_value = Some(total_alpha_value_u128);

        (read_all, last_completed_key)
    }

    pub fn destroy_alpha_in_out_stakes_settle_stakes(
        netuid: NetUid,
        weight_meter: &mut WeightMeter,
        last_key: Option<Vec<u8>>,
        status: &mut DissolveCleanupStatus,
    ) -> (bool, Option<Vec<u8>>) {
        let r = T::DbWeight::get().reads(1);
        let w = T::DbWeight::get().writes(1);
        let weight_for_tansfer_tao = T::DbWeight::get().reads_writes(11, 3);
        let mut read_all = true;

        let mut stakers: Vec<(T::AccountId, T::AccountId, u128)> = Vec::new();
        let Some(total_alpha_value_u128) = status.subnet_total_alpha_value else {
            log::warn!("DissolveCleanupStatus.subnet_total_alpha_value not set");
            return (false, None);
        };
        let Some(mut distributed_tao_value_u128) = status.subnet_distributed_tao else {
            log::warn!("DissolveCleanupStatus.subnet_distributed_tao not set");
            return (false, None);
        };

        let mut hotkeys_in_subnet: Vec<T::AccountId> = Vec::new();
        let mut coldkeys = BTreeSet::<T::AccountId>::new();
        let mut last_completed_key = last_key.clone();
        // Finish the current hotkey even if weight runs out mid-prefix; otherwise a fat
        // Alpha/AlphaV2 hotkey prefix livelocks dissolution across blocks.
        let mut exhausted = false;

        let iter = match last_key {
            Some(key) => TotalHotkeyAlpha::<T>::iter_from(key),
            None => TotalHotkeyAlpha::<T>::iter(),
        };

        for (hot, this_netuid, _) in iter {
            if exhausted || !weight_meter.can_consume(r) {
                read_all = false;
                break;
            }
            weight_meter.consume(r);

            if this_netuid != netuid {
                continue;
            }
            hotkeys_in_subnet.push(hot.clone());

            let mut coldkey_value_vec: Vec<(T::AccountId, u128)> = Vec::new();

            // Drain the whole hotkey prefix once started. Weight is accounted when it
            // still fits; overshoot is allowed so the cursor can advance past this hotkey.
            for (cold, this_netuid, share_u64f64) in Self::alpha_iter_single_prefix(&hot) {
                let inner_reads = r.saturating_mul(2_u64);
                if weight_meter.can_consume(inner_reads) {
                    weight_meter.consume(inner_reads);
                } else {
                    exhausted = true;
                }
                if this_netuid != netuid {
                    continue;
                }

                // Primary: actual α value via share pool.
                let pool = Self::get_alpha_share_pool(hot.clone(), netuid);
                let actual_val_u64 = pool.try_get_value(&cold).unwrap_or(0);

                // Fallback: if pool uninitialized, treat raw Alpha share as value.
                let val_u64 = if actual_val_u64 == 0 {
                    u64::from(share_u64f64)
                } else {
                    actual_val_u64
                };

                if val_u64 > 0 {
                    let mut need_to_consume_weight = w;

                    // if the coldkey is not in the set, we need to consume the weight for the transfer_tao_from_subnet function call
                    if !coldkeys.contains(&cold) {
                        need_to_consume_weight =
                            need_to_consume_weight.saturating_add(weight_for_tansfer_tao);
                        coldkeys.insert(cold.clone());
                    }

                    if weight_meter.can_consume(need_to_consume_weight) {
                        weight_meter.consume(need_to_consume_weight);
                    } else {
                        exhausted = true;
                    }
                    let val_u128 = val_u64 as u128;
                    coldkey_value_vec.push((cold.clone(), val_u128));
                }
            }

            for (cold, value) in coldkey_value_vec {
                stakers.push((hot.clone(), cold, value));
            }
            last_completed_key = Some(TotalHotkeyAlpha::<T>::hashed_key_for(&hot, netuid));
            if exhausted {
                read_all = false;
                break;
            }
        }

        // total TAO in the subnet pool
        let pot_tao: TaoBalance = SubnetTAO::<T>::get(netuid);
        let pot_u64: u64 = pot_tao.into();

        struct Portion<A, C> {
            _hot: A,
            cold: C,
            share: u64, // TAO to credit to coldkey balance
            rem: u128,  // remainder for largest‑remainder method
        }
        let mut portions: Vec<Portion<_, _>> = Vec::with_capacity(stakers.len());

        // Pro‑rata distribution of the pot by α value (largest‑remainder),
        //    **credited directly to each staker's COLDKEY free balance**.
        if pot_u64 > 0 && total_alpha_value_u128 > 0 && !stakers.is_empty() {
            let pot_u128: u128 = pot_u64 as u128;

            let mut distributed: u128 = 0;
            let mut total_rem: u128 = 0;

            for (hot, cold, alpha_val) in &stakers {
                let prod: u128 = pot_u128.saturating_mul(*alpha_val);
                let share_u128: u128 = prod.checked_div(total_alpha_value_u128).unwrap_or_default();
                let share_u64: u64 = share_u128.min(u128::from(u64::MAX)) as u64;
                distributed = distributed.saturating_add(u128::from(share_u64));

                let rem: u128 = prod.checked_rem(total_alpha_value_u128).unwrap_or_default();
                total_rem = total_rem.saturating_add(rem);
                portions.push(Portion {
                    _hot: hot.clone(),
                    cold: cold.clone(),
                    share: share_u64,
                    rem,
                });
            }

            let leftover: u128 = total_rem
                .checked_div(total_alpha_value_u128)
                .unwrap_or_default();
            if leftover > 0 {
                portions.sort_by(|a, b| b.rem.cmp(&a.rem));
                let give: usize = core::cmp::min(leftover, portions.len() as u128) as usize;
                for p in portions.iter_mut().take(give) {
                    p.share = p.share.saturating_add(1);
                }
            }

            portions = portions
                .into_iter()
                .filter(|p| p.share > 0)
                .collect::<Vec<_>>();

            // Aggregate the transfer amount for each coldkey
            let mut transfer_map = BTreeMap::<T::AccountId, TaoBalance>::new();
            for p in portions {
                if transfer_map.contains_key(&p.cold) {
                    transfer_map.insert(
                        p.cold.clone(),
                        transfer_map
                            .get(&p.cold)
                            .unwrap_or(&TaoBalance::ZERO)
                            .saturating_add(p.share.into()),
                    );
                } else {
                    transfer_map.insert(p.cold.clone(), p.share.into());
                }
            }

            // Credit each share directly to coldkey free balance.
            for transfer in transfer_map.iter() {
                // Cannot fail the whole transaction if this transfer fails
                distributed_tao_value_u128 = distributed_tao_value_u128
                    .saturating_add(transfer.1.to_u128().unwrap_or(0_u128));
                let _ = Self::transfer_tao_from_subnet(netuid, transfer.0, *transfer.1);
            }
        }

        // ignore the weight for handling the final operation, we must set the correct status for the next run
        status.subnet_distributed_tao = Some(distributed_tao_value_u128);

        (read_all, last_completed_key)
    }

    pub fn destroy_alpha_in_out_stakes_clean_alpha(
        netuid: NetUid,
        weight_meter: &mut WeightMeter,
        last_key: Option<Vec<u8>>,
    ) -> (bool, Option<Vec<u8>>) {
        let r = T::DbWeight::get().reads(1);
        let w = T::DbWeight::get().writes(1);
        let mut read_all = true;

        // Same cursor preserve as settle/get_total: do not wipe last_key when a pass
        // only skips non-matching rows or runs out of weight before the next hotkey.
        let mut last_completed_key = last_key.clone();
        // Finish the current hotkey's Alpha/AlphaV2 cleanup even if weight is exhausted.
        let mut exhausted = false;

        let iter = match last_key {
            Some(key) => TotalHotkeyAlpha::<T>::iter_from(key),
            None => TotalHotkeyAlpha::<T>::iter(),
        };

        for (hot, this_netuid, _) in iter {
            let mut coldkeys: Vec<T::AccountId> = Vec::new();
            if exhausted || !weight_meter.can_consume(r) {
                read_all = false;
                break;
            }
            weight_meter.consume(r);

            if this_netuid != netuid {
                continue;
            }

            for (cold, this_netuid, _) in Self::alpha_iter_single_prefix(&hot) {
                if weight_meter.can_consume(r) {
                    weight_meter.consume(r);
                } else {
                    exhausted = true;
                }
                if this_netuid != netuid {
                    continue;
                }
                coldkeys.push(cold.clone());
            }

            let weight_for_all_remove = w.saturating_mul(coldkeys.len() as u64);
            if weight_meter.can_consume(weight_for_all_remove) {
                weight_meter.consume(weight_for_all_remove);
            } else {
                exhausted = true;
            }

            last_completed_key = Some(TotalHotkeyAlpha::<T>::hashed_key_for(&hot, netuid));

            for cold in coldkeys {
                Alpha::<T>::remove((&hot, &cold, netuid));
                AlphaV2::<T>::remove((&hot, &cold, netuid));
            }

            if exhausted {
                read_all = false;
                break;
            }
        }

        (read_all, last_completed_key)
    }

    pub fn destroy_alpha_in_out_stakes_clear_hotkey_totals(
        netuid: NetUid,
        weight_meter: &mut WeightMeter,
        last_key: Option<Vec<u8>>,
    ) -> (bool, Option<Vec<u8>>) {
        let iter = match last_key {
            Some(key) => TotalHotkeyAlpha::<T>::iter_from(key),
            None => TotalHotkeyAlpha::<T>::iter(),
        };

        let (read_all, last_item) = Self::remove_storage_entries_for_netuid(
            weight_meter,
            iter,
            |(_, nu, _)| *nu == netuid,
            |(hotkey, _, _)| hotkey,
            |hotkey| {
                TotalHotkeyAlpha::<T>::remove(hotkey, netuid);
                TotalHotkeyShares::<T>::remove(hotkey, netuid);
                TotalHotkeySharesV2::<T>::remove(hotkey, netuid);
            },
            3,
        );

        (
            read_all,
            last_item.map(|(hotkey, nu, _)| TotalHotkeyAlpha::<T>::hashed_key_for(&hotkey, nu)),
        )
    }

    pub fn destroy_alpha_in_out_stakes_clear_locks(
        netuid: NetUid,
        weight_meter: &mut WeightMeter,
        last_key: Option<Vec<u8>>,
    ) -> (bool, Option<Vec<u8>>) {
        let iter = match last_key {
            Some(key) => Lock::<T>::iter_from(key),
            None => Lock::<T>::iter(),
        };

        let (read_all, last_item) = Self::remove_storage_entries_for_netuid(
            weight_meter,
            iter,
            |((_, this_netuid, _), _)| *this_netuid == netuid,
            |((coldkey, _this_netuid, hotkey), _)| (coldkey, hotkey),
            |(coldkey, hotkey)| Lock::<T>::remove((coldkey.clone(), netuid, hotkey.clone())),
            1,
        );

        (
            read_all,
            last_item.map(|((coldkey, _, hotkey), _)| {
                Lock::<T>::hashed_key_for((&coldkey, netuid, &hotkey))
            }),
        )
    }

    pub fn destroy_alpha_in_out_stakes_clear_decaying_locks(
        netuid: NetUid,
        weight_meter: &mut WeightMeter,
        last_key: Option<Vec<u8>>,
    ) -> (bool, Option<Vec<u8>>) {
        let iter = match last_key {
            Some(key) => DecayingLock::<T>::iter_from(key),
            None => DecayingLock::<T>::iter(),
        };

        let (read_all, last_item) = Self::remove_storage_entries_for_netuid(
            weight_meter,
            iter,
            |(_, this_netuid, _)| *this_netuid == netuid,
            |(coldkey, _, _)| coldkey,
            |coldkey| DecayingLock::<T>::remove(coldkey, netuid),
            1,
        );

        (
            read_all,
            last_item.map(|(coldkey, nu, _)| DecayingLock::<T>::hashed_key_for(&coldkey, nu)),
        )
    }
}