From 2f7ea4b2ee2178f949f10ec0fbf8ed1297ad2624 Mon Sep 17 00:00:00 2001 From: Shamil Gadelshin Date: Tue, 9 Sep 2025 15:10:54 +0300 Subject: [PATCH 01/54] Add root claim infrastructure --- pallets/subtensor/src/coinbase/block_step.rs | 6 +- pallets/subtensor/src/lib.rs | 105 +++++++ pallets/subtensor/src/macros/dispatches.rs | 41 +++ pallets/subtensor/src/macros/events.rs | 30 ++ pallets/subtensor/src/staking/claim_root.rs | 311 +++++++++++++++++++ pallets/subtensor/src/staking/mod.rs | 1 + pallets/subtensor/src/staking/stake_utils.rs | 13 + 7 files changed, 506 insertions(+), 1 deletion(-) create mode 100644 pallets/subtensor/src/staking/claim_root.rs diff --git a/pallets/subtensor/src/coinbase/block_step.rs b/pallets/subtensor/src/coinbase/block_step.rs index 6a96090b05..8fd1814961 100644 --- a/pallets/subtensor/src/coinbase/block_step.rs +++ b/pallets/subtensor/src/coinbase/block_step.rs @@ -7,7 +7,8 @@ impl Pallet { /// Executes the necessary operations for each block. pub fn block_step() -> Result<(), &'static str> { let block_number: u64 = Self::get_current_block_as_u64(); - log::debug!("block_step for block: {block_number:?} "); + let last_block_hash: T::Hash = >::parent_hash(); + // --- 1. Adjust difficulties. Self::adjust_registration_terms_for_networks(); // --- 2. Get the current coinbase emission. @@ -21,6 +22,9 @@ impl Pallet { Self::run_coinbase(block_emission); // --- 4. Set pending children on the epoch; but only after the coinbase has been run. Self::try_set_pending_children(block_number); + // --- 5. Run auto-claim root divs. + Self::run_auto_claim_root_divs(last_block_hash); + // Return ok. Ok(()) } diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 2a0d7e1de0..3bccc112c6 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -307,6 +307,62 @@ pub mod pallet { /// ==== Staking + Accounts ==== /// ============================ + #[derive( + Encode, Decode, Default, TypeInfo, Clone, PartialEq, Eq, Debug, DecodeWithMemTracking, + )] + /// Enum for the per-coldkey root claim setting. + pub enum RootClaimTypeEnum { + /// Swap any alpha emission for TAO. + #[default] + Swap, + /// Keep all alpha emission. + Keep, + } + + /// Enum for the per-coldkey root claim frequency setting. + #[derive(Encode, Decode, Default, TypeInfo, Clone, PartialEq, Eq, Debug)] + pub enum RootClaimFrequencyEnum { + /// Claim automatically. + #[default] + Auto, + /// Only claim manually; Never automatically. + Manual, + } + + #[pallet::type_value] + /// Default minimum root claim amount. + /// This is the minimum amount of root claim that can be made. + /// Any amount less than this will not be claimed. + pub fn DefaultMinRootClaimAmount() -> u64 { + 500_000 + } + + #[pallet::type_value] + /// Default root claim type. + /// This is the type of root claim that will be made. + /// This is set by the user. Either swap to TAO or keep as alpha. + pub fn DefaultRootClaimType() -> RootClaimTypeEnum { + RootClaimTypeEnum::default() + } + + #[pallet::type_value] + /// Default root claim frequency. + /// This is the frequency of root claims for a coldkey. + /// This is set by the user. Either auto or manual. + pub fn DefaultRootClaimFrequency() -> RootClaimFrequencyEnum { + RootClaimFrequencyEnum::default() + } + + #[pallet::type_value] + /// Default number of root claims per claim call. + /// Ideally this is calculated using the number of staking coldkey + /// and the block time. + pub fn DefaultNumRootClaim() -> u64 { + // TODO: replace with size of staking coldkeys / 7200 + // i.e. once per day + 15 + } + #[pallet::type_value] /// Default value for zero. pub fn DefaultZeroU64() -> u64 { @@ -1769,6 +1825,55 @@ pub mod pallet { ValueQuery, >; + #[pallet::storage] // --- DMAP ( hot, netuid ) --> claimable_dividends | Root claimable dividends. + pub type RootClaimable = StorageDoubleMap< + _, + Blake2_128Concat, + T::AccountId, + Identity, + NetUid, + u64, + ValueQuery, + DefaultZeroU64, + >; + #[pallet::storage] // --- NMAP ( hot, cold, netuid ) --> claimable_debt | Returns a keys debt for claimable divs. + pub type RootDebt = StorageNMap< + _, + ( + NMapKey, // hot + NMapKey, // cold + NMapKey, // subnet + ), + I96F32, // Shares + ValueQuery, + >; + #[pallet::storage] // -- MAP ( cold ) --> root_claim_type enum + pub type RootClaimType = StorageMap< + _, + Blake2_128Concat, + T::AccountId, + RootClaimTypeEnum, + ValueQuery, + DefaultRootClaimType, + >; + #[pallet::storage] // -- MAP ( cold ) --> root_claim_frequency enum + pub type RootClaimFrequency = StorageMap< + _, + Blake2_128Concat, + T::AccountId, + RootClaimFrequencyEnum, + ValueQuery, + DefaultRootClaimFrequency, + >; + + #[pallet::storage] // --- MAP ( u64 ) --> coldkey | Maps coldkeys that have stake to an index + pub type StakingColdkeys = StorageMap<_, Identity, u64, T::AccountId, OptionQuery>; + + #[pallet::storage] // --- Value --> num_staking_coldkeys + pub type NumStakingColdkeys = StorageValue<_, u64, ValueQuery, DefaultZeroU64>; + #[pallet::storage] // --- Value --> num_root_claim | Number of coldkeys to claim each auto-claim. + pub type NumRootClaim = StorageValue<_, u64, ValueQuery, DefaultNumRootClaim>; + /// ============================= /// ==== EVM related storage ==== /// ============================= diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index e82fecb3e6..560c3a8d68 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -2075,5 +2075,46 @@ mod dispatches { Ok(()) } + + /// --- Claims the root emissions for a coldkey. + /// # Args: + /// * 'origin': (Origin): + /// - The signature of the caller's coldkey. + /// + /// # Event: + /// * RootClaimed; + /// - On the successfully claiming the root emissions for a coldkey. + /// + /// # Raises: + /// + #[pallet::call_index(116)] + #[pallet::weight((Weight::from_parts(200_000, 0).saturating_add(T::DbWeight::get().reads_writes(1, 2)), DispatchClass::Normal, Pays::Yes))] + pub fn claim_root(origin: OriginFor) -> DispatchResultWithPostInfo { + let coldkey: T::AccountId = ensure_signed(origin)?; + + let weight = Self::do_root_claim(coldkey); + Ok((Some(weight), Pays::Yes).into()) + } + + /// --- Sets the root claim type for the coldkey. + /// # Args: + /// * 'origin': (Origin): + /// - The signature of the caller's coldkey. + /// + /// # Event: + /// * RootClaimTypeSet; + /// - On the successfully setting the root claim type for the coldkey. + /// + #[pallet::call_index(117)] + #[pallet::weight((Weight::from_parts(45_000_000, 0).saturating_add(T::DbWeight::get().writes(1)), DispatchClass::Normal, Pays::Yes))] + pub fn set_root_claim_type( + origin: OriginFor, + new_root_claim_type: RootClaimTypeEnum, + ) -> DispatchResult { + let coldkey: T::AccountId = ensure_signed(origin)?; + + Self::change_root_claim_type(&coldkey, new_root_claim_type); + Ok(()) + } } } diff --git a/pallets/subtensor/src/macros/events.rs b/pallets/subtensor/src/macros/events.rs index 2fab5ecdb4..e4a84d04a4 100644 --- a/pallets/subtensor/src/macros/events.rs +++ b/pallets/subtensor/src/macros/events.rs @@ -413,5 +413,35 @@ mod events { /// - **netuid**: The network identifier. /// - **who**: The account ID of the user revealing the weights. TimelockedWeightsRevealed(NetUid, T::AccountId), + + /// Root emissions have been claimed for a coldkey on all subnets and hotkeys. + /// Parameters: + /// (coldkey) + RootClaimed { + /// Claim coldkey + coldkey: T::AccountId, + }, + + /// Root claim type for a coldkey has been set. + /// Parameters: + /// (coldkey, u8) + RootClaimTypeSet { + /// Claim coldkey + coldkey: T::AccountId, + + /// Claim type + root_claim_type: RootClaimTypeEnum, + }, + + /// Root claim frequency for a coldkey has been set. + /// Parameters: + /// (coldkey, u8) + RootClaimFrequencySet { + /// Claim coldkey + coldkey: T::AccountId, + + /// Claim type + root_claim_type: RootClaimTypeEnum, + }, } } diff --git a/pallets/subtensor/src/staking/claim_root.rs b/pallets/subtensor/src/staking/claim_root.rs new file mode 100644 index 0000000000..853165b5b9 --- /dev/null +++ b/pallets/subtensor/src/staking/claim_root.rs @@ -0,0 +1,311 @@ +use super::*; +use frame_support::weights::Weight; +use sp_core::Get; +use substrate_fixed::types::{I96F32, I110F18}; +use subtensor_swap_interface::SwapHandler; + +impl Pallet { + pub fn block_hash_to_indices(block_hash: T::Hash, k: u64, n: u64) -> Vec { + let block_hash_bytes = block_hash.as_ref(); + let mut indices: Vec = Vec::new(); + // k < n + let start_index: u64 = u64::from_be_bytes( + block_hash_bytes + .get(0..8) + .unwrap_or(&[0; 8]) + .try_into() + .unwrap_or([0; 8]), + ); + let mut last_idx = start_index; + for i in 0..k { + let bh_idx: usize = ((i.saturating_mul(8)) % 32) as usize; + let idx_step = u64::from_be_bytes( + block_hash_bytes + .get(bh_idx..(bh_idx.saturating_add(8))) + .unwrap_or(&[0; 8]) + .try_into() + .unwrap_or([0; 8]), + ); + let idx = last_idx + .saturating_add(idx_step) + .checked_rem(n) + .unwrap_or(0); + indices.push(idx); + last_idx = idx; + } + indices + } + + pub fn increase_root_claimable_for_hotkey_and_subnet( + hotkey: &T::AccountId, + netuid: NetUid, + amount: AlphaCurrency, + ) { + // Get total stake on this hotkey on root. + let total: I96F32 = + I96F32::saturating_from_num(Self::get_stake_for_hotkey_on_subnet(hotkey, netuid)); + + // Get increment + let increment: I96F32 = I96F32::saturating_from_num(amount) + .checked_div(total) + .unwrap_or(I96F32::saturating_from_num(0.0)); + + // Convert increment to u64, mapping negative values to 0 + let increment_u64: u64 = if increment.is_negative() { + 0 + } else { + increment.saturating_to_num::() + }; + + // Increment claimable for this subnet. + RootClaimable::::mutate(hotkey, netuid, |total| { + *total = total.saturating_add(increment_u64); + }); + } + + pub fn get_root_claimable_for_hotkey_coldkey( + hotkey: &T::AccountId, + coldkey: &T::AccountId, + netuid: NetUid, + ) -> I110F18 { + // Get this keys stake balance on root. + let root_stake: I110F18 = I110F18::saturating_from_num( + Self::get_stake_for_hotkey_and_coldkey_on_subnet(hotkey, coldkey, NetUid::ROOT), + ); + + // Get the total claimable_rate for this hotkey and this network + let claimable_rate: I110F18 = + I110F18::saturating_from_num(RootClaimable::::get(hotkey, netuid)); + + // Compute the proportion owed to this coldkey via balance. + let claimable: I110F18 = claimable_rate.saturating_mul(root_stake); + + claimable + } + + pub fn get_root_owed_for_hotkey_coldkey_float( + hotkey: &T::AccountId, + coldkey: &T::AccountId, + netuid: NetUid, + ) -> I110F18 { + let claimable = Self::get_root_claimable_for_hotkey_coldkey(hotkey, coldkey, netuid); + + // Attain the claimable debt to avoid overclaiming. + let debt: I110F18 = + I110F18::saturating_from_num(RootDebt::::get((hotkey, coldkey, netuid))); + + // Substract the debt. + let owed: I110F18 = claimable.saturating_sub(debt); + + owed + } + + pub fn get_root_owed_for_hotkey_coldkey( + hotkey: &T::AccountId, + coldkey: &T::AccountId, + netuid: NetUid, + ) -> u64 { + let owed = Self::get_root_owed_for_hotkey_coldkey_float(hotkey, coldkey, netuid); + + // Convert owed to u64, mapping negative values to 0 + let owed_u64: u64 = if owed.is_negative() { + 0 + } else { + owed.saturating_to_num::() + }; + + owed_u64 + } + + pub fn root_claim_on_subnet( + hotkey: &T::AccountId, + coldkey: &T::AccountId, + netuid: NetUid, + root_claim_type: RootClaimTypeEnum, + ) { + // Substract the debt. + let owed: I110F18 = Self::get_root_owed_for_hotkey_coldkey_float(hotkey, coldkey, netuid); + + if owed == 0 || owed < I110F18::saturating_from_num(DefaultMinRootClaimAmount::::get()) { + return; // no-op + } + + // Convert owed to u64, mapping negative values to 0 + let owed_u64: u64 = if owed.is_negative() { + 0 + } else { + owed.saturating_to_num::() + }; + + if owed_u64 == 0 { + return; // no-op + } + + match root_claim_type { + // Increase stake on root + RootClaimTypeEnum::Swap => { + // Swap the alpha owed to TAO + let Ok(owed_tao) = Self::swap_alpha_for_tao( + netuid, + owed_u64.into(), + T::SwapInterface::max_price().into(), + false, + ) else { + return; // no-op + }; + + Self::increase_stake_for_hotkey_and_coldkey_on_subnet( + hotkey, + coldkey, + NetUid::ROOT, + owed_tao.amount_paid_out.into(), + ); + } + RootClaimTypeEnum::Keep => { + // Increase the stake with the alpha owned + Self::increase_stake_for_hotkey_and_coldkey_on_subnet( + hotkey, + coldkey, + netuid, + owed_u64.into(), + ); + } + }; + + // Increase root debt by owed amount. + RootDebt::::mutate((hotkey, coldkey, netuid), |debt| { + *debt = debt.saturating_add(owed.saturating_to_num::()); + }); + } + + // TODO: weight + fn root_claim_on_subnet_weight(_root_claim_type: RootClaimTypeEnum) -> Weight { + Weight::default() + } + pub fn root_claim_all(hotkey: &T::AccountId, coldkey: &T::AccountId) -> Weight { + let mut weight = Weight::default(); + + weight.saturating_accrue(T::DbWeight::get().reads(1)); + let root_claim_type = RootClaimType::::get(coldkey); + + // Iterate over all the subnets this hotkey has claimable for root. + RootClaimable::::iter_prefix(hotkey).for_each(|(netuid, _)| { + weight.saturating_accrue(T::DbWeight::get().reads(1)); + weight.saturating_accrue(Self::root_claim_on_subnet_weight(root_claim_type.clone())); + + Self::root_claim_on_subnet(hotkey, coldkey, netuid, root_claim_type.clone()); + }); + + weight + } + + pub fn add_stake_adjust_debt_for_hotkey_and_coldkey( + hotkey: &T::AccountId, + coldkey: &T::AccountId, + amount: u64, + ) { + // Iterate over all the subnets this hotkey is staked on for root. + for (netuid, claimable_rate) in RootClaimable::::iter_prefix(hotkey) { + // Get the total claimable_rate for this hotkey and this network + let claimable_rate_float = I110F18::saturating_from_num(claimable_rate); + + // Get current staker-debt. + let debt: I110F18 = + I110F18::saturating_from_num(RootDebt::::get((hotkey, coldkey, netuid))); + + // Increase debt based on the claimable rate. + let new_debt: I110F18 = debt.saturating_add( + claimable_rate_float.saturating_mul(I110F18::saturating_from_num(amount)), + ); + + // Set the new debt. + RootDebt::::insert( + (hotkey, coldkey, netuid), + new_debt.saturating_to_num::(), + ); + } + } + + pub fn remove_stake_adjust_debt_for_hotkey_and_coldkey( + hotkey: &T::AccountId, + coldkey: &T::AccountId, + amount: AlphaCurrency, + ) { + // Iterate over all the subnets this hotkey is staked on for root. + for (netuid, claimable_rate) in RootClaimable::::iter_prefix(hotkey) { + if netuid == NetUid::ROOT.into() { + continue; // Skip the root netuid. + } + + // Get the total claimable_rate for this hotkey and this network + let claimable_rate_float = I110F18::saturating_from_num(claimable_rate); + + // Get current staker-debt. + let debt: I110F18 = + I110F18::saturating_from_num(RootDebt::::get((hotkey, coldkey, netuid))); + + // Decrease debt based on the claimable rate. + let new_debt: I110F18 = debt.saturating_sub( + claimable_rate_float.saturating_mul(I110F18::saturating_from_num(amount)), + ); + + // Set the new debt. + RootDebt::::insert( + (hotkey, coldkey, netuid), + new_debt.saturating_to_num::(), + ); + } + } + + pub fn do_root_claim(coldkey: T::AccountId) -> Weight { + let mut weight = Weight::default(); + + let hotkeys = StakingHotkeys::::get(&coldkey); + weight.saturating_accrue(T::DbWeight::get().reads(1)); + + hotkeys.iter().for_each(|hotkey| { + weight.saturating_accrue(T::DbWeight::get().reads(1)); + weight.saturating_accrue(Self::root_claim_all(hotkey, &coldkey)); + }); + + Self::deposit_event(Event::RootClaimed { coldkey }); + + weight + } + + // TODO: + fn block_hash_to_indices_weight(_k: u64, _n: u64) -> Weight { + Weight::default() + } + + pub fn run_auto_claim_root_divs(last_block_hash: T::Hash) -> Weight { + let mut weight: Weight = Weight::default(); + + let n = NumStakingColdkeys::::get(); + let k = NumRootClaim::::get(); + weight.saturating_accrue(T::DbWeight::get().reads(2)); + + let coldkeys_to_claim: Vec = Self::block_hash_to_indices(last_block_hash, k, n); + weight.saturating_accrue(Self::block_hash_to_indices_weight(k, n)); + + for i in coldkeys_to_claim.iter() { + weight.saturating_accrue(T::DbWeight::get().reads(1)); + if let Ok(coldkey) = StakingColdkeys::::try_get(i) { + weight.saturating_accrue(Self::do_root_claim(coldkey.clone())); + } + + continue; + } + + weight + } + + pub fn change_root_claim_type(coldkey: &T::AccountId, new_type: RootClaimTypeEnum) { + RootClaimType::::insert(coldkey.clone(), new_type.clone()); + + Self::deposit_event(Event::RootClaimTypeSet { + coldkey: coldkey.clone(), + root_claim_type: new_type, + }); + } +} diff --git a/pallets/subtensor/src/staking/mod.rs b/pallets/subtensor/src/staking/mod.rs index 570658631a..ad2b66189f 100644 --- a/pallets/subtensor/src/staking/mod.rs +++ b/pallets/subtensor/src/staking/mod.rs @@ -1,6 +1,7 @@ use super::*; pub mod account; pub mod add_stake; +mod claim_root; pub mod decrease_take; pub mod helpers; pub mod increase_take; diff --git a/pallets/subtensor/src/staking/stake_utils.rs b/pallets/subtensor/src/staking/stake_utils.rs index 528289ec0e..3bc18094c9 100644 --- a/pallets/subtensor/src/staking/stake_utils.rs +++ b/pallets/subtensor/src/staking/stake_utils.rs @@ -734,6 +734,12 @@ impl Pallet { Self::increase_stake_for_hotkey_and_coldkey_on_subnet(hotkey, coldkey, netuid, refund); } + // If this is a root-stake + if netuid == NetUid::ROOT { + // Adjust root debt for this hotkey and coldkey. + Self::remove_stake_adjust_debt_for_hotkey_and_coldkey(hotkey, coldkey, alpha); + } + // Step 3: Update StakingHotkeys if the hotkey's total alpha, across all subnets, is zero // TODO const: fix. // if Self::get_stake(hotkey, coldkey) == 0 { @@ -819,6 +825,13 @@ impl Pallet { Self::set_stake_operation_limit(hotkey, coldkey, netuid.into()); } + // If this is a root-stake + if netuid == NetUid::ROOT { + // Adjust root debt for this hotkey and coldkey. + let alpha = swap_result.amount_paid_out.into(); + Self::add_stake_adjust_debt_for_hotkey_and_coldkey(hotkey, coldkey, alpha); + } + // Deposit and log the staking event. Self::deposit_event(Event::StakeAdded( coldkey.clone(), From c579597d9d68f9f00a264ab773af39513d1dba57 Mon Sep 17 00:00:00 2001 From: Shamil Gadelshin Date: Fri, 12 Sep 2025 12:37:57 +0300 Subject: [PATCH 02/54] Alter coinbase.rs --- .../subtensor/src/coinbase/run_coinbase.rs | 134 ++++++++++-------- pallets/subtensor/src/lib.rs | 5 + 2 files changed, 82 insertions(+), 57 deletions(-) diff --git a/pallets/subtensor/src/coinbase/run_coinbase.rs b/pallets/subtensor/src/coinbase/run_coinbase.rs index 21fc9bd603..c6a0484e07 100644 --- a/pallets/subtensor/src/coinbase/run_coinbase.rs +++ b/pallets/subtensor/src/coinbase/run_coinbase.rs @@ -214,23 +214,32 @@ impl Pallet { // Get pending alpha as original alpha_out - root_alpha. let pending_alpha: U96F32 = alpha_out_i.saturating_sub(root_alpha); log::debug!("pending_alpha: {pending_alpha:?}"); - // Sell root emission through the pool (do not pay fees) + let subsidized: bool = *is_subsidized.get(netuid_i).unwrap_or(&false); if !subsidized { - let swap_result = Self::swap_alpha_for_tao( - *netuid_i, - tou64!(root_alpha).into(), - T::SwapInterface::min_price().into(), - true, - ); - if let Ok(ok_result) = swap_result { - let root_tao: u64 = ok_result.amount_paid_out; - // Accumulate root divs for subnet. - PendingRootDivs::::mutate(*netuid_i, |total| { - *total = total.saturating_add(root_tao.into()); - }); - } + PendingRootAlphaDivs::::mutate(*netuid_i, |total| { + *total = total.saturating_add(tou64!(root_alpha).into()); + }); } + + // // Sell root emission through the pool (do not pay fees) + // let subsidized: bool = *is_subsidized.get(netuid_i).unwrap_or(&false); + // if !subsidized { + // let swap_result = Self::swap_alpha_for_tao( + // *netuid_i, + // tou64!(root_alpha).into(), + // T::SwapInterface::min_price().into(), + // true, + // ); + // if let Ok(ok_result) = swap_result { + // let root_tao: u64 = ok_result.amount_paid_out; + // // Accumulate root divs for subnet. + // PendingRootDivs::::mutate(*netuid_i, |total| { + // *total = total.saturating_add(root_tao.into()); + // }); + // } + // } + // Accumulate alpha emission in pending. PendingAlphaSwapped::::mutate(*netuid_i, |total| { *total = total.saturating_add(tou64!(root_alpha).into()); @@ -266,8 +275,8 @@ impl Pallet { PendingEmission::::insert(netuid, AlphaCurrency::ZERO); // Get and drain the subnet pending root divs. - let pending_tao = PendingRootDivs::::get(netuid); - PendingRootDivs::::insert(netuid, TaoCurrency::ZERO); + let pending_root_alpha = PendingRootAlphaDivs::::get(netuid); + PendingRootAlphaDivs::::insert(netuid, AlphaCurrency::ZERO); // Get this amount as alpha that was swapped for pending root divs. let pending_swapped = PendingAlphaSwapped::::get(netuid); @@ -281,7 +290,7 @@ impl Pallet { Self::drain_pending_emission( netuid, pending_alpha, - pending_tao, + pending_root_alpha, pending_swapped, owner_cut, ); @@ -327,7 +336,7 @@ impl Pallet { pub fn calculate_dividend_distribution( pending_alpha: AlphaCurrency, - pending_tao: TaoCurrency, + pending_root_alpha: AlphaCurrency, tao_weight: U96F32, stake_map: BTreeMap, dividends: BTreeMap, @@ -338,7 +347,7 @@ impl Pallet { log::debug!("dividends: {dividends:?}"); log::debug!("stake_map: {stake_map:?}"); log::debug!("pending_alpha: {pending_alpha:?}"); - log::debug!("pending_tao: {pending_tao:?}"); + log::debug!("pending_root_alpha: {pending_root_alpha:?}"); log::debug!("tao_weight: {tao_weight:?}"); // Setup. @@ -390,22 +399,22 @@ impl Pallet { log::debug!("total_root_divs: {total_root_divs:?}"); log::debug!("total_alpha_divs: {total_alpha_divs:?}"); - // Compute root divs as TAO. Here we take - let mut tao_dividends: BTreeMap = BTreeMap::new(); + // Compute root alpha divs. Here we take + let mut root_alpha_dividends: BTreeMap = BTreeMap::new(); for (hotkey, root_divs) in root_dividends { // Root proportion. let root_share: U96F32 = root_divs.checked_div(total_root_divs).unwrap_or(zero); log::debug!("hotkey: {hotkey:?}, root_share: {root_share:?}"); // Root proportion in TAO - let root_tao: U96F32 = asfloat!(pending_tao).saturating_mul(root_share); + let root_tao: U96F32 = asfloat!(pending_root_alpha).saturating_mul(root_share); log::debug!("hotkey: {hotkey:?}, root_tao: {root_tao:?}"); // Record root dividends as TAO. - tao_dividends + root_alpha_dividends .entry(hotkey) .and_modify(|e| *e = root_tao) .or_insert(root_tao); } - log::debug!("tao_dividends: {tao_dividends:?}"); + log::debug!("root_alpha_dividends: {root_alpha_dividends:?}"); // Compute proportional alpha divs using the pending alpha and total alpha divs from the epoch. let mut prop_alpha_dividends: BTreeMap = BTreeMap::new(); @@ -425,7 +434,7 @@ impl Pallet { } log::debug!("prop_alpha_dividends: {prop_alpha_dividends:?}"); - (prop_alpha_dividends, tao_dividends) + (prop_alpha_dividends, root_alpha_dividends) } fn get_immune_owner_hotkeys(netuid: NetUid, coldkey: &T::AccountId) -> Vec { @@ -474,7 +483,7 @@ impl Pallet { owner_cut: AlphaCurrency, incentives: BTreeMap, alpha_dividends: BTreeMap, - tao_dividends: BTreeMap, + root_alpha_dividends: BTreeMap, ) { // Distribute the owner cut. if let Ok(owner_coldkey) = SubnetOwner::::try_get(netuid) { @@ -550,38 +559,49 @@ impl Pallet { TotalHotkeyAlphaLastEpoch::::insert(hotkey, netuid, total_hotkey_alpha); } - // Distribute root tao divs. - let _ = TaoDividendsPerSubnet::::clear_prefix(netuid, u32::MAX, None); - for (hotkey, mut root_tao) in tao_dividends { + // Distribute root alpha divs. + // TODO: TaoDividendsPerSubnet -> AlphaDividendsPerSubnet +// let _ = TaoDividendsPerSubnet::::clear_prefix(netuid, u32::MAX, None); + for (hotkey, mut root_alpha) in root_alpha_dividends { // Get take prop - let tao_take: U96F32 = Self::get_hotkey_take_float(&hotkey).saturating_mul(root_tao); + let alpha_take: U96F32 = Self::get_hotkey_take_float(&hotkey).saturating_mul(root_alpha); // Remove take prop from root_tao - root_tao = root_tao.saturating_sub(tao_take); + root_alpha = root_alpha.saturating_sub(alpha_take); // Give the validator their take. - log::debug!("hotkey: {hotkey:?} tao_take: {tao_take:?}"); - let validator_stake = Self::increase_stake_for_hotkey_and_coldkey_on_subnet( + log::debug!("hotkey: {hotkey:?} alpha_take: {alpha_take:?}"); + let _validator_stake = Self::increase_stake_for_hotkey_and_coldkey_on_subnet( &hotkey, &Owner::::get(hotkey.clone()), - NetUid::ROOT, - tou64!(tao_take).into(), + netuid, + tou64!(alpha_take).into(), ); + // Give rest to nominators. - log::debug!("hotkey: {hotkey:?} root_tao: {root_tao:?}"); Self::increase_stake_for_hotkey_on_subnet( &hotkey, - NetUid::ROOT, - tou64!(root_tao).into(), + netuid, + tou64!(root_alpha).into(), ); - // Record root dividends for this validator on this subnet. - TaoDividendsPerSubnet::::mutate(netuid, hotkey.clone(), |divs| { - *divs = divs.saturating_add(tou64!(root_tao).into()); - }); - // Update the total TAO on the subnet with root tao dividends. - SubnetTAO::::mutate(NetUid::ROOT, |total| { - *total = total - .saturating_add(validator_stake.to_u64().into()) - .saturating_add(tou64!(root_tao).into()); - }); + + Self::increase_root_claimable_for_hotkey_and_subnet( + &hotkey, + netuid, + tou64!(root_alpha).into(), + ); + + // TODO: TaoDividendsPerSubnet -> AlphaDividendsPerSubnet + // TODO: SubnetTAO -> SubnetAlphaIn ?? SubnetAlphaOut + + // // Record root dividends for this validator on this subnet. + // TaoDividendsPerSubnet::::mutate(netuid, hotkey.clone(), |divs| { + // *divs = divs.saturating_add(tou64!(root_tao).into()); + // }); + // // Update the total TAO on the subnet with root tao dividends. + // SubnetTAO::::mutate(NetUid::ROOT, |total| { + // *total = total + // .saturating_add(validator_stake.to_u64().into()) + // .saturating_add(tou64!(root_tao).into()); + // }); } } @@ -602,7 +622,7 @@ impl Pallet { pub fn calculate_dividend_and_incentive_distribution( netuid: NetUid, - pending_tao: TaoCurrency, + pending_root_alpha: AlphaCurrency, pending_validator_alpha: AlphaCurrency, hotkey_emission: Vec<(T::AccountId, AlphaCurrency, AlphaCurrency)>, tao_weight: U96F32, @@ -618,26 +638,26 @@ impl Pallet { let stake_map = Self::get_stake_map(netuid, dividends.keys().collect::>()); - let (alpha_dividends, tao_dividends) = Self::calculate_dividend_distribution( + let (alpha_dividends, root_alpha_dividends) = Self::calculate_dividend_distribution( pending_validator_alpha, - pending_tao, + pending_root_alpha, tao_weight, stake_map, dividends, ); - (incentives, (alpha_dividends, tao_dividends)) + (incentives, (alpha_dividends, root_alpha_dividends)) } pub fn drain_pending_emission( netuid: NetUid, pending_alpha: AlphaCurrency, - pending_tao: TaoCurrency, + pending_root_alpha: AlphaCurrency, pending_swapped: AlphaCurrency, owner_cut: AlphaCurrency, ) { log::debug!( - "Draining pending alpha emission for netuid {netuid:?}, pending_alpha: {pending_alpha:?}, pending_tao: {pending_tao:?}, pending_swapped: {pending_swapped:?}, owner_cut: {owner_cut:?}" + "Draining pending alpha emission for netuid {netuid:?}, pending_alpha: {pending_alpha:?}, pending_root_alpha: {pending_root_alpha:?}, pending_swapped: {pending_swapped:?}, owner_cut: {owner_cut:?}" ); let tao_weight = Self::get_tao_weight(); @@ -669,10 +689,10 @@ impl Pallet { pending_alpha }; - let (incentives, (alpha_dividends, tao_dividends)) = + let (incentives, (alpha_dividends, root_alpha_dividends)) = Self::calculate_dividend_and_incentive_distribution( netuid, - pending_tao, + pending_root_alpha, pending_validator_alpha, hotkey_emission, tao_weight, @@ -683,7 +703,7 @@ impl Pallet { owner_cut, incentives, alpha_dividends, - tao_dividends, + root_alpha_dividends, ); } diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 3bccc112c6..2718a98084 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -1332,6 +1332,11 @@ pub mod pallet { /// --- MAP ( netuid ) --> pending_root_emission pub type PendingRootDivs = StorageMap<_, Identity, NetUid, TaoCurrency, ValueQuery, DefaultZeroTao>; + + /// --- MAP ( netuid ) --> pending_root_emission + #[pallet::storage] + pub type PendingRootAlphaDivs = + StorageMap<_, Identity, NetUid, AlphaCurrency, ValueQuery, DefaultZeroAlpha>; #[pallet::storage] /// --- MAP ( netuid ) --> pending_alpha_swapped pub type PendingAlphaSwapped = From 6c614fe542ef636c286f97a619e60c322af0020d Mon Sep 17 00:00:00 2001 From: Shamil Gadelshin Date: Fri, 12 Sep 2025 12:37:50 +0300 Subject: [PATCH 03/54] Alter tests --- pallets/subtensor/src/tests/coinbase.rs | 62 +++++++++++++++---------- 1 file changed, 37 insertions(+), 25 deletions(-) diff --git a/pallets/subtensor/src/tests/coinbase.rs b/pallets/subtensor/src/tests/coinbase.rs index 30cef8556f..ac9bc50522 100644 --- a/pallets/subtensor/src/tests/coinbase.rs +++ b/pallets/subtensor/src/tests/coinbase.rs @@ -578,7 +578,7 @@ fn test_drain_base() { SubtensorModule::drain_pending_emission( 0.into(), AlphaCurrency::ZERO, - TaoCurrency::ZERO, + AlphaCurrency::ZERO, AlphaCurrency::ZERO, AlphaCurrency::ZERO, ) @@ -594,7 +594,7 @@ fn test_drain_base_with_subnet() { SubtensorModule::drain_pending_emission( netuid, AlphaCurrency::ZERO, - TaoCurrency::ZERO, + AlphaCurrency::ZERO, AlphaCurrency::ZERO, AlphaCurrency::ZERO, ) @@ -620,7 +620,7 @@ fn test_drain_base_with_subnet_with_single_staker_not_registered() { SubtensorModule::drain_pending_emission( netuid, pending_alpha.into(), - TaoCurrency::ZERO, + AlphaCurrency::ZERO, AlphaCurrency::ZERO, AlphaCurrency::ZERO, ); @@ -650,7 +650,7 @@ fn test_drain_base_with_subnet_with_single_staker_registered() { SubtensorModule::drain_pending_emission( netuid, pending_alpha, - TaoCurrency::ZERO, + AlphaCurrency::ZERO, AlphaCurrency::ZERO, AlphaCurrency::ZERO, ); @@ -695,7 +695,8 @@ fn test_drain_base_with_subnet_with_single_staker_registered_root_weight() { SubtensorModule::drain_pending_emission( netuid, pending_alpha, - pending_tao, + // pending_tao, + AlphaCurrency::ZERO, AlphaCurrency::ZERO, AlphaCurrency::ZERO, ); @@ -748,7 +749,7 @@ fn test_drain_base_with_subnet_with_two_stakers_registered() { SubtensorModule::drain_pending_emission( netuid, pending_alpha, - TaoCurrency::ZERO, + AlphaCurrency::ZERO, AlphaCurrency::ZERO, AlphaCurrency::ZERO, ); @@ -814,7 +815,8 @@ fn test_drain_base_with_subnet_with_two_stakers_registered_and_root() { SubtensorModule::drain_pending_emission( netuid, pending_alpha, - pending_tao, + // pending_tao, + AlphaCurrency::ZERO, AlphaCurrency::ZERO, AlphaCurrency::ZERO, ); @@ -901,7 +903,8 @@ fn test_drain_base_with_subnet_with_two_stakers_registered_and_root_different_am SubtensorModule::drain_pending_emission( netuid, pending_alpha, - pending_tao, + // pending_tao, + AlphaCurrency::ZERO, 0.into(), 0.into(), ); @@ -1001,7 +1004,8 @@ fn test_drain_base_with_subnet_with_two_stakers_registered_and_root_different_am SubtensorModule::drain_pending_emission( netuid, pending_alpha, - pending_tao, + // pending_tao, + AlphaCurrency::ZERO, AlphaCurrency::ZERO, AlphaCurrency::ZERO, ); @@ -1084,7 +1088,7 @@ fn test_drain_alpha_childkey_parentkey() { SubtensorModule::drain_pending_emission( netuid, pending_alpha, - TaoCurrency::ZERO, + AlphaCurrency::ZERO, AlphaCurrency::ZERO, AlphaCurrency::ZERO, ); @@ -1310,7 +1314,7 @@ fn test_get_root_children_drain() { SubtensorModule::drain_pending_emission( alpha, pending_alpha, - TaoCurrency::ZERO, + AlphaCurrency::ZERO, AlphaCurrency::ZERO, AlphaCurrency::ZERO, ); @@ -1334,7 +1338,8 @@ fn test_get_root_children_drain() { SubtensorModule::drain_pending_emission( alpha, pending_alpha, - pending_root1, + // pending_root1, + AlphaCurrency::ZERO, AlphaCurrency::ZERO, AlphaCurrency::ZERO, ); @@ -1361,7 +1366,8 @@ fn test_get_root_children_drain() { SubtensorModule::drain_pending_emission( alpha, pending_alpha, - pending_root2, + // pending_root2, + AlphaCurrency::ZERO, AlphaCurrency::ZERO, AlphaCurrency::ZERO, ); @@ -1463,7 +1469,7 @@ fn test_get_root_children_drain_half_proportion() { SubtensorModule::drain_pending_emission( alpha, pending_alpha, - TaoCurrency::ZERO, + AlphaCurrency::ZERO, AlphaCurrency::ZERO, AlphaCurrency::ZERO, ); @@ -1550,7 +1556,7 @@ fn test_get_root_children_drain_with_take() { SubtensorModule::drain_pending_emission( alpha, pending_alpha, - TaoCurrency::ZERO, + AlphaCurrency::ZERO, AlphaCurrency::ZERO, AlphaCurrency::ZERO, ); @@ -1638,7 +1644,7 @@ fn test_get_root_children_drain_with_half_take() { SubtensorModule::drain_pending_emission( alpha, pending_alpha, - TaoCurrency::ZERO, + AlphaCurrency::ZERO, AlphaCurrency::ZERO, AlphaCurrency::ZERO, ); @@ -2012,7 +2018,8 @@ fn test_calculate_dividend_distribution_totals() { let (alpha_dividends, tao_dividends) = SubtensorModule::calculate_dividend_distribution( pending_validator_alpha, - pending_tao, + // pending_tao, + AlphaCurrency::ZERO, tao_weight, stake_map, dividends, @@ -2055,7 +2062,8 @@ fn test_calculate_dividend_distribution_total_only_tao() { let (alpha_dividends, tao_dividends) = SubtensorModule::calculate_dividend_distribution( pending_validator_alpha, - pending_tao, + // pending_tao, + AlphaCurrency::ZERO, tao_weight, stake_map, dividends, @@ -2098,7 +2106,8 @@ fn test_calculate_dividend_distribution_total_no_tao_weight() { let (alpha_dividends, tao_dividends) = SubtensorModule::calculate_dividend_distribution( pending_validator_alpha, - pending_tao, + // pending_tao, + AlphaCurrency::ZERO, tao_weight, stake_map, dividends, @@ -2141,7 +2150,8 @@ fn test_calculate_dividend_distribution_total_only_alpha() { let (alpha_dividends, tao_dividends) = SubtensorModule::calculate_dividend_distribution( pending_validator_alpha, - pending_tao, + // pending_tao, + AlphaCurrency::ZERO, tao_weight, stake_map, dividends, @@ -2195,7 +2205,8 @@ fn test_calculate_dividend_and_incentive_distribution() { let (incentives, (alpha_dividends, tao_dividends)) = SubtensorModule::calculate_dividend_and_incentive_distribution( netuid, - pending_tao, + // pending_tao, + AlphaCurrency::ZERO, pending_validator_alpha, hotkey_emission, tao_weight, @@ -2245,7 +2256,8 @@ fn test_calculate_dividend_and_incentive_distribution_all_to_validators() { let (incentives, (alpha_dividends, tao_dividends)) = SubtensorModule::calculate_dividend_and_incentive_distribution( netuid, - pending_tao, + // pending_tao, + AlphaCurrency::ZERO, pending_validator_alpha, hotkey_emission, tao_weight, @@ -2428,7 +2440,7 @@ fn test_drain_pending_emission_no_miners_all_drained() { SubtensorModule::drain_pending_emission( netuid, emission, - TaoCurrency::ZERO, + AlphaCurrency::ZERO, AlphaCurrency::ZERO, AlphaCurrency::ZERO, ); @@ -2500,7 +2512,7 @@ fn test_drain_pending_emission_zero_emission() { SubtensorModule::drain_pending_emission( netuid, 0.into(), - TaoCurrency::ZERO, + AlphaCurrency::ZERO, AlphaCurrency::ZERO, AlphaCurrency::ZERO, ); @@ -2784,7 +2796,7 @@ fn test_drain_alpha_childkey_parentkey_with_burn() { SubtensorModule::drain_pending_emission( netuid, pending_alpha, - TaoCurrency::ZERO, + AlphaCurrency::ZERO, AlphaCurrency::ZERO, AlphaCurrency::ZERO, ); From e04c8e958a19f19c6949943b0fc6bee1ce6f79f5 Mon Sep 17 00:00:00 2001 From: Shamil Gadelshin Date: Fri, 12 Sep 2025 14:32:55 +0300 Subject: [PATCH 04/54] Remove comments --- pallets/subtensor/src/coinbase/run_coinbase.rs | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/pallets/subtensor/src/coinbase/run_coinbase.rs b/pallets/subtensor/src/coinbase/run_coinbase.rs index c6a0484e07..f11e2e5589 100644 --- a/pallets/subtensor/src/coinbase/run_coinbase.rs +++ b/pallets/subtensor/src/coinbase/run_coinbase.rs @@ -222,24 +222,6 @@ impl Pallet { }); } - // // Sell root emission through the pool (do not pay fees) - // let subsidized: bool = *is_subsidized.get(netuid_i).unwrap_or(&false); - // if !subsidized { - // let swap_result = Self::swap_alpha_for_tao( - // *netuid_i, - // tou64!(root_alpha).into(), - // T::SwapInterface::min_price().into(), - // true, - // ); - // if let Ok(ok_result) = swap_result { - // let root_tao: u64 = ok_result.amount_paid_out; - // // Accumulate root divs for subnet. - // PendingRootDivs::::mutate(*netuid_i, |total| { - // *total = total.saturating_add(root_tao.into()); - // }); - // } - // } - // Accumulate alpha emission in pending. PendingAlphaSwapped::::mutate(*netuid_i, |total| { *total = total.saturating_add(tou64!(root_alpha).into()); From f60799307a2bacfa3f03a2febe4a1f555476b4c8 Mon Sep 17 00:00:00 2001 From: Shamil Gadelshin Date: Fri, 12 Sep 2025 14:37:56 +0300 Subject: [PATCH 05/54] Change TaoDividendsPerSubnet -> AlphaDividendsPerSubnet --- pallets/subtensor/src/coinbase/run_coinbase.rs | 12 +++++------- pallets/subtensor/src/lib.rs | 2 ++ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pallets/subtensor/src/coinbase/run_coinbase.rs b/pallets/subtensor/src/coinbase/run_coinbase.rs index f11e2e5589..d08ca543a9 100644 --- a/pallets/subtensor/src/coinbase/run_coinbase.rs +++ b/pallets/subtensor/src/coinbase/run_coinbase.rs @@ -542,8 +542,6 @@ impl Pallet { } // Distribute root alpha divs. - // TODO: TaoDividendsPerSubnet -> AlphaDividendsPerSubnet -// let _ = TaoDividendsPerSubnet::::clear_prefix(netuid, u32::MAX, None); for (hotkey, mut root_alpha) in root_alpha_dividends { // Get take prop let alpha_take: U96F32 = Self::get_hotkey_take_float(&hotkey).saturating_mul(root_alpha); @@ -571,13 +569,13 @@ impl Pallet { tou64!(root_alpha).into(), ); - // TODO: TaoDividendsPerSubnet -> AlphaDividendsPerSubnet + // Record root dividends for this validator on this subnet. + AlphaDividendsPerSubnet::::mutate(netuid, hotkey.clone(), |divs| { + *divs = divs.saturating_add(tou64!(root_alpha).into()); + }); + // TODO: SubnetTAO -> SubnetAlphaIn ?? SubnetAlphaOut - // // Record root dividends for this validator on this subnet. - // TaoDividendsPerSubnet::::mutate(netuid, hotkey.clone(), |divs| { - // *divs = divs.saturating_add(tou64!(root_tao).into()); - // }); // // Update the total TAO on the subnet with root tao dividends. // SubnetTAO::::mutate(NetUid::ROOT, |total| { // *total = total diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 2718a98084..d1d93cdaed 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -1058,6 +1058,8 @@ pub mod pallet { ValueQuery, DefaultZeroAlpha, >; + + #[deprecated] #[pallet::storage] // --- DMAP ( netuid, hotkey ) --> u64 | Last total root dividend paid to this hotkey on this subnet. pub type TaoDividendsPerSubnet = StorageDoubleMap< _, From 8877c407c9480b6eaa4ad3da117993a2266c9946 Mon Sep 17 00:00:00 2001 From: Greg Zaitsev Date: Fri, 12 Sep 2025 12:22:25 -0400 Subject: [PATCH 06/54] Fix: PendingAlphaSwapped only needs to increase for non-subsidized subnets --- pallets/subtensor/src/coinbase/run_coinbase.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/pallets/subtensor/src/coinbase/run_coinbase.rs b/pallets/subtensor/src/coinbase/run_coinbase.rs index d08ca543a9..456069985a 100644 --- a/pallets/subtensor/src/coinbase/run_coinbase.rs +++ b/pallets/subtensor/src/coinbase/run_coinbase.rs @@ -220,12 +220,13 @@ impl Pallet { PendingRootAlphaDivs::::mutate(*netuid_i, |total| { *total = total.saturating_add(tou64!(root_alpha).into()); }); + + // Accumulate alpha emission in pending. + PendingAlphaSwapped::::mutate(*netuid_i, |total| { + *total = total.saturating_add(tou64!(root_alpha).into()); + }); } - // Accumulate alpha emission in pending. - PendingAlphaSwapped::::mutate(*netuid_i, |total| { - *total = total.saturating_add(tou64!(root_alpha).into()); - }); // Accumulate alpha emission in pending. PendingEmission::::mutate(*netuid_i, |total| { *total = total.saturating_add(tou64!(pending_alpha).into()); From e7605df7e98f26a16668cd8636ece4d9371608f0 Mon Sep 17 00:00:00 2001 From: Shamil Gadelshin Date: Tue, 16 Sep 2025 12:39:51 +0300 Subject: [PATCH 07/54] Update tests --- pallets/subtensor/src/tests/coinbase.rs | 37 +++++++++++-------------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/pallets/subtensor/src/tests/coinbase.rs b/pallets/subtensor/src/tests/coinbase.rs index ac9bc50522..7184dabf3c 100644 --- a/pallets/subtensor/src/tests/coinbase.rs +++ b/pallets/subtensor/src/tests/coinbase.rs @@ -689,14 +689,13 @@ fn test_drain_base_with_subnet_with_single_staker_registered_root_weight() { netuid, stake_before, ); - let pending_tao = TaoCurrency::from(1_000_000_000); let pending_alpha = AlphaCurrency::from(1_000_000_000); + let pending_root_alpha = AlphaCurrency::from(1_000_000_000); assert_eq!(SubnetTAO::::get(NetUid::ROOT), TaoCurrency::ZERO); SubtensorModule::drain_pending_emission( netuid, pending_alpha, - // pending_tao, - AlphaCurrency::ZERO, + pending_root_alpha, AlphaCurrency::ZERO, AlphaCurrency::ZERO, ); @@ -713,11 +712,10 @@ fn test_drain_base_with_subnet_with_single_staker_registered_root_weight() { 10, ); // Registered gets all alpha emission. close( - stake_before.to_u64() + pending_tao.to_u64(), + stake_before.to_u64(), root_after.into(), 10, - ); // Registered gets all tao emission - assert_eq!(SubnetTAO::::get(NetUid::ROOT), pending_tao); + ); // Registered doesn't get tao immediately }); } @@ -854,7 +852,6 @@ fn test_drain_base_with_subnet_with_two_stakers_registered_and_root() { root_after2.into(), 10, ); // Registered gets 1/2 tao emission - assert_eq!(SubnetTAO::::get(NetUid::ROOT), pending_tao); }); } @@ -2005,7 +2002,7 @@ fn test_calculate_dividend_distribution_totals() { let mut dividends: BTreeMap = BTreeMap::new(); let pending_validator_alpha = AlphaCurrency::from(183_123_567_452); - let pending_tao = TaoCurrency::from(837_120_949_872); + let pending_root_alpha = AlphaCurrency::from(837_120_949_872); let tao_weight: U96F32 = U96F32::saturating_from_num(0.18); // 18% let hotkeys = [U256::from(0), U256::from(1)]; @@ -2016,10 +2013,9 @@ fn test_calculate_dividend_distribution_totals() { dividends.insert(hotkeys[0], 77_783_738_u64.into()); dividends.insert(hotkeys[1], 19_283_940_u64.into()); - let (alpha_dividends, tao_dividends) = SubtensorModule::calculate_dividend_distribution( + let (alpha_dividends, root_alpha_dividends) = SubtensorModule::calculate_dividend_distribution( pending_validator_alpha, - // pending_tao, - AlphaCurrency::ZERO, + pending_root_alpha, tao_weight, stake_map, dividends, @@ -2027,7 +2023,7 @@ fn test_calculate_dividend_distribution_totals() { // Verify the total of each dividends type is close to the inputs. let total_alpha_dividends = alpha_dividends.values().sum::(); - let total_tao_dividends = tao_dividends.values().sum::(); + let total_root_alpha_dividends = root_alpha_dividends.values().sum::(); assert_abs_diff_eq!( total_alpha_dividends.saturating_to_num::(), @@ -2035,8 +2031,8 @@ fn test_calculate_dividend_distribution_totals() { epsilon = 1_000 ); assert_abs_diff_eq!( - total_tao_dividends.saturating_to_num::(), - pending_tao.to_u64(), + total_root_alpha_dividends.saturating_to_num::(), + pending_root_alpha.to_u64(), epsilon = 1_000 ); }); @@ -2049,7 +2045,7 @@ fn test_calculate_dividend_distribution_total_only_tao() { let mut dividends: BTreeMap = BTreeMap::new(); let pending_validator_alpha = AlphaCurrency::ZERO; - let pending_tao = TaoCurrency::from(837_120_949_872); + let pending_root_alpha = AlphaCurrency::from(837_120_949_872); let tao_weight: U96F32 = U96F32::saturating_from_num(0.18); // 18% let hotkeys = [U256::from(0), U256::from(1)]; @@ -2060,10 +2056,9 @@ fn test_calculate_dividend_distribution_total_only_tao() { dividends.insert(hotkeys[0], 77_783_738_u64.into()); dividends.insert(hotkeys[1], 19_283_940_u64.into()); - let (alpha_dividends, tao_dividends) = SubtensorModule::calculate_dividend_distribution( + let (alpha_dividends, root_alpha_dividends) = SubtensorModule::calculate_dividend_distribution( pending_validator_alpha, - // pending_tao, - AlphaCurrency::ZERO, + pending_root_alpha, tao_weight, stake_map, dividends, @@ -2071,7 +2066,7 @@ fn test_calculate_dividend_distribution_total_only_tao() { // Verify the total of each dividends type is close to the inputs. let total_alpha_dividends = alpha_dividends.values().sum::(); - let total_tao_dividends = tao_dividends.values().sum::(); + let total_root_alpha_dividends = root_alpha_dividends.values().sum::(); assert_abs_diff_eq!( total_alpha_dividends.saturating_to_num::(), @@ -2079,8 +2074,8 @@ fn test_calculate_dividend_distribution_total_only_tao() { epsilon = 1_000 ); assert_abs_diff_eq!( - total_tao_dividends.saturating_to_num::(), - pending_tao.to_u64(), + total_root_alpha_dividends.saturating_to_num::(), + pending_root_alpha.to_u64(), epsilon = 1_000 ); }); From c48a9c45760a1502b20d60673e5a97ff558bc5cb Mon Sep 17 00:00:00 2001 From: Shamil Gadelshin Date: Tue, 16 Sep 2025 12:40:05 +0300 Subject: [PATCH 08/54] Update coinbase.rs --- .../subtensor/src/coinbase/run_coinbase.rs | 29 +++++++------------ 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/pallets/subtensor/src/coinbase/run_coinbase.rs b/pallets/subtensor/src/coinbase/run_coinbase.rs index 456069985a..699133270a 100644 --- a/pallets/subtensor/src/coinbase/run_coinbase.rs +++ b/pallets/subtensor/src/coinbase/run_coinbase.rs @@ -388,14 +388,14 @@ impl Pallet { // Root proportion. let root_share: U96F32 = root_divs.checked_div(total_root_divs).unwrap_or(zero); log::debug!("hotkey: {hotkey:?}, root_share: {root_share:?}"); - // Root proportion in TAO - let root_tao: U96F32 = asfloat!(pending_root_alpha).saturating_mul(root_share); - log::debug!("hotkey: {hotkey:?}, root_tao: {root_tao:?}"); + // Root proportion in alpha + let root_alpha: U96F32 = asfloat!(pending_root_alpha).saturating_mul(root_share); + log::debug!("hotkey: {hotkey:?}, root_alpha: {root_alpha:?}"); // Record root dividends as TAO. root_alpha_dividends .entry(hotkey) - .and_modify(|e| *e = root_tao) - .or_insert(root_tao); + .and_modify(|e| *e = root_alpha) + .or_insert(root_alpha); } log::debug!("root_alpha_dividends: {root_alpha_dividends:?}"); @@ -558,11 +558,11 @@ impl Pallet { ); // Give rest to nominators. - Self::increase_stake_for_hotkey_on_subnet( - &hotkey, - netuid, - tou64!(root_alpha).into(), - ); + // Self::increase_stake_for_hotkey_on_subnet( + // &hotkey, + // netuid, + // tou64!(root_alpha).into(), + // ); Self::increase_root_claimable_for_hotkey_and_subnet( &hotkey, @@ -574,15 +574,6 @@ impl Pallet { AlphaDividendsPerSubnet::::mutate(netuid, hotkey.clone(), |divs| { *divs = divs.saturating_add(tou64!(root_alpha).into()); }); - - // TODO: SubnetTAO -> SubnetAlphaIn ?? SubnetAlphaOut - - // // Update the total TAO on the subnet with root tao dividends. - // SubnetTAO::::mutate(NetUid::ROOT, |total| { - // *total = total - // .saturating_add(validator_stake.to_u64().into()) - // .saturating_add(tou64!(root_tao).into()); - // }); } } From 9fa484a1f42c601e8bf4cd3616d8882181cff179 Mon Sep 17 00:00:00 2001 From: Shamil Gadelshin Date: Tue, 16 Sep 2025 13:04:40 +0300 Subject: [PATCH 09/54] Cargo fmt --- .../subtensor/src/coinbase/run_coinbase.rs | 3 +- pallets/subtensor/src/tests/coinbase.rs | 36 +++++++++---------- 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/pallets/subtensor/src/coinbase/run_coinbase.rs b/pallets/subtensor/src/coinbase/run_coinbase.rs index 699133270a..12d793ee3e 100644 --- a/pallets/subtensor/src/coinbase/run_coinbase.rs +++ b/pallets/subtensor/src/coinbase/run_coinbase.rs @@ -545,7 +545,8 @@ impl Pallet { // Distribute root alpha divs. for (hotkey, mut root_alpha) in root_alpha_dividends { // Get take prop - let alpha_take: U96F32 = Self::get_hotkey_take_float(&hotkey).saturating_mul(root_alpha); + let alpha_take: U96F32 = + Self::get_hotkey_take_float(&hotkey).saturating_mul(root_alpha); // Remove take prop from root_tao root_alpha = root_alpha.saturating_sub(alpha_take); // Give the validator their take. diff --git a/pallets/subtensor/src/tests/coinbase.rs b/pallets/subtensor/src/tests/coinbase.rs index 7184dabf3c..b44de66f02 100644 --- a/pallets/subtensor/src/tests/coinbase.rs +++ b/pallets/subtensor/src/tests/coinbase.rs @@ -711,11 +711,7 @@ fn test_drain_base_with_subnet_with_single_staker_registered_root_weight() { stake_after.into(), 10, ); // Registered gets all alpha emission. - close( - stake_before.to_u64(), - root_after.into(), - 10, - ); // Registered doesn't get tao immediately + close(stake_before.to_u64(), root_after.into(), 10); // Registered doesn't get tao immediately }); } @@ -2013,13 +2009,14 @@ fn test_calculate_dividend_distribution_totals() { dividends.insert(hotkeys[0], 77_783_738_u64.into()); dividends.insert(hotkeys[1], 19_283_940_u64.into()); - let (alpha_dividends, root_alpha_dividends) = SubtensorModule::calculate_dividend_distribution( - pending_validator_alpha, - pending_root_alpha, - tao_weight, - stake_map, - dividends, - ); + let (alpha_dividends, root_alpha_dividends) = + SubtensorModule::calculate_dividend_distribution( + pending_validator_alpha, + pending_root_alpha, + tao_weight, + stake_map, + dividends, + ); // Verify the total of each dividends type is close to the inputs. let total_alpha_dividends = alpha_dividends.values().sum::(); @@ -2056,13 +2053,14 @@ fn test_calculate_dividend_distribution_total_only_tao() { dividends.insert(hotkeys[0], 77_783_738_u64.into()); dividends.insert(hotkeys[1], 19_283_940_u64.into()); - let (alpha_dividends, root_alpha_dividends) = SubtensorModule::calculate_dividend_distribution( - pending_validator_alpha, - pending_root_alpha, - tao_weight, - stake_map, - dividends, - ); + let (alpha_dividends, root_alpha_dividends) = + SubtensorModule::calculate_dividend_distribution( + pending_validator_alpha, + pending_root_alpha, + tao_weight, + stake_map, + dividends, + ); // Verify the total of each dividends type is close to the inputs. let total_alpha_dividends = alpha_dividends.values().sum::(); From 7f5dba49c0a4e35178a434ca1ab48b062e041ff8 Mon Sep 17 00:00:00 2001 From: Shamil Gadelshin Date: Tue, 16 Sep 2025 13:04:46 +0300 Subject: [PATCH 10/54] Remove TaoDividendsPerSubnet --- pallets/subtensor/src/lib.rs | 13 ------------- pallets/subtensor/src/rpc_info/metagraph.rs | 6 ++++-- pallets/subtensor/src/rpc_info/stake_info.rs | 6 ++++-- pallets/subtensor/src/swap/swap_hotkey.rs | 10 +--------- pallets/subtensor/src/tests/coinbase.rs | 8 -------- pallets/subtensor/src/tests/swap_hotkey.rs | 9 --------- .../subtensor/src/tests/swap_hotkey_with_subnet.rs | 9 --------- 7 files changed, 9 insertions(+), 52 deletions(-) diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index d1d93cdaed..3d6c83a72e 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -1059,19 +1059,6 @@ pub mod pallet { DefaultZeroAlpha, >; - #[deprecated] - #[pallet::storage] // --- DMAP ( netuid, hotkey ) --> u64 | Last total root dividend paid to this hotkey on this subnet. - pub type TaoDividendsPerSubnet = StorageDoubleMap< - _, - Identity, - NetUid, - Blake2_128Concat, - T::AccountId, - TaoCurrency, - ValueQuery, - DefaultZeroTao, - >; - /// ================== /// ==== Coinbase ==== /// ================== diff --git a/pallets/subtensor/src/rpc_info/metagraph.rs b/pallets/subtensor/src/rpc_info/metagraph.rs index 7f9dc46bee..b14a37014a 100644 --- a/pallets/subtensor/src/rpc_info/metagraph.rs +++ b/pallets/subtensor/src/rpc_info/metagraph.rs @@ -635,7 +635,8 @@ impl Pallet { let mut tao_dividends_per_hotkey: Vec<(T::AccountId, Compact)> = vec![]; let mut alpha_dividends_per_hotkey: Vec<(T::AccountId, Compact)> = vec![]; for hotkey in hotkeys.clone() { - let tao_divs = TaoDividendsPerSubnet::::get(netuid, hotkey.clone()); + // Tao dividends were removed + let tao_divs = TaoCurrency::ZERO; let alpha_divs = AlphaDividendsPerSubnet::::get(netuid, hotkey.clone()); tao_dividends_per_hotkey.push((hotkey.clone(), tao_divs.into())); alpha_dividends_per_hotkey.push((hotkey.clone(), alpha_divs.into())); @@ -1336,7 +1337,8 @@ impl Pallet { let mut tao_dividends_per_hotkey: Vec<(T::AccountId, Compact)> = vec![]; for hotkey in hotkeys.clone() { - let tao_divs = TaoDividendsPerSubnet::::get(netuid, hotkey.clone()); + // Tao dividends were removed + let tao_divs = TaoCurrency::ZERO; tao_dividends_per_hotkey.push((hotkey.clone(), tao_divs.into())); } SelectiveMetagraph { diff --git a/pallets/subtensor/src/rpc_info/stake_info.rs b/pallets/subtensor/src/rpc_info/stake_info.rs index 84e9e6d6b7..cbe51cbb85 100644 --- a/pallets/subtensor/src/rpc_info/stake_info.rs +++ b/pallets/subtensor/src/rpc_info/stake_info.rs @@ -43,7 +43,8 @@ impl Pallet { continue; } let emission = AlphaDividendsPerSubnet::::get(*netuid_i, &hotkey_i); - let tao_emission = TaoDividendsPerSubnet::::get(*netuid_i, &hotkey_i); + // Tao dividends were removed + let tao_emission = TaoCurrency::ZERO; let is_registered: bool = Self::is_hotkey_registered_on_network(*netuid_i, hotkey_i); stake_info_for_coldkey.push(StakeInfo { @@ -101,7 +102,8 @@ impl Pallet { netuid, ); let emission = AlphaDividendsPerSubnet::::get(netuid, &hotkey_account); - let tao_emission = TaoDividendsPerSubnet::::get(netuid, &hotkey_account); + // Tao dividends were removed + let tao_emission = TaoCurrency::ZERO; let is_registered: bool = Self::is_hotkey_registered_on_network(netuid, &hotkey_account); Some(StakeInfo { diff --git a/pallets/subtensor/src/swap/swap_hotkey.rs b/pallets/subtensor/src/swap/swap_hotkey.rs index 0454b1dd16..78d73e3cba 100644 --- a/pallets/subtensor/src/swap/swap_hotkey.rs +++ b/pallets/subtensor/src/swap/swap_hotkey.rs @@ -552,15 +552,7 @@ impl Pallet { weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); // 8.3 Swap TaoDividendsPerSubnet - let old_hotkey_tao_dividends = TaoDividendsPerSubnet::::get(netuid, old_hotkey); - let new_hotkey_tao_dividends = TaoDividendsPerSubnet::::get(netuid, new_hotkey); - TaoDividendsPerSubnet::::remove(netuid, old_hotkey); - TaoDividendsPerSubnet::::insert( - netuid, - new_hotkey, - old_hotkey_tao_dividends.saturating_add(new_hotkey_tao_dividends), - ); - weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); + // Tao dividends were removed // 9. Swap Alpha // Alpha( hotkey, coldkey, netuid ) -> alpha diff --git a/pallets/subtensor/src/tests/coinbase.rs b/pallets/subtensor/src/tests/coinbase.rs index b44de66f02..2ae513f2c7 100644 --- a/pallets/subtensor/src/tests/coinbase.rs +++ b/pallets/subtensor/src/tests/coinbase.rs @@ -1370,20 +1370,12 @@ fn test_get_root_children_drain() { AlphaDividendsPerSubnet::::get(alpha, alice), AlphaCurrency::ZERO ); - assert_eq!( - TaoDividendsPerSubnet::::get(alpha, alice), - TaoCurrency::ZERO - ); // Bob makes it all. assert_abs_diff_eq!( AlphaDividendsPerSubnet::::get(alpha, bob), pending_alpha, epsilon = 1.into() ); - assert_eq!( - TaoDividendsPerSubnet::::get(alpha, bob), - pending_root2 - ); // The pending root dividends should be present in root subnet. assert_eq!( SubnetTAO::::get(NetUid::ROOT), diff --git a/pallets/subtensor/src/tests/swap_hotkey.rs b/pallets/subtensor/src/tests/swap_hotkey.rs index dae5a3f176..dff3607893 100644 --- a/pallets/subtensor/src/tests/swap_hotkey.rs +++ b/pallets/subtensor/src/tests/swap_hotkey.rs @@ -897,7 +897,6 @@ fn test_swap_stake_success() { TotalHotkeyShares::::insert(old_hotkey, netuid, shares); Alpha::::insert((old_hotkey, coldkey, netuid), U64F64::from_num(amount)); AlphaDividendsPerSubnet::::insert(netuid, old_hotkey, AlphaCurrency::from(amount)); - TaoDividendsPerSubnet::::insert(netuid, old_hotkey, TaoCurrency::from(amount)); // Perform the swap SubtensorModule::perform_hotkey_swap_on_all_subnets( @@ -948,14 +947,6 @@ fn test_swap_stake_success() { AlphaDividendsPerSubnet::::get(netuid, new_hotkey), amount.into() ); - assert_eq!( - TaoDividendsPerSubnet::::get(netuid, old_hotkey), - TaoCurrency::ZERO - ); - assert_eq!( - TaoDividendsPerSubnet::::get(netuid, new_hotkey), - amount.into() - ); }); } diff --git a/pallets/subtensor/src/tests/swap_hotkey_with_subnet.rs b/pallets/subtensor/src/tests/swap_hotkey_with_subnet.rs index 349c28903a..9362a5794e 100644 --- a/pallets/subtensor/src/tests/swap_hotkey_with_subnet.rs +++ b/pallets/subtensor/src/tests/swap_hotkey_with_subnet.rs @@ -950,7 +950,6 @@ fn test_swap_stake_success() { TotalHotkeyShares::::insert(old_hotkey, netuid, U64F64::from_num(shares)); Alpha::::insert((old_hotkey, coldkey, netuid), U64F64::from_num(amount)); AlphaDividendsPerSubnet::::insert(netuid, old_hotkey, AlphaCurrency::from(amount)); - TaoDividendsPerSubnet::::insert(netuid, old_hotkey, TaoCurrency::from(amount)); // Perform the swap System::set_block_number(System::block_number() + HotkeySwapOnSubnetInterval::get()); @@ -1002,14 +1001,6 @@ fn test_swap_stake_success() { AlphaDividendsPerSubnet::::get(netuid, new_hotkey), AlphaCurrency::from(amount) ); - assert_eq!( - TaoDividendsPerSubnet::::get(netuid, old_hotkey), - TaoCurrency::ZERO - ); - assert_eq!( - TaoDividendsPerSubnet::::get(netuid, new_hotkey), - amount.into() - ); }); } From 28e2f743cf640fb2db727a33470d30d3a2f8cda9 Mon Sep 17 00:00:00 2001 From: Shamil Gadelshin Date: Tue, 16 Sep 2025 13:15:44 +0300 Subject: [PATCH 11/54] Remove PendingAlphaSwapped --- .../subtensor/src/coinbase/run_coinbase.rs | 26 ++++------------ pallets/subtensor/src/lib.rs | 4 --- pallets/subtensor/src/tests/coinbase.rs | 30 ++----------------- 3 files changed, 7 insertions(+), 53 deletions(-) diff --git a/pallets/subtensor/src/coinbase/run_coinbase.rs b/pallets/subtensor/src/coinbase/run_coinbase.rs index 12d793ee3e..83903f921a 100644 --- a/pallets/subtensor/src/coinbase/run_coinbase.rs +++ b/pallets/subtensor/src/coinbase/run_coinbase.rs @@ -220,11 +220,6 @@ impl Pallet { PendingRootAlphaDivs::::mutate(*netuid_i, |total| { *total = total.saturating_add(tou64!(root_alpha).into()); }); - - // Accumulate alpha emission in pending. - PendingAlphaSwapped::::mutate(*netuid_i, |total| { - *total = total.saturating_add(tou64!(root_alpha).into()); - }); } // Accumulate alpha emission in pending. @@ -261,22 +256,12 @@ impl Pallet { let pending_root_alpha = PendingRootAlphaDivs::::get(netuid); PendingRootAlphaDivs::::insert(netuid, AlphaCurrency::ZERO); - // Get this amount as alpha that was swapped for pending root divs. - let pending_swapped = PendingAlphaSwapped::::get(netuid); - PendingAlphaSwapped::::insert(netuid, AlphaCurrency::ZERO); - // Get owner cut and drain. let owner_cut = PendingOwnerCut::::get(netuid); PendingOwnerCut::::insert(netuid, AlphaCurrency::ZERO); // Drain pending root divs, alpha emission, and owner cut. - Self::drain_pending_emission( - netuid, - pending_alpha, - pending_root_alpha, - pending_swapped, - owner_cut, - ); + Self::drain_pending_emission(netuid, pending_alpha, pending_root_alpha, owner_cut); } else { // Increment BlocksSinceLastStep::::mutate(netuid, |total| *total = total.saturating_add(1)); @@ -626,18 +611,17 @@ impl Pallet { netuid: NetUid, pending_alpha: AlphaCurrency, pending_root_alpha: AlphaCurrency, - pending_swapped: AlphaCurrency, owner_cut: AlphaCurrency, ) { log::debug!( - "Draining pending alpha emission for netuid {netuid:?}, pending_alpha: {pending_alpha:?}, pending_root_alpha: {pending_root_alpha:?}, pending_swapped: {pending_swapped:?}, owner_cut: {owner_cut:?}" + "Draining pending alpha emission for netuid {netuid:?}, pending_alpha: {pending_alpha:?}, pending_root_alpha: {pending_root_alpha:?}, owner_cut: {owner_cut:?}" ); let tao_weight = Self::get_tao_weight(); // Run the epoch. let hotkey_emission: Vec<(T::AccountId, AlphaCurrency, AlphaCurrency)> = - Self::epoch(netuid, pending_alpha.saturating_add(pending_swapped)); + Self::epoch(netuid, pending_alpha.saturating_add(pending_root_alpha)); log::debug!("hotkey_emission: {hotkey_emission:?}"); // Compute the pending validator alpha. @@ -654,9 +638,9 @@ impl Pallet { let pending_validator_alpha = if !incentive_sum.is_zero() { pending_alpha - .saturating_add(pending_swapped) + .saturating_add(pending_root_alpha) .saturating_div(2.into()) - .saturating_sub(pending_swapped) + .saturating_sub(pending_root_alpha) } else { // If the incentive is 0, then Validators get 100% of the alpha. pending_alpha diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 3d6c83a72e..de5dfc74f4 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -1327,10 +1327,6 @@ pub mod pallet { pub type PendingRootAlphaDivs = StorageMap<_, Identity, NetUid, AlphaCurrency, ValueQuery, DefaultZeroAlpha>; #[pallet::storage] - /// --- MAP ( netuid ) --> pending_alpha_swapped - pub type PendingAlphaSwapped = - StorageMap<_, Identity, NetUid, AlphaCurrency, ValueQuery, DefaultZeroAlpha>; - #[pallet::storage] /// --- MAP ( netuid ) --> pending_owner_cut pub type PendingOwnerCut = StorageMap<_, Identity, NetUid, AlphaCurrency, ValueQuery, DefaultZeroAlpha>; diff --git a/pallets/subtensor/src/tests/coinbase.rs b/pallets/subtensor/src/tests/coinbase.rs index 2ae513f2c7..104a06dccd 100644 --- a/pallets/subtensor/src/tests/coinbase.rs +++ b/pallets/subtensor/src/tests/coinbase.rs @@ -538,26 +538,20 @@ fn test_owner_cut_base() { // SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::coinbase::test_pending_swapped --exact --show-output --nocapture #[test] -fn test_pending_swapped() { +fn test_pending_emission() { new_test_ext(1).execute_with(|| { let netuid = NetUid::from(1); let emission: u64 = 1_000_000; add_network(netuid, 1, 0); mock::setup_reserves(netuid, 1_000_000.into(), 1.into()); SubtensorModule::run_coinbase(U96F32::from_num(0)); - assert_eq!(PendingAlphaSwapped::::get(netuid), 0.into()); // Zero tao weight and no root. SubnetTAO::::insert(NetUid::ROOT, TaoCurrency::from(1_000_000_000)); // Add root weight. SubtensorModule::run_coinbase(U96F32::from_num(0)); - assert_eq!(PendingAlphaSwapped::::get(netuid), 0.into()); // Zero tao weight with 1 root. SubtensorModule::set_tempo(netuid, 10000); // Large number (dont drain) SubtensorModule::set_tao_weight(u64::MAX); // Set TAO weight to 1.0 SubtensorModule::run_coinbase(U96F32::from_num(0)); // 1 TAO / ( 1 + 3 ) = 0.25 * 1 / 2 = 125000000 - assert_abs_diff_eq!( - u64::from(PendingAlphaSwapped::::get(netuid)), - 125000000, - epsilon = 1 - ); + assert_abs_diff_eq!( u64::from(PendingEmission::::get(netuid)), 1_000_000_000 - 125000000, @@ -580,7 +574,6 @@ fn test_drain_base() { AlphaCurrency::ZERO, AlphaCurrency::ZERO, AlphaCurrency::ZERO, - AlphaCurrency::ZERO, ) }); } @@ -596,7 +589,6 @@ fn test_drain_base_with_subnet() { AlphaCurrency::ZERO, AlphaCurrency::ZERO, AlphaCurrency::ZERO, - AlphaCurrency::ZERO, ) }); } @@ -622,7 +614,6 @@ fn test_drain_base_with_subnet_with_single_staker_not_registered() { pending_alpha.into(), AlphaCurrency::ZERO, AlphaCurrency::ZERO, - AlphaCurrency::ZERO, ); let stake_after = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid); @@ -652,7 +643,6 @@ fn test_drain_base_with_subnet_with_single_staker_registered() { pending_alpha, AlphaCurrency::ZERO, AlphaCurrency::ZERO, - AlphaCurrency::ZERO, ); let stake_after = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid); @@ -697,7 +687,6 @@ fn test_drain_base_with_subnet_with_single_staker_registered_root_weight() { pending_alpha, pending_root_alpha, AlphaCurrency::ZERO, - AlphaCurrency::ZERO, ); let stake_after = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid); @@ -745,7 +734,6 @@ fn test_drain_base_with_subnet_with_two_stakers_registered() { pending_alpha, AlphaCurrency::ZERO, AlphaCurrency::ZERO, - AlphaCurrency::ZERO, ); let stake_after1 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey1, &coldkey, netuid); @@ -812,7 +800,6 @@ fn test_drain_base_with_subnet_with_two_stakers_registered_and_root() { // pending_tao, AlphaCurrency::ZERO, AlphaCurrency::ZERO, - AlphaCurrency::ZERO, ); let stake_after1 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey1, &coldkey, netuid); @@ -899,7 +886,6 @@ fn test_drain_base_with_subnet_with_two_stakers_registered_and_root_different_am // pending_tao, AlphaCurrency::ZERO, 0.into(), - 0.into(), ); let stake_after1 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey1, &coldkey, netuid); @@ -1000,7 +986,6 @@ fn test_drain_base_with_subnet_with_two_stakers_registered_and_root_different_am // pending_tao, AlphaCurrency::ZERO, AlphaCurrency::ZERO, - AlphaCurrency::ZERO, ); let stake_after1 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey1, &coldkey, netuid); @@ -1083,7 +1068,6 @@ fn test_drain_alpha_childkey_parentkey() { pending_alpha, AlphaCurrency::ZERO, AlphaCurrency::ZERO, - AlphaCurrency::ZERO, ); let parent_stake_after = SubtensorModule::get_stake_for_hotkey_on_subnet(&parent, netuid); let child_stake_after = SubtensorModule::get_stake_for_hotkey_on_subnet(&child, netuid); @@ -1309,7 +1293,6 @@ fn test_get_root_children_drain() { pending_alpha, AlphaCurrency::ZERO, AlphaCurrency::ZERO, - AlphaCurrency::ZERO, ); // Alice and Bob both made half of the dividends. @@ -1334,7 +1317,6 @@ fn test_get_root_children_drain() { // pending_root1, AlphaCurrency::ZERO, AlphaCurrency::ZERO, - AlphaCurrency::ZERO, ); // Alice and Bob both made half of the dividends. @@ -1359,8 +1341,6 @@ fn test_get_root_children_drain() { SubtensorModule::drain_pending_emission( alpha, pending_alpha, - // pending_root2, - AlphaCurrency::ZERO, AlphaCurrency::ZERO, AlphaCurrency::ZERO, ); @@ -1456,7 +1436,6 @@ fn test_get_root_children_drain_half_proportion() { pending_alpha, AlphaCurrency::ZERO, AlphaCurrency::ZERO, - AlphaCurrency::ZERO, ); // Alice and Bob make the same amount. @@ -1543,7 +1522,6 @@ fn test_get_root_children_drain_with_take() { pending_alpha, AlphaCurrency::ZERO, AlphaCurrency::ZERO, - AlphaCurrency::ZERO, ); // Bob makes it all. @@ -1631,7 +1609,6 @@ fn test_get_root_children_drain_with_half_take() { pending_alpha, AlphaCurrency::ZERO, AlphaCurrency::ZERO, - AlphaCurrency::ZERO, ); // Alice and Bob make the same amount. @@ -2427,7 +2404,6 @@ fn test_drain_pending_emission_no_miners_all_drained() { emission, AlphaCurrency::ZERO, AlphaCurrency::ZERO, - AlphaCurrency::ZERO, ); // Get the new stake of the hotkey. @@ -2499,7 +2475,6 @@ fn test_drain_pending_emission_zero_emission() { 0.into(), AlphaCurrency::ZERO, AlphaCurrency::ZERO, - AlphaCurrency::ZERO, ); // Get the new stake of the hotkey. @@ -2783,7 +2758,6 @@ fn test_drain_alpha_childkey_parentkey_with_burn() { pending_alpha, AlphaCurrency::ZERO, AlphaCurrency::ZERO, - AlphaCurrency::ZERO, ); let parent_stake_after = SubtensorModule::get_stake_for_hotkey_on_subnet(&parent, netuid); let child_stake_after = SubtensorModule::get_stake_for_hotkey_on_subnet(&child, netuid); From eb3223705b1823f1db9f5e6f9572d0d757e3348d Mon Sep 17 00:00:00 2001 From: Shamil Gadelshin Date: Tue, 16 Sep 2025 14:47:53 +0300 Subject: [PATCH 12/54] Rename RootDebt -> RootClaimed --- pallets/subtensor/src/lib.rs | 8 ++- pallets/subtensor/src/staking/claim_root.rs | 64 +++++++++----------- pallets/subtensor/src/staking/stake_utils.rs | 8 +-- 3 files changed, 36 insertions(+), 44 deletions(-) diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index de5dfc74f4..948abc0d0b 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -1826,15 +1826,17 @@ pub mod pallet { ValueQuery, DefaultZeroU64, >; - #[pallet::storage] // --- NMAP ( hot, cold, netuid ) --> claimable_debt | Returns a keys debt for claimable divs. - pub type RootDebt = StorageNMap< + + // Already claimed root alpha. + #[pallet::storage] + pub type RootClaimed = StorageNMap< _, ( NMapKey, // hot NMapKey, // cold NMapKey, // subnet ), - I96F32, // Shares + u128, ValueQuery, >; #[pallet::storage] // -- MAP ( cold ) --> root_claim_type enum diff --git a/pallets/subtensor/src/staking/claim_root.rs b/pallets/subtensor/src/staking/claim_root.rs index 853165b5b9..aa37557342 100644 --- a/pallets/subtensor/src/staking/claim_root.rs +++ b/pallets/subtensor/src/staking/claim_root.rs @@ -90,12 +90,12 @@ impl Pallet { ) -> I110F18 { let claimable = Self::get_root_claimable_for_hotkey_coldkey(hotkey, coldkey, netuid); - // Attain the claimable debt to avoid overclaiming. - let debt: I110F18 = - I110F18::saturating_from_num(RootDebt::::get((hotkey, coldkey, netuid))); + // Attain the root claimed to avoid overclaiming. + let root_claimed: I110F18 = + I110F18::saturating_from_num(RootClaimed::::get((hotkey, coldkey, netuid))); - // Substract the debt. - let owed: I110F18 = claimable.saturating_sub(debt); + // Substract the already claimed alpha. + let owed: I110F18 = claimable.saturating_sub(root_claimed); owed } @@ -123,7 +123,7 @@ impl Pallet { netuid: NetUid, root_claim_type: RootClaimTypeEnum, ) { - // Substract the debt. + // Substract the root claimed. let owed: I110F18 = Self::get_root_owed_for_hotkey_coldkey_float(hotkey, coldkey, netuid); if owed == 0 || owed < I110F18::saturating_from_num(DefaultMinRootClaimAmount::::get()) { @@ -172,9 +172,9 @@ impl Pallet { } }; - // Increase root debt by owed amount. - RootDebt::::mutate((hotkey, coldkey, netuid), |debt| { - *debt = debt.saturating_add(owed.saturating_to_num::()); + // Increase root claimed by owed amount. + RootClaimed::::mutate((hotkey, coldkey, netuid), |root_claimed| { + *root_claimed = root_claimed.saturating_add(owed_u64.into()); }); } @@ -199,7 +199,7 @@ impl Pallet { weight } - pub fn add_stake_adjust_debt_for_hotkey_and_coldkey( + pub fn add_stake_adjust_root_claimed_for_hotkey_and_coldkey( hotkey: &T::AccountId, coldkey: &T::AccountId, amount: u64, @@ -207,26 +207,21 @@ impl Pallet { // Iterate over all the subnets this hotkey is staked on for root. for (netuid, claimable_rate) in RootClaimable::::iter_prefix(hotkey) { // Get the total claimable_rate for this hotkey and this network - let claimable_rate_float = I110F18::saturating_from_num(claimable_rate); + let claimable_rate_u128: u128 = claimable_rate.into(); - // Get current staker-debt. - let debt: I110F18 = - I110F18::saturating_from_num(RootDebt::::get((hotkey, coldkey, netuid))); + // Get current staker root claimed value. + let root_claimed: u128 = RootClaimed::::get((hotkey, coldkey, netuid)); - // Increase debt based on the claimable rate. - let new_debt: I110F18 = debt.saturating_add( - claimable_rate_float.saturating_mul(I110F18::saturating_from_num(amount)), - ); + // Increase root claimed based on the claimable rate. + let new_root_claimed = + root_claimed.saturating_add(claimable_rate_u128.saturating_mul(amount.into())); - // Set the new debt. - RootDebt::::insert( - (hotkey, coldkey, netuid), - new_debt.saturating_to_num::(), - ); + // Set the new root claimed value. + RootClaimed::::insert((hotkey, coldkey, netuid), new_root_claimed); } } - pub fn remove_stake_adjust_debt_for_hotkey_and_coldkey( + pub fn remove_stake_adjust_root_claimed_for_hotkey_and_coldkey( hotkey: &T::AccountId, coldkey: &T::AccountId, amount: AlphaCurrency, @@ -238,22 +233,17 @@ impl Pallet { } // Get the total claimable_rate for this hotkey and this network - let claimable_rate_float = I110F18::saturating_from_num(claimable_rate); + let claimable_rate_u128: u128 = claimable_rate.into(); - // Get current staker-debt. - let debt: I110F18 = - I110F18::saturating_from_num(RootDebt::::get((hotkey, coldkey, netuid))); + // Get current staker root claimed value. + let root_claimed: u128 = RootClaimed::::get((hotkey, coldkey, netuid)); - // Decrease debt based on the claimable rate. - let new_debt: I110F18 = debt.saturating_sub( - claimable_rate_float.saturating_mul(I110F18::saturating_from_num(amount)), - ); + // Decrease root claimed based on the claimable rate. + let new_root_claimed = root_claimed + .saturating_sub(claimable_rate_u128.saturating_mul(u64::from(amount).into())); - // Set the new debt. - RootDebt::::insert( - (hotkey, coldkey, netuid), - new_debt.saturating_to_num::(), - ); + // Set the new root_claimed value. + RootClaimed::::insert((hotkey, coldkey, netuid), new_root_claimed); } } diff --git a/pallets/subtensor/src/staking/stake_utils.rs b/pallets/subtensor/src/staking/stake_utils.rs index 3bc18094c9..e38e46fea6 100644 --- a/pallets/subtensor/src/staking/stake_utils.rs +++ b/pallets/subtensor/src/staking/stake_utils.rs @@ -736,8 +736,8 @@ impl Pallet { // If this is a root-stake if netuid == NetUid::ROOT { - // Adjust root debt for this hotkey and coldkey. - Self::remove_stake_adjust_debt_for_hotkey_and_coldkey(hotkey, coldkey, alpha); + // Adjust root claimed value for this hotkey and coldkey. + Self::remove_stake_adjust_root_claimed_for_hotkey_and_coldkey(hotkey, coldkey, alpha); } // Step 3: Update StakingHotkeys if the hotkey's total alpha, across all subnets, is zero @@ -827,9 +827,9 @@ impl Pallet { // If this is a root-stake if netuid == NetUid::ROOT { - // Adjust root debt for this hotkey and coldkey. + // Adjust root claimed for this hotkey and coldkey. let alpha = swap_result.amount_paid_out.into(); - Self::add_stake_adjust_debt_for_hotkey_and_coldkey(hotkey, coldkey, alpha); + Self::add_stake_adjust_root_claimed_for_hotkey_and_coldkey(hotkey, coldkey, alpha); } // Deposit and log the staking event. From 2e613b09e9f71419c8082896cabe6804bab9b7bc Mon Sep 17 00:00:00 2001 From: Shamil Gadelshin Date: Wed, 17 Sep 2025 14:55:18 +0300 Subject: [PATCH 13/54] Fix RootClaimable map --- pallets/subtensor/src/lib.rs | 10 ++++-- pallets/subtensor/src/staking/claim_root.rs | 36 ++++++++------------- 2 files changed, 22 insertions(+), 24 deletions(-) diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 948abc0d0b..ba48e25544 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -893,6 +893,12 @@ pub mod pallet { pub fn DefaultMovingPrice() -> I96F32 { I96F32::saturating_from_num(0.0) } + + #[pallet::type_value] + /// Default subnet root claimable + pub fn DefaultRootClaimable() -> I96F32 { + I96F32::saturating_from_num(0.0) + } #[pallet::type_value] /// Default value for Share Pool variables pub fn DefaultSharePoolZero() -> U64F64 { @@ -1822,9 +1828,9 @@ pub mod pallet { T::AccountId, Identity, NetUid, - u64, + I96F32, ValueQuery, - DefaultZeroU64, + DefaultRootClaimable, >; // Already claimed root alpha. diff --git a/pallets/subtensor/src/staking/claim_root.rs b/pallets/subtensor/src/staking/claim_root.rs index aa37557342..23d378fe80 100644 --- a/pallets/subtensor/src/staking/claim_root.rs +++ b/pallets/subtensor/src/staking/claim_root.rs @@ -1,7 +1,7 @@ use super::*; use frame_support::weights::Weight; use sp_core::Get; -use substrate_fixed::types::{I96F32, I110F18}; +use substrate_fixed::types::I96F32; use subtensor_swap_interface::SwapHandler; impl Pallet { @@ -50,16 +50,9 @@ impl Pallet { .checked_div(total) .unwrap_or(I96F32::saturating_from_num(0.0)); - // Convert increment to u64, mapping negative values to 0 - let increment_u64: u64 = if increment.is_negative() { - 0 - } else { - increment.saturating_to_num::() - }; - // Increment claimable for this subnet. RootClaimable::::mutate(hotkey, netuid, |total| { - *total = total.saturating_add(increment_u64); + *total = total.saturating_add(increment); }); } @@ -67,18 +60,17 @@ impl Pallet { hotkey: &T::AccountId, coldkey: &T::AccountId, netuid: NetUid, - ) -> I110F18 { + ) -> I96F32 { // Get this keys stake balance on root. - let root_stake: I110F18 = I110F18::saturating_from_num( + let root_stake: I96F32 = I96F32::saturating_from_num( Self::get_stake_for_hotkey_and_coldkey_on_subnet(hotkey, coldkey, NetUid::ROOT), ); // Get the total claimable_rate for this hotkey and this network - let claimable_rate: I110F18 = - I110F18::saturating_from_num(RootClaimable::::get(hotkey, netuid)); + let claimable_rate: I96F32 = RootClaimable::::get(hotkey, netuid); // Compute the proportion owed to this coldkey via balance. - let claimable: I110F18 = claimable_rate.saturating_mul(root_stake); + let claimable: I96F32 = claimable_rate.saturating_mul(root_stake); claimable } @@ -87,15 +79,15 @@ impl Pallet { hotkey: &T::AccountId, coldkey: &T::AccountId, netuid: NetUid, - ) -> I110F18 { + ) -> I96F32 { let claimable = Self::get_root_claimable_for_hotkey_coldkey(hotkey, coldkey, netuid); // Attain the root claimed to avoid overclaiming. - let root_claimed: I110F18 = - I110F18::saturating_from_num(RootClaimed::::get((hotkey, coldkey, netuid))); + let root_claimed: I96F32 = + I96F32::saturating_from_num(RootClaimed::::get((hotkey, coldkey, netuid))); // Substract the already claimed alpha. - let owed: I110F18 = claimable.saturating_sub(root_claimed); + let owed: I96F32 = claimable.saturating_sub(root_claimed); owed } @@ -124,9 +116,9 @@ impl Pallet { root_claim_type: RootClaimTypeEnum, ) { // Substract the root claimed. - let owed: I110F18 = Self::get_root_owed_for_hotkey_coldkey_float(hotkey, coldkey, netuid); + let owed: I96F32 = Self::get_root_owed_for_hotkey_coldkey_float(hotkey, coldkey, netuid); - if owed == 0 || owed < I110F18::saturating_from_num(DefaultMinRootClaimAmount::::get()) { + if owed == 0 || owed < I96F32::saturating_from_num(DefaultMinRootClaimAmount::::get()) { return; // no-op } @@ -207,7 +199,7 @@ impl Pallet { // Iterate over all the subnets this hotkey is staked on for root. for (netuid, claimable_rate) in RootClaimable::::iter_prefix(hotkey) { // Get the total claimable_rate for this hotkey and this network - let claimable_rate_u128: u128 = claimable_rate.into(); + let claimable_rate_u128: u128 = claimable_rate.saturating_to_num(); // Get current staker root claimed value. let root_claimed: u128 = RootClaimed::::get((hotkey, coldkey, netuid)); @@ -233,7 +225,7 @@ impl Pallet { } // Get the total claimable_rate for this hotkey and this network - let claimable_rate_u128: u128 = claimable_rate.into(); + let claimable_rate_u128: u128 = claimable_rate.saturating_to_num(); // Get current staker root claimed value. let root_claimed: u128 = RootClaimed::::get((hotkey, coldkey, netuid)); From b125673f205befe3bf08c2f49debbd7cdd94d13f Mon Sep 17 00:00:00 2001 From: Shamil Gadelshin Date: Wed, 17 Sep 2025 14:55:25 +0300 Subject: [PATCH 14/54] Add root claim tests. --- pallets/subtensor/src/tests/claim_root.rs | 72 +++++++++++++++++++++++ pallets/subtensor/src/tests/mod.rs | 1 + 2 files changed, 73 insertions(+) create mode 100644 pallets/subtensor/src/tests/claim_root.rs diff --git a/pallets/subtensor/src/tests/claim_root.rs b/pallets/subtensor/src/tests/claim_root.rs new file mode 100644 index 0000000000..c3a1143ee2 --- /dev/null +++ b/pallets/subtensor/src/tests/claim_root.rs @@ -0,0 +1,72 @@ +use crate::TotalHotkeyAlpha; +use crate::tests::mock::{RuntimeOrigin, SubtensorModule, Test, add_dynamic_network, new_test_ext}; +use crate::{RootClaimType, RootClaimTypeEnum, SubnetAlphaIn, SubnetTAO, pallet}; +use frame_support::assert_ok; +use sp_core::U256; +use substrate_fixed::types::U96F32; +use subtensor_runtime_common::{AlphaCurrency, NetUid, TaoCurrency}; +use subtensor_swap_interface::SwapHandler; + +#[test] +fn test_set_root_claim_type() { + new_test_ext(1).execute_with(|| { + let coldkey = U256::from(1); + + assert_ok!(SubtensorModule::set_root_claim_type( + RuntimeOrigin::signed(coldkey), + RootClaimTypeEnum::Keep + ),); + + assert_eq!(RootClaimType::::get(coldkey), RootClaimTypeEnum::Keep); + }); +} + +#[test] +fn test_claim_root() { + new_test_ext(1).execute_with(|| { + let coldkey = U256::from(1001); + let hotkey = U256::from(1002); + let netuid = add_dynamic_network(&hotkey, &coldkey); + + let tao_reserve = TaoCurrency::from(50_000_000_000); + let alpha_in = AlphaCurrency::from(100_000_000_000); + SubnetTAO::::insert(netuid, tao_reserve); + SubnetAlphaIn::::insert(netuid, alpha_in); + let current_price = + ::SwapInterface::current_alpha_price(netuid.into()); + assert_eq!(current_price, U96F32::from_num(0.5)); + + let stake = 1_000_000u64; + TotalHotkeyAlpha::::insert(hotkey, netuid, AlphaCurrency::from(stake)); + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &coldkey, + NetUid::ROOT, + stake.into(), + ); + + let claimable_amount = 1_000_000u64; + SubtensorModule::increase_root_claimable_for_hotkey_and_subnet( + &hotkey, + netuid, + claimable_amount.into(), + ); + + assert_ok!(SubtensorModule::set_root_claim_type( + RuntimeOrigin::signed(coldkey), + RootClaimTypeEnum::Keep + ),); + + assert_eq!(RootClaimType::::get(coldkey), RootClaimTypeEnum::Keep); + + let new_stake = + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid); + assert_eq!(new_stake, 0u64.into()); + + assert_ok!(SubtensorModule::claim_root(RuntimeOrigin::signed(coldkey),)); + + let new_stake = + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid); + assert_eq!(new_stake, (stake * 2).into()); + }); +} diff --git a/pallets/subtensor/src/tests/mod.rs b/pallets/subtensor/src/tests/mod.rs index b743d7c1ff..15b7027e77 100644 --- a/pallets/subtensor/src/tests/mod.rs +++ b/pallets/subtensor/src/tests/mod.rs @@ -1,5 +1,6 @@ mod batch_tx; mod children; +mod claim_root; mod coinbase; mod consensus; mod delegate_info; From 0151f490ce243719a52ac33c35e8c901595a31b2 Mon Sep 17 00:00:00 2001 From: Shamil Gadelshin Date: Wed, 17 Sep 2025 17:30:11 +0300 Subject: [PATCH 15/54] Add basic test with drain_emissions --- pallets/subtensor/src/staking/claim_root.rs | 7 ++ pallets/subtensor/src/tests/claim_root.rs | 99 +++++++++++++++++---- 2 files changed, 89 insertions(+), 17 deletions(-) diff --git a/pallets/subtensor/src/staking/claim_root.rs b/pallets/subtensor/src/staking/claim_root.rs index 23d378fe80..2e19193e90 100644 --- a/pallets/subtensor/src/staking/claim_root.rs +++ b/pallets/subtensor/src/staking/claim_root.rs @@ -50,6 +50,13 @@ impl Pallet { .checked_div(total) .unwrap_or(I96F32::saturating_from_num(0.0)); + log::debug!( + "Increasing root claimable by {} with {} divided by {}", + increment, + amount, + total + ); + // Increment claimable for this subnet. RootClaimable::::mutate(hotkey, netuid, |total| { *total = total.saturating_add(increment); diff --git a/pallets/subtensor/src/tests/claim_root.rs b/pallets/subtensor/src/tests/claim_root.rs index c3a1143ee2..db9d00d300 100644 --- a/pallets/subtensor/src/tests/claim_root.rs +++ b/pallets/subtensor/src/tests/claim_root.rs @@ -1,10 +1,13 @@ -use crate::TotalHotkeyAlpha; -use crate::tests::mock::{RuntimeOrigin, SubtensorModule, Test, add_dynamic_network, new_test_ext}; +use crate::tests::mock::{ + RuntimeOrigin, SubtensorModule, Test, add_dynamic_network, new_test_ext, +}; use crate::{RootClaimType, RootClaimTypeEnum, SubnetAlphaIn, SubnetTAO, pallet}; +use crate::{RootClaimable, TotalHotkeyAlpha}; +use approx::assert_abs_diff_eq; use frame_support::assert_ok; use sp_core::U256; -use substrate_fixed::types::U96F32; -use subtensor_runtime_common::{AlphaCurrency, NetUid, TaoCurrency}; +use substrate_fixed::types::{I96F32, U96F32}; +use subtensor_runtime_common::{AlphaCurrency, Currency, NetUid, TaoCurrency}; use subtensor_swap_interface::SwapHandler; #[test] @@ -22,11 +25,14 @@ fn test_set_root_claim_type() { } #[test] -fn test_claim_root() { +fn test_claim_root_with_drain_emissions() { new_test_ext(1).execute_with(|| { - let coldkey = U256::from(1001); + let owner_coldkey = U256::from(1001); let hotkey = U256::from(1002); - let netuid = add_dynamic_network(&hotkey, &coldkey); + let coldkey = U256::from(1003); + let netuid = add_dynamic_network(&hotkey, &owner_coldkey); + let initial_balance = 10_000_000u64; + SubtensorModule::add_balance_to_coldkey_account(&coldkey, initial_balance.into()); let tao_reserve = TaoCurrency::from(50_000_000_000); let alpha_in = AlphaCurrency::from(100_000_000_000); @@ -36,6 +42,8 @@ fn test_claim_root() { ::SwapInterface::current_alpha_price(netuid.into()); assert_eq!(current_price, U96F32::from_num(0.5)); + SubtensorModule::set_tao_weight(u64::MAX); // Set TAO weight to 1.0 + let stake = 1_000_000u64; TotalHotkeyAlpha::::insert(hotkey, netuid, AlphaCurrency::from(stake)); SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( @@ -45,28 +53,85 @@ fn test_claim_root() { stake.into(), ); - let claimable_amount = 1_000_000u64; - SubtensorModule::increase_root_claimable_for_hotkey_and_subnet( - &hotkey, + let pending_root_alpha = AlphaCurrency::from(1_000_000); + SubtensorModule::drain_pending_emission( netuid, - claimable_amount.into(), + AlphaCurrency::ZERO, + pending_root_alpha, + AlphaCurrency::ZERO, + ); + + let claimable = RootClaimable::::get(hotkey, netuid); + + let validator_take_percent = 18u64; + let calculated_rate = (I96F32::from(stake) * I96F32::from(100u64 - validator_take_percent)) + / I96F32::from(100u64) + / I96F32::from(u64::from(TotalHotkeyAlpha::::get(hotkey, netuid))); + + assert_abs_diff_eq!( + (claimable * I96F32::from(1000u64)).saturating_to_num::(), + (calculated_rate * I96F32::from(1000u64)).saturating_to_num::(), + epsilon = 10u64, ); assert_ok!(SubtensorModule::set_root_claim_type( RuntimeOrigin::signed(coldkey), RootClaimTypeEnum::Keep ),); - assert_eq!(RootClaimType::::get(coldkey), RootClaimTypeEnum::Keep); - let new_stake = - SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid); - assert_eq!(new_stake, 0u64.into()); - assert_ok!(SubtensorModule::claim_root(RuntimeOrigin::signed(coldkey),)); + log::debug!("RootClaimable = {}", claimable); let new_stake = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid); - assert_eq!(new_stake, (stake * 2).into()); + + assert_abs_diff_eq!( + u64::from(new_stake), + (I96F32::from(stake) * claimable).saturating_to_num::(), + epsilon = 10u64, + ); + }); +} + +/* +Test 1 - Adding Stake Disproportionally + +1. Beginning of the epoch 1: Alice stakes 1 TAO to root, Bob stakes 1 TAO to root, and both say "I want airdrop" +2. For simplicity let's say `root_alpha = 0.1` in every block. So at the end of the epoch 1 we have 36 root alpha to share between Alice and Bob. Each is entitled to 18 Alpha. +3. Beginning of the epoch 2: Alice stakes 1 TAO more, Bob stakes 2 TAO more. State is now: + +``` +alpha_to_airdrop: 36 +rewards_per_tao: 18 + +Alice's stake: 2 +Bob's stake: 3 +debt_alice: 18 +debt_bob: 36 +``` + +4. As epoch 2 goes, we keep adding 0.1 root Alpha in every block, so it will be still +36 Alpha, but new Alpha is shared differently between Alice and Bob: Alice gets 2/5 of the new Alpha and Bob gets 3/5 of the new Alpha. + +5. End of epoch 2: Alice is entitled to 18 + 14.4 = 32.4 Alpha, Bob is entitled to 18 + 21.6 = 39.6 Alpha. State update is following: + +``` +alpha_to_airdrop: 72 +rewards_per_tao: 18 + 36/5 = 25.2 +``` + +6. Alice and Bob want to claim their Alpha: + +``` +alpha_alice = 2 * 25.2 - 18 = 32.4 +alpha_bob = 3 * 25.2 - 36 = 39.6 +``` + +*/ + +#[test] +fn test_adding_stake_disproportionally() { + new_test_ext(1).execute_with(|| { + }); } From 83b2b364271ce3d4b73f47fb9e80914770fac62d Mon Sep 17 00:00:00 2001 From: Shamil Gadelshin Date: Wed, 17 Sep 2025 19:03:21 +0300 Subject: [PATCH 16/54] Update tests. --- .../subtensor/src/coinbase/run_coinbase.rs | 7 --- pallets/subtensor/src/tests/claim_root.rs | 58 ++++++++++++++----- 2 files changed, 45 insertions(+), 20 deletions(-) diff --git a/pallets/subtensor/src/coinbase/run_coinbase.rs b/pallets/subtensor/src/coinbase/run_coinbase.rs index 83903f921a..0c721d6abb 100644 --- a/pallets/subtensor/src/coinbase/run_coinbase.rs +++ b/pallets/subtensor/src/coinbase/run_coinbase.rs @@ -543,13 +543,6 @@ impl Pallet { tou64!(alpha_take).into(), ); - // Give rest to nominators. - // Self::increase_stake_for_hotkey_on_subnet( - // &hotkey, - // netuid, - // tou64!(root_alpha).into(), - // ); - Self::increase_root_claimable_for_hotkey_and_subnet( &hotkey, netuid, diff --git a/pallets/subtensor/src/tests/claim_root.rs b/pallets/subtensor/src/tests/claim_root.rs index db9d00d300..aca9d05b3f 100644 --- a/pallets/subtensor/src/tests/claim_root.rs +++ b/pallets/subtensor/src/tests/claim_root.rs @@ -1,6 +1,4 @@ -use crate::tests::mock::{ - RuntimeOrigin, SubtensorModule, Test, add_dynamic_network, new_test_ext, -}; +use crate::tests::mock::{RuntimeOrigin, SubtensorModule, Test, add_dynamic_network, new_test_ext}; use crate::{RootClaimType, RootClaimTypeEnum, SubnetAlphaIn, SubnetTAO, pallet}; use crate::{RootClaimable, TotalHotkeyAlpha}; use approx::assert_abs_diff_eq; @@ -44,16 +42,32 @@ fn test_claim_root_with_drain_emissions() { SubtensorModule::set_tao_weight(u64::MAX); // Set TAO weight to 1.0 - let stake = 1_000_000u64; - TotalHotkeyAlpha::::insert(hotkey, netuid, AlphaCurrency::from(stake)); + let stake = 2_000_000u64; + let initial_total_hotkey_alpha = 1_000_000u64; + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( &hotkey, &coldkey, NetUid::ROOT, stake.into(), ); + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &owner_coldkey, + netuid, + initial_total_hotkey_alpha.into(), + ); + + let old_validator_stake = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &owner_coldkey, + netuid, + ); + assert_eq!(old_validator_stake, initial_total_hotkey_alpha.into()); - let pending_root_alpha = AlphaCurrency::from(1_000_000); + // Distribute pending root alpha + + let pending_root_alpha = AlphaCurrency::from(10_000_000); SubtensorModule::drain_pending_emission( netuid, AlphaCurrency::ZERO, @@ -61,11 +75,29 @@ fn test_claim_root_with_drain_emissions() { AlphaCurrency::ZERO, ); - let claimable = RootClaimable::::get(hotkey, netuid); + // Check new validator stake + let validator_take_percent = I96F32::from(18u64) / I96F32::from(100u64); - let validator_take_percent = 18u64; - let calculated_rate = (I96F32::from(stake) * I96F32::from(100u64 - validator_take_percent)) - / I96F32::from(100u64) + let new_validator_stake = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &owner_coldkey, + netuid, + ); + let calculated_validator_stake = I96F32::from(u64::from(pending_root_alpha)) + * I96F32::from(initial_total_hotkey_alpha) + * validator_take_percent + / I96F32::from(u64::from(initial_total_hotkey_alpha)) + + I96F32::from(initial_total_hotkey_alpha); + + assert_abs_diff_eq!( + u64::from(new_validator_stake), + calculated_validator_stake.saturating_to_num::(), + epsilon = 100u64, + ); + + let claimable = RootClaimable::::get(hotkey, netuid); + let calculated_rate = (I96F32::from(u64::from(pending_root_alpha)) + * (I96F32::from(1u64) - validator_take_percent)) / I96F32::from(u64::from(TotalHotkeyAlpha::::get(hotkey, netuid))); assert_abs_diff_eq!( @@ -74,6 +106,8 @@ fn test_claim_root_with_drain_emissions() { epsilon = 10u64, ); + // Claim root alpha + assert_ok!(SubtensorModule::set_root_claim_type( RuntimeOrigin::signed(coldkey), RootClaimTypeEnum::Keep @@ -131,7 +165,5 @@ alpha_bob = 3 * 25.2 - 36 = 39.6 #[test] fn test_adding_stake_disproportionally() { - new_test_ext(1).execute_with(|| { - - }); + new_test_ext(1).execute_with(|| {}); } From c482f2f51c22099cc755b71baf0985191fba5279 Mon Sep 17 00:00:00 2001 From: Shamil Gadelshin Date: Thu, 18 Sep 2025 14:21:20 +0300 Subject: [PATCH 17/54] Update tests --- pallets/subtensor/src/tests/claim_root.rs | 75 +++++++++++++++++------ 1 file changed, 55 insertions(+), 20 deletions(-) diff --git a/pallets/subtensor/src/tests/claim_root.rs b/pallets/subtensor/src/tests/claim_root.rs index aca9d05b3f..047b6d14f0 100644 --- a/pallets/subtensor/src/tests/claim_root.rs +++ b/pallets/subtensor/src/tests/claim_root.rs @@ -1,12 +1,11 @@ use crate::tests::mock::{RuntimeOrigin, SubtensorModule, Test, add_dynamic_network, new_test_ext}; -use crate::{RootClaimType, RootClaimTypeEnum, SubnetAlphaIn, SubnetTAO, pallet}; +use crate::{RootClaimType, RootClaimTypeEnum, RootClaimed}; use crate::{RootClaimable, TotalHotkeyAlpha}; use approx::assert_abs_diff_eq; use frame_support::assert_ok; use sp_core::U256; -use substrate_fixed::types::{I96F32, U96F32}; -use subtensor_runtime_common::{AlphaCurrency, Currency, NetUid, TaoCurrency}; -use subtensor_swap_interface::SwapHandler; +use substrate_fixed::types::I96F32; +use subtensor_runtime_common::{AlphaCurrency, Currency, NetUid}; #[test] fn test_set_root_claim_type() { @@ -29,28 +28,18 @@ fn test_claim_root_with_drain_emissions() { let hotkey = U256::from(1002); let coldkey = U256::from(1003); let netuid = add_dynamic_network(&hotkey, &owner_coldkey); - let initial_balance = 10_000_000u64; - SubtensorModule::add_balance_to_coldkey_account(&coldkey, initial_balance.into()); - - let tao_reserve = TaoCurrency::from(50_000_000_000); - let alpha_in = AlphaCurrency::from(100_000_000_000); - SubnetTAO::::insert(netuid, tao_reserve); - SubnetAlphaIn::::insert(netuid, alpha_in); - let current_price = - ::SwapInterface::current_alpha_price(netuid.into()); - assert_eq!(current_price, U96F32::from_num(0.5)); SubtensorModule::set_tao_weight(u64::MAX); // Set TAO weight to 1.0 - let stake = 2_000_000u64; - let initial_total_hotkey_alpha = 1_000_000u64; - + let root_stake = 2_000_000u64; SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( &hotkey, &coldkey, NetUid::ROOT, - stake.into(), + root_stake.into(), ); + + let initial_total_hotkey_alpha = 20_000_000u64; SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( &hotkey, &owner_coldkey, @@ -95,6 +84,8 @@ fn test_claim_root_with_drain_emissions() { epsilon = 100u64, ); + // Check claimable + let claimable = RootClaimable::::get(hotkey, netuid); let calculated_rate = (I96F32::from(u64::from(pending_root_alpha)) * (I96F32::from(1u64) - validator_take_percent)) @@ -116,15 +107,59 @@ fn test_claim_root_with_drain_emissions() { assert_ok!(SubtensorModule::claim_root(RuntimeOrigin::signed(coldkey),)); - log::debug!("RootClaimable = {}", claimable); let new_stake = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid); assert_abs_diff_eq!( u64::from(new_stake), - (I96F32::from(stake) * claimable).saturating_to_num::(), + (I96F32::from(root_stake) * claimable).saturating_to_num::(), epsilon = 10u64, ); + + // Check root claimed value saved + + let claimed = RootClaimed::::get((&hotkey, &coldkey, netuid)); + assert_eq!(u128::from(u64::from(new_stake)), claimed); + + // Distribute pending root alpha (round 2) + + SubtensorModule::drain_pending_emission( + netuid, + AlphaCurrency::ZERO, + pending_root_alpha, + AlphaCurrency::ZERO, + ); + + // Check claimable (round 2) + + let claimable2 = RootClaimable::::get(hotkey, netuid); + let calculated_rate = (I96F32::from(u64::from(pending_root_alpha)) + * (I96F32::from(1u64) - validator_take_percent)) + / I96F32::from(u64::from(TotalHotkeyAlpha::::get(hotkey, netuid))); + + assert_abs_diff_eq!( + (claimable2 * I96F32::from(1000u64)).saturating_to_num::(), + ((calculated_rate + claimable) * I96F32::from(1000u64)).saturating_to_num::(), + epsilon = 10u64, + ); + + assert_ok!(SubtensorModule::claim_root(RuntimeOrigin::signed(coldkey),)); + + let new_stake2 = + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid); + let calculated_new_stake2 = + (I96F32::from(u64::from(root_stake)) * claimable2).saturating_to_num::(); + + assert_abs_diff_eq!( + u64::from(new_stake2), + calculated_new_stake2, + epsilon = 10u64, + ); + + // Check root claimed value saved (round 2) + + let claimed = RootClaimed::::get((&hotkey, &coldkey, netuid)); + assert_eq!(u128::from(u64::from(new_stake2)), claimed); }); } From 55c1b1d92a827fee0174ab06ce98f05118576663 Mon Sep 17 00:00:00 2001 From: Shamil Gadelshin Date: Thu, 18 Sep 2025 17:50:09 +0300 Subject: [PATCH 18/54] Simplify tests --- pallets/subtensor/src/staking/claim_root.rs | 7 --- pallets/subtensor/src/tests/claim_root.rs | 55 ++++++++++----------- 2 files changed, 26 insertions(+), 36 deletions(-) diff --git a/pallets/subtensor/src/staking/claim_root.rs b/pallets/subtensor/src/staking/claim_root.rs index 2e19193e90..23d378fe80 100644 --- a/pallets/subtensor/src/staking/claim_root.rs +++ b/pallets/subtensor/src/staking/claim_root.rs @@ -50,13 +50,6 @@ impl Pallet { .checked_div(total) .unwrap_or(I96F32::saturating_from_num(0.0)); - log::debug!( - "Increasing root claimable by {} with {} divided by {}", - increment, - amount, - total - ); - // Increment claimable for this subnet. RootClaimable::::mutate(hotkey, netuid, |total| { *total = total.saturating_add(increment); diff --git a/pallets/subtensor/src/tests/claim_root.rs b/pallets/subtensor/src/tests/claim_root.rs index 047b6d14f0..4836268962 100644 --- a/pallets/subtensor/src/tests/claim_root.rs +++ b/pallets/subtensor/src/tests/claim_root.rs @@ -56,45 +56,41 @@ fn test_claim_root_with_drain_emissions() { // Distribute pending root alpha - let pending_root_alpha = AlphaCurrency::from(10_000_000); + let pending_root_alpha = 10_000_000u64; SubtensorModule::drain_pending_emission( netuid, AlphaCurrency::ZERO, - pending_root_alpha, + pending_root_alpha.into(), AlphaCurrency::ZERO, ); // Check new validator stake - let validator_take_percent = I96F32::from(18u64) / I96F32::from(100u64); + let validator_take_percent = 0.18f64; let new_validator_stake = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( &hotkey, &owner_coldkey, netuid, ); - let calculated_validator_stake = I96F32::from(u64::from(pending_root_alpha)) - * I96F32::from(initial_total_hotkey_alpha) - * validator_take_percent - / I96F32::from(u64::from(initial_total_hotkey_alpha)) - + I96F32::from(initial_total_hotkey_alpha); + let calculated_validator_stake = (pending_root_alpha as f64) * validator_take_percent + + (initial_total_hotkey_alpha as f64); assert_abs_diff_eq!( u64::from(new_validator_stake), - calculated_validator_stake.saturating_to_num::(), + calculated_validator_stake as u64, epsilon = 100u64, ); // Check claimable let claimable = RootClaimable::::get(hotkey, netuid); - let calculated_rate = (I96F32::from(u64::from(pending_root_alpha)) - * (I96F32::from(1u64) - validator_take_percent)) - / I96F32::from(u64::from(TotalHotkeyAlpha::::get(hotkey, netuid))); + let calculated_rate = (pending_root_alpha as f64) * (1f64 - validator_take_percent) + / (u64::from(TotalHotkeyAlpha::::get(hotkey, netuid)) as f64); assert_abs_diff_eq!( - (claimable * I96F32::from(1000u64)).saturating_to_num::(), - (calculated_rate * I96F32::from(1000u64)).saturating_to_num::(), - epsilon = 10u64, + claimable.saturating_to_num::(), + calculated_rate, + epsilon = 0.001f64, ); // Claim root alpha @@ -107,11 +103,12 @@ fn test_claim_root_with_drain_emissions() { assert_ok!(SubtensorModule::claim_root(RuntimeOrigin::signed(coldkey),)); - let new_stake = - SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid); + let new_stake: u64 = + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid) + .into(); assert_abs_diff_eq!( - u64::from(new_stake), + new_stake, (I96F32::from(root_stake) * claimable).saturating_to_num::(), epsilon = 10u64, ); @@ -119,36 +116,36 @@ fn test_claim_root_with_drain_emissions() { // Check root claimed value saved let claimed = RootClaimed::::get((&hotkey, &coldkey, netuid)); - assert_eq!(u128::from(u64::from(new_stake)), claimed); + assert_eq!(u128::from(new_stake), claimed); // Distribute pending root alpha (round 2) SubtensorModule::drain_pending_emission( netuid, AlphaCurrency::ZERO, - pending_root_alpha, + pending_root_alpha.into(), AlphaCurrency::ZERO, ); // Check claimable (round 2) let claimable2 = RootClaimable::::get(hotkey, netuid); - let calculated_rate = (I96F32::from(u64::from(pending_root_alpha)) - * (I96F32::from(1u64) - validator_take_percent)) - / I96F32::from(u64::from(TotalHotkeyAlpha::::get(hotkey, netuid))); + let calculated_rate = (pending_root_alpha as f64) * (1f64 - validator_take_percent) + / (u64::from(TotalHotkeyAlpha::::get(hotkey, netuid)) as f64); assert_abs_diff_eq!( - (claimable2 * I96F32::from(1000u64)).saturating_to_num::(), - ((calculated_rate + claimable) * I96F32::from(1000u64)).saturating_to_num::(), - epsilon = 10u64, + claimable2.saturating_to_num::(), + calculated_rate + claimable.saturating_to_num::(), + epsilon = 0.001f64, ); assert_ok!(SubtensorModule::claim_root(RuntimeOrigin::signed(coldkey),)); - let new_stake2 = - SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid); + let new_stake2: u64 = + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid) + .into(); let calculated_new_stake2 = - (I96F32::from(u64::from(root_stake)) * claimable2).saturating_to_num::(); + (I96F32::from(root_stake) * claimable2).saturating_to_num::(); assert_abs_diff_eq!( u64::from(new_stake2), From a11d7a9c9ede9f9488079fedb7babbb6d138edaa Mon Sep 17 00:00:00 2001 From: Shamil Gadelshin Date: Fri, 19 Sep 2025 12:19:46 +0300 Subject: [PATCH 19/54] Add disproportional stake test --- pallets/subtensor/src/staking/claim_root.rs | 2 +- pallets/subtensor/src/tests/claim_root.rs | 201 ++++++++++++++++---- 2 files changed, 168 insertions(+), 35 deletions(-) diff --git a/pallets/subtensor/src/staking/claim_root.rs b/pallets/subtensor/src/staking/claim_root.rs index 23d378fe80..b376be790e 100644 --- a/pallets/subtensor/src/staking/claim_root.rs +++ b/pallets/subtensor/src/staking/claim_root.rs @@ -43,7 +43,7 @@ impl Pallet { ) { // Get total stake on this hotkey on root. let total: I96F32 = - I96F32::saturating_from_num(Self::get_stake_for_hotkey_on_subnet(hotkey, netuid)); + I96F32::saturating_from_num(Self::get_stake_for_hotkey_on_subnet(hotkey, NetUid::ROOT)); // Get increment let increment: I96F32 = I96F32::saturating_from_num(amount) diff --git a/pallets/subtensor/src/tests/claim_root.rs b/pallets/subtensor/src/tests/claim_root.rs index 4836268962..4fc99d7f9b 100644 --- a/pallets/subtensor/src/tests/claim_root.rs +++ b/pallets/subtensor/src/tests/claim_root.rs @@ -1,6 +1,6 @@ use crate::tests::mock::{RuntimeOrigin, SubtensorModule, Test, add_dynamic_network, new_test_ext}; use crate::{RootClaimType, RootClaimTypeEnum, RootClaimed}; -use crate::{RootClaimable, TotalHotkeyAlpha}; +use crate::{RootClaimable}; use approx::assert_abs_diff_eq; use frame_support::assert_ok; use sp_core::U256; @@ -39,7 +39,7 @@ fn test_claim_root_with_drain_emissions() { root_stake.into(), ); - let initial_total_hotkey_alpha = 20_000_000u64; + let initial_total_hotkey_alpha = 10_000_000u64; SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( &hotkey, &owner_coldkey, @@ -56,7 +56,7 @@ fn test_claim_root_with_drain_emissions() { // Distribute pending root alpha - let pending_root_alpha = 10_000_000u64; + let pending_root_alpha = 1_000_000u64; SubtensorModule::drain_pending_emission( netuid, AlphaCurrency::ZERO, @@ -84,8 +84,8 @@ fn test_claim_root_with_drain_emissions() { // Check claimable let claimable = RootClaimable::::get(hotkey, netuid); - let calculated_rate = (pending_root_alpha as f64) * (1f64 - validator_take_percent) - / (u64::from(TotalHotkeyAlpha::::get(hotkey, netuid)) as f64); + let calculated_rate = + (pending_root_alpha as f64) * (1f64 - validator_take_percent) / (root_stake as f64); assert_abs_diff_eq!( claimable.saturating_to_num::(), @@ -130,8 +130,8 @@ fn test_claim_root_with_drain_emissions() { // Check claimable (round 2) let claimable2 = RootClaimable::::get(hotkey, netuid); - let calculated_rate = (pending_root_alpha as f64) * (1f64 - validator_take_percent) - / (u64::from(TotalHotkeyAlpha::::get(hotkey, netuid)) as f64); + let calculated_rate = + (pending_root_alpha as f64) * (1f64 - validator_take_percent) / (root_stake as f64); assert_abs_diff_eq!( claimable2.saturating_to_num::(), @@ -160,42 +160,175 @@ fn test_claim_root_with_drain_emissions() { }); } -/* -Test 1 - Adding Stake Disproportionally +#[test] +fn test_adding_stake_proportionally_for_two_stakers() { + new_test_ext(1).execute_with(|| { + let owner_coldkey = U256::from(1001); + let hotkey = U256::from(1002); + let alice_coldkey = U256::from(1003); + let bob_coldkey = U256::from(1004); + let netuid = add_dynamic_network(&hotkey, &owner_coldkey); + + SubtensorModule::set_tao_weight(u64::MAX); // Set TAO weight to 1.0 + + let root_stake = 1_000_000u64; + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &alice_coldkey, + NetUid::ROOT, + root_stake.into(), + ); + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &bob_coldkey, + NetUid::ROOT, + root_stake.into(), + ); + + let initial_total_hotkey_alpha = 10_000_000u64; + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &owner_coldkey, + netuid, + initial_total_hotkey_alpha.into(), + ); + + // Claim root alpha -1. Beginning of the epoch 1: Alice stakes 1 TAO to root, Bob stakes 1 TAO to root, and both say "I want airdrop" -2. For simplicity let's say `root_alpha = 0.1` in every block. So at the end of the epoch 1 we have 36 root alpha to share between Alice and Bob. Each is entitled to 18 Alpha. -3. Beginning of the epoch 2: Alice stakes 1 TAO more, Bob stakes 2 TAO more. State is now: + assert_ok!(SubtensorModule::set_root_claim_type( + RuntimeOrigin::signed(alice_coldkey), + RootClaimTypeEnum::Keep + ),); + assert_ok!(SubtensorModule::set_root_claim_type( + RuntimeOrigin::signed(bob_coldkey), + RootClaimTypeEnum::Keep + ),); + + // Distribute pending root alpha -``` -alpha_to_airdrop: 36 -rewards_per_tao: 18 + let pending_root_alpha = 10_000_000u64; + SubtensorModule::drain_pending_emission( + netuid, + AlphaCurrency::ZERO, + pending_root_alpha.into(), + AlphaCurrency::ZERO, + ); -Alice's stake: 2 -Bob's stake: 3 -debt_alice: 18 -debt_bob: 36 -``` + assert_ok!(SubtensorModule::claim_root(RuntimeOrigin::signed( + alice_coldkey + ),)); + assert_ok!(SubtensorModule::claim_root(RuntimeOrigin::signed( + bob_coldkey + ),)); -4. As epoch 2 goes, we keep adding 0.1 root Alpha in every block, so it will be still +36 Alpha, but new Alpha is shared differently between Alice and Bob: Alice gets 2/5 of the new Alpha and Bob gets 3/5 of the new Alpha. + // Check stakes + let validator_take_percent = 0.18f64; -5. End of epoch 2: Alice is entitled to 18 + 14.4 = 32.4 Alpha, Bob is entitled to 18 + 21.6 = 39.6 Alpha. State update is following: + let alice_stake: u64 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &alice_coldkey, + netuid, + ) + .into(); -``` -alpha_to_airdrop: 72 -rewards_per_tao: 18 + 36/5 = 25.2 -``` + let bob_stake: u64 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &bob_coldkey, + netuid, + ) + .into(); -6. Alice and Bob want to claim their Alpha: + let estimated_stake = (pending_root_alpha as f64) * (1f64 - validator_take_percent) / 2f64; -``` -alpha_alice = 2 * 25.2 - 18 = 32.4 -alpha_bob = 3 * 25.2 - 36 = 39.6 -``` + assert_eq!(alice_stake, bob_stake); -*/ + assert_abs_diff_eq!(alice_stake, estimated_stake as u64, epsilon = 100u64,); + }); +} #[test] -fn test_adding_stake_disproportionally() { - new_test_ext(1).execute_with(|| {}); +fn test_adding_stake_disproportionally_for_two_stakers() { + new_test_ext(1).execute_with(|| { + let owner_coldkey = U256::from(1001); + let hotkey = U256::from(1002); + let alice_coldkey = U256::from(1003); + let bob_coldkey = U256::from(1004); + let netuid = add_dynamic_network(&hotkey, &owner_coldkey); + + SubtensorModule::set_tao_weight(u64::MAX); // Set TAO weight to 1.0 + + let alice_root_stake = 1_000_000u64; + let bob_root_stake = 2_000_000u64; + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &alice_coldkey, + NetUid::ROOT, + alice_root_stake.into(), + ); + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &bob_coldkey, + NetUid::ROOT, + bob_root_stake.into(), + ); + + let initial_total_hotkey_alpha = 10_000_000u64; + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &owner_coldkey, + netuid, + initial_total_hotkey_alpha.into(), + ); + + // Claim root alpha + + assert_ok!(SubtensorModule::set_root_claim_type( + RuntimeOrigin::signed(alice_coldkey), + RootClaimTypeEnum::Keep + ),); + assert_ok!(SubtensorModule::set_root_claim_type( + RuntimeOrigin::signed(bob_coldkey), + RootClaimTypeEnum::Keep + ),); + + // Distribute pending root alpha + + let pending_root_alpha = 10_000_000u64; + SubtensorModule::drain_pending_emission( + netuid, + AlphaCurrency::ZERO, + pending_root_alpha.into(), + AlphaCurrency::ZERO, + ); + + assert_ok!(SubtensorModule::claim_root(RuntimeOrigin::signed( + alice_coldkey + ),)); + assert_ok!(SubtensorModule::claim_root(RuntimeOrigin::signed( + bob_coldkey + ),)); + + // Check stakes + let validator_take_percent = 0.18f64; + + let alice_stake: u64 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &alice_coldkey, + netuid, + ) + .into(); + + let bob_stake: u64 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &bob_coldkey, + netuid, + ) + .into(); + + let estimated_stake = (pending_root_alpha as f64) * (1f64 - validator_take_percent) / 3f64; + + assert_eq!(2 * alice_stake, bob_stake); + + assert_abs_diff_eq!(alice_stake, estimated_stake as u64, epsilon = 100u64,); + }); } From e3cc8e10295bb5b644161a85b7a26181011b6657 Mon Sep 17 00:00:00 2001 From: Shamil Gadelshin Date: Fri, 19 Sep 2025 15:29:22 +0300 Subject: [PATCH 20/54] Add test with removed stake. --- pallets/subtensor/src/tests/claim_root.rs | 135 +++++++++++++++++++++- 1 file changed, 134 insertions(+), 1 deletion(-) diff --git a/pallets/subtensor/src/tests/claim_root.rs b/pallets/subtensor/src/tests/claim_root.rs index 4fc99d7f9b..de2c40814c 100644 --- a/pallets/subtensor/src/tests/claim_root.rs +++ b/pallets/subtensor/src/tests/claim_root.rs @@ -1,6 +1,6 @@ +use crate::RootClaimable; use crate::tests::mock::{RuntimeOrigin, SubtensorModule, Test, add_dynamic_network, new_test_ext}; use crate::{RootClaimType, RootClaimTypeEnum, RootClaimed}; -use crate::{RootClaimable}; use approx::assert_abs_diff_eq; use frame_support::assert_ok; use sp_core::U256; @@ -332,3 +332,136 @@ fn test_adding_stake_disproportionally_for_two_stakers() { assert_abs_diff_eq!(alice_stake, estimated_stake as u64, epsilon = 100u64,); }); } + +#[test] +fn test_claim_root_with_removed_stake() { + new_test_ext(1).execute_with(|| { + let owner_coldkey = U256::from(1001); + let hotkey = U256::from(1002); + let coldkey = U256::from(1003); + let netuid = add_dynamic_network(&hotkey, &owner_coldkey); + let validator_take_percent = 0.18f64; + + SubtensorModule::set_tao_weight(u64::MAX); // Set TAO weight to 1.0 + + let root_stake = 2_000_000u64; + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &coldkey, + NetUid::ROOT, + root_stake.into(), + ); + + let initial_total_hotkey_alpha = 10_000_000u64; + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &owner_coldkey, + netuid, + initial_total_hotkey_alpha.into(), + ); + + let old_validator_stake = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &owner_coldkey, + netuid, + ); + assert_eq!(old_validator_stake, initial_total_hotkey_alpha.into()); + + // Distribute pending root alpha + + let pending_root_alpha = 20_000_000u64; + SubtensorModule::drain_pending_emission( + netuid, + AlphaCurrency::ZERO, + pending_root_alpha.into(), + AlphaCurrency::ZERO, + ); + + // Check claimable + + let claimable = RootClaimable::::get(hotkey, netuid); + let calculated_rate = + (pending_root_alpha as f64) * (1f64 - validator_take_percent) / (root_stake as f64); + + assert_abs_diff_eq!( + claimable.saturating_to_num::(), + calculated_rate, + epsilon = 0.001f64, + ); + + // Claim root alpha + + assert_ok!(SubtensorModule::set_root_claim_type( + RuntimeOrigin::signed(coldkey), + RootClaimTypeEnum::Keep + ),); + assert_eq!(RootClaimType::::get(coldkey), RootClaimTypeEnum::Keep); + + assert_ok!(SubtensorModule::claim_root(RuntimeOrigin::signed(coldkey),)); + + let new_stake: u64 = + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid) + .into(); + + assert_abs_diff_eq!( + new_stake, + (I96F32::from(root_stake) * claimable).saturating_to_num::(), + epsilon = 10u64, + ); + + // Check root claimed value saved + + let claimed = RootClaimed::::get((&hotkey, &coldkey, netuid)); + assert_eq!(u128::from(new_stake), claimed); + + // Distribute pending root alpha (round 2) + + let root_stake_decrement = 1_000_000u64; + let decreased_root_stake = root_stake - root_stake_decrement; + SubtensorModule::decrease_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &coldkey, + NetUid::ROOT, + root_stake_decrement.into(), + ); + + SubtensorModule::drain_pending_emission( + netuid, + AlphaCurrency::ZERO, + pending_root_alpha.into(), + AlphaCurrency::ZERO, + ); + + // Check claimable (round 2) + + let claimable2 = RootClaimable::::get(hotkey, netuid); + let calculated_rate = (pending_root_alpha as f64) * (1f64 - validator_take_percent) + / (decreased_root_stake as f64); + + assert_abs_diff_eq!( + claimable2.saturating_to_num::(), + calculated_rate + claimable.saturating_to_num::(), + epsilon = 0.001f64, + ); + + assert_ok!(SubtensorModule::claim_root(RuntimeOrigin::signed(coldkey),)); + + let new_stake2: u64 = + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid) + .into(); + let calculated_new_stake2 = (I96F32::from(decreased_root_stake) * claimable2) + .saturating_to_num::() + - (claimed as u64); + + assert_abs_diff_eq!( + u64::from(new_stake2), + new_stake + calculated_new_stake2, + epsilon = 10u64, + ); + + // Check root claimed value saved (round 2) + + let claimed2 = RootClaimed::::get((&hotkey, &coldkey, netuid)); + assert_eq!(u128::from(u64::from(new_stake2)), claimed2); + }); +} From dd44dd8964051222fedfd2f057cbb09601002fc8 Mon Sep 17 00:00:00 2001 From: Shamil Gadelshin Date: Fri, 19 Sep 2025 16:09:39 +0300 Subject: [PATCH 21/54] Fix existing tests --- pallets/subtensor/src/tests/coinbase.rs | 69 +------------------------ 1 file changed, 2 insertions(+), 67 deletions(-) diff --git a/pallets/subtensor/src/tests/coinbase.rs b/pallets/subtensor/src/tests/coinbase.rs index 104a06dccd..c19799b582 100644 --- a/pallets/subtensor/src/tests/coinbase.rs +++ b/pallets/subtensor/src/tests/coinbase.rs @@ -557,11 +557,6 @@ fn test_pending_emission() { 1_000_000_000 - 125000000, epsilon = 1 ); // 1 - swapped. - assert_abs_diff_eq!( - u64::from(PendingRootDivs::::get(netuid)), - 125000000, - epsilon = 1 - ); // swapped * (price = 1) }); } @@ -797,7 +792,6 @@ fn test_drain_base_with_subnet_with_two_stakers_registered_and_root() { SubtensorModule::drain_pending_emission( netuid, pending_alpha, - // pending_tao, AlphaCurrency::ZERO, AlphaCurrency::ZERO, ); @@ -825,16 +819,6 @@ fn test_drain_base_with_subnet_with_two_stakers_registered_and_root() { stake_after2.into(), 10, ); // Registered gets 1/2 emission. - close( - stake_before.to_u64() + pending_tao.to_u64() / 2, - root_after1.into(), - 10, - ); // Registered gets 1/2 tao emission - close( - stake_before.to_u64() + pending_tao.to_u64() / 2, - root_after2.into(), - 10, - ); // Registered gets 1/2 tao emission }); } @@ -883,7 +867,6 @@ fn test_drain_base_with_subnet_with_two_stakers_registered_and_root_different_am SubtensorModule::drain_pending_emission( netuid, pending_alpha, - // pending_tao, AlphaCurrency::ZERO, 0.into(), ); @@ -915,25 +898,6 @@ fn test_drain_base_with_subnet_with_two_stakers_registered_and_root_different_am stake_after2.into(), epsilon = 10 ); // Registered gets 50% emission - let expected_root1 = I96F32::from_num(2 * u64::from(stake_before)) - + I96F32::from_num(pending_tao.to_u64()) * I96F32::from_num(2.0 / 3.0); - assert_abs_diff_eq!( - expected_root1.to_num::(), - root_after1.into(), - epsilon = 10 - ); // Registered gets 2/3 tao emission - let expected_root2 = I96F32::from_num(u64::from(stake_before)) - + I96F32::from_num(pending_tao.to_u64()) * I96F32::from_num(1.0 / 3.0); - assert_abs_diff_eq!( - expected_root2.to_num::(), - root_after2.into(), - epsilon = 10 - ); // Registered gets 1/3 tao emission - assert_abs_diff_eq!( - SubnetTAO::::get(NetUid::ROOT), - pending_tao, - epsilon = 10.into() - ); }); } @@ -1015,27 +979,6 @@ fn test_drain_base_with_subnet_with_two_stakers_registered_and_root_different_am u64::from(stake_after2), epsilon = 10 ); - // hotkey 1 has 2 / 3 root tao - let expected_root1 = I96F32::from_num(2 * u64::from(stake_before)) - + I96F32::from_num(pending_tao) * I96F32::from_num(2.0 / 3.0); - assert_abs_diff_eq!( - expected_root1.to_num::(), - u64::from(root_after1), - epsilon = 10 - ); - // hotkey 1 has 1 / 3 root tao - let expected_root2 = I96F32::from_num(u64::from(stake_before)) - + I96F32::from_num(pending_tao) * I96F32::from_num(1.0 / 3.0); - assert_abs_diff_eq!( - expected_root2.to_num::(), - u64::from(root_after2), - epsilon = 10 - ); - assert_abs_diff_eq!( - SubnetTAO::::get(NetUid::ROOT), - pending_tao, - epsilon = 10.into() - ); }); } @@ -1322,16 +1265,13 @@ fn test_get_root_children_drain() { // Alice and Bob both made half of the dividends. assert_eq!( SubtensorModule::get_stake_for_hotkey_on_subnet(&alice, NetUid::ROOT), - AlphaCurrency::from(alice_root_stake + pending_root1.to_u64() / 2) + AlphaCurrency::from(alice_root_stake) ); assert_eq!( SubtensorModule::get_stake_for_hotkey_on_subnet(&bob, NetUid::ROOT), - AlphaCurrency::from(bob_root_stake + pending_root1.to_u64() / 2) + AlphaCurrency::from(bob_root_stake) ); - // The pending root dividends should be present in root subnet. - assert_eq!(SubnetTAO::::get(NetUid::ROOT), pending_root1); - // Lets change the take value. (Bob is greedy.) ChildkeyTake::::insert(bob, alpha, u16::MAX); @@ -1356,11 +1296,6 @@ fn test_get_root_children_drain() { pending_alpha, epsilon = 1.into() ); - // The pending root dividends should be present in root subnet. - assert_eq!( - SubnetTAO::::get(NetUid::ROOT), - pending_root1 + pending_root2 - ); }); } From 0b13d2e7c34fce8a4b3aa1da33f4c8228dd250b3 Mon Sep 17 00:00:00 2001 From: Shamil Gadelshin Date: Mon, 22 Sep 2025 15:36:16 +0300 Subject: [PATCH 22/54] Update tests --- pallets/subtensor/src/staking/claim_root.rs | 20 +- pallets/subtensor/src/tests/claim_root.rs | 205 +++++++++++++------- 2 files changed, 141 insertions(+), 84 deletions(-) diff --git a/pallets/subtensor/src/staking/claim_root.rs b/pallets/subtensor/src/staking/claim_root.rs index b376be790e..32a8b3afe7 100644 --- a/pallets/subtensor/src/staking/claim_root.rs +++ b/pallets/subtensor/src/staking/claim_root.rs @@ -198,15 +198,15 @@ impl Pallet { ) { // Iterate over all the subnets this hotkey is staked on for root. for (netuid, claimable_rate) in RootClaimable::::iter_prefix(hotkey) { - // Get the total claimable_rate for this hotkey and this network - let claimable_rate_u128: u128 = claimable_rate.saturating_to_num(); - // Get current staker root claimed value. let root_claimed: u128 = RootClaimed::::get((hotkey, coldkey, netuid)); // Increase root claimed based on the claimable rate. - let new_root_claimed = - root_claimed.saturating_add(claimable_rate_u128.saturating_mul(amount.into())); + let new_root_claimed = root_claimed.saturating_add( + claimable_rate + .saturating_mul(I96F32::from(u64::from(amount))) + .saturating_to_num(), + ); // Set the new root claimed value. RootClaimed::::insert((hotkey, coldkey, netuid), new_root_claimed); @@ -224,15 +224,15 @@ impl Pallet { continue; // Skip the root netuid. } - // Get the total claimable_rate for this hotkey and this network - let claimable_rate_u128: u128 = claimable_rate.saturating_to_num(); - // Get current staker root claimed value. let root_claimed: u128 = RootClaimed::::get((hotkey, coldkey, netuid)); // Decrease root claimed based on the claimable rate. - let new_root_claimed = root_claimed - .saturating_sub(claimable_rate_u128.saturating_mul(u64::from(amount).into())); + let new_root_claimed = root_claimed.saturating_sub( + claimable_rate + .saturating_mul(I96F32::from(u64::from(amount))) + .saturating_to_num(), + ); // Set the new root_claimed value. RootClaimed::::insert((hotkey, coldkey, netuid), new_root_claimed); diff --git a/pallets/subtensor/src/tests/claim_root.rs b/pallets/subtensor/src/tests/claim_root.rs index de2c40814c..76626b11f9 100644 --- a/pallets/subtensor/src/tests/claim_root.rs +++ b/pallets/subtensor/src/tests/claim_root.rs @@ -1,5 +1,5 @@ -use crate::RootClaimable; use crate::tests::mock::{RuntimeOrigin, SubtensorModule, Test, add_dynamic_network, new_test_ext}; +use crate::{NetworksAdded, RootClaimable, SubtokenEnabled}; use crate::{RootClaimType, RootClaimTypeEnum, RootClaimed}; use approx::assert_abs_diff_eq; use frame_support::assert_ok; @@ -8,7 +8,7 @@ use substrate_fixed::types::I96F32; use subtensor_runtime_common::{AlphaCurrency, Currency, NetUid}; #[test] -fn test_set_root_claim_type() { +fn test_claim_root_set_claim_type() { new_test_ext(1).execute_with(|| { let coldkey = U256::from(1); @@ -161,7 +161,7 @@ fn test_claim_root_with_drain_emissions() { } #[test] -fn test_adding_stake_proportionally_for_two_stakers() { +fn test_claim_root_adding_stake_proportionally_for_two_stakers() { new_test_ext(1).execute_with(|| { let owner_coldkey = U256::from(1001); let hotkey = U256::from(1002); @@ -247,7 +247,7 @@ fn test_adding_stake_proportionally_for_two_stakers() { } #[test] -fn test_adding_stake_disproportionally_for_two_stakers() { +fn test_claim_root_adding_stake_disproportionally_for_two_stakers() { new_test_ext(1).execute_with(|| { let owner_coldkey = U256::from(1001); let hotkey = U256::from(1002); @@ -334,20 +334,28 @@ fn test_adding_stake_disproportionally_for_two_stakers() { } #[test] -fn test_claim_root_with_removed_stake() { +fn test_claim_root_with_changed_stake() { new_test_ext(1).execute_with(|| { let owner_coldkey = U256::from(1001); let hotkey = U256::from(1002); - let coldkey = U256::from(1003); + let alice_coldkey = U256::from(1003); + let bob_coldkey = U256::from(1004); let netuid = add_dynamic_network(&hotkey, &owner_coldkey); - let validator_take_percent = 0.18f64; SubtensorModule::set_tao_weight(u64::MAX); // Set TAO weight to 1.0 + SubtokenEnabled::::insert(NetUid::ROOT, true); + NetworksAdded::::insert(NetUid::ROOT, true); - let root_stake = 2_000_000u64; + let root_stake = 8_000_000u64; SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( &hotkey, - &coldkey, + &alice_coldkey, + NetUid::ROOT, + root_stake.into(), + ); + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &bob_coldkey, NetUid::ROOT, root_stake.into(), ); @@ -360,16 +368,20 @@ fn test_claim_root_with_removed_stake() { initial_total_hotkey_alpha.into(), ); - let old_validator_stake = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( - &hotkey, - &owner_coldkey, - netuid, - ); - assert_eq!(old_validator_stake, initial_total_hotkey_alpha.into()); + // Claim root alpha + + assert_ok!(SubtensorModule::set_root_claim_type( + RuntimeOrigin::signed(alice_coldkey), + RootClaimTypeEnum::Keep + ),); + assert_ok!(SubtensorModule::set_root_claim_type( + RuntimeOrigin::signed(bob_coldkey), + RootClaimTypeEnum::Keep + ),); // Distribute pending root alpha - let pending_root_alpha = 20_000_000u64; + let pending_root_alpha = 10_000_000u64; SubtensorModule::drain_pending_emission( netuid, AlphaCurrency::ZERO, @@ -377,54 +389,100 @@ fn test_claim_root_with_removed_stake() { AlphaCurrency::ZERO, ); - // Check claimable + assert_ok!(SubtensorModule::claim_root(RuntimeOrigin::signed( + alice_coldkey + ),)); + assert_ok!(SubtensorModule::claim_root(RuntimeOrigin::signed( + bob_coldkey + ),)); - let claimable = RootClaimable::::get(hotkey, netuid); - let calculated_rate = - (pending_root_alpha as f64) * (1f64 - validator_take_percent) / (root_stake as f64); + // Check stakes + let validator_take_percent = 0.18f64; - assert_abs_diff_eq!( - claimable.saturating_to_num::(), - calculated_rate, - epsilon = 0.001f64, - ); + let alice_stake: u64 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &alice_coldkey, + netuid, + ) + .into(); - // Claim root alpha + let bob_stake: u64 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &bob_coldkey, + netuid, + ) + .into(); - assert_ok!(SubtensorModule::set_root_claim_type( - RuntimeOrigin::signed(coldkey), - RootClaimTypeEnum::Keep - ),); - assert_eq!(RootClaimType::::get(coldkey), RootClaimTypeEnum::Keep); + let estimated_stake = (pending_root_alpha as f64) * (1f64 - validator_take_percent) / 2f64; - assert_ok!(SubtensorModule::claim_root(RuntimeOrigin::signed(coldkey),)); + assert_eq!(alice_stake, bob_stake); - let new_stake: u64 = - SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid) - .into(); + assert_abs_diff_eq!(alice_stake, estimated_stake as u64, epsilon = 100u64,); - assert_abs_diff_eq!( - new_stake, - (I96F32::from(root_stake) * claimable).saturating_to_num::(), - epsilon = 10u64, + // Remove stake + let stake_decrement = root_stake / 2u64; + + assert_ok!(SubtensorModule::remove_stake( + RuntimeOrigin::signed(bob_coldkey,), + hotkey, + NetUid::ROOT, + stake_decrement.into(), + )); + + // Distribute pending root alpha + + let pending_root_alpha = 10_000_000u64; + SubtensorModule::drain_pending_emission( + netuid, + AlphaCurrency::ZERO, + pending_root_alpha.into(), + AlphaCurrency::ZERO, ); - // Check root claimed value saved + assert_ok!(SubtensorModule::claim_root(RuntimeOrigin::signed( + alice_coldkey + ),)); + assert_ok!(SubtensorModule::claim_root(RuntimeOrigin::signed( + bob_coldkey + ),)); - let claimed = RootClaimed::::get((&hotkey, &coldkey, netuid)); - assert_eq!(u128::from(new_stake), claimed); + // Check new stakes - // Distribute pending root alpha (round 2) + let alice_stake2: u64 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &alice_coldkey, + netuid, + ) + .into(); - let root_stake_decrement = 1_000_000u64; - let decreased_root_stake = root_stake - root_stake_decrement; - SubtensorModule::decrease_stake_for_hotkey_and_coldkey_on_subnet( + let bob_stake2: u64 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( &hotkey, - &coldkey, + &bob_coldkey, + netuid, + ) + .into(); + + let estimated_stake = (pending_root_alpha as f64) * (1f64 - validator_take_percent) / 3f64; + + let alice_stake_diff = alice_stake2 - alice_stake; + let bob_stake_diff = bob_stake2 - bob_stake; + + assert_abs_diff_eq!(alice_stake_diff, 2 * bob_stake_diff, epsilon = 100u64,); + assert_abs_diff_eq!(bob_stake_diff, estimated_stake as u64, epsilon = 100u64,); + + // Add stake + let stake_increment = root_stake / 2u64; + + assert_ok!(SubtensorModule::add_stake( + RuntimeOrigin::signed(bob_coldkey,), + hotkey, NetUid::ROOT, - root_stake_decrement.into(), - ); + stake_increment.into(), + )); + // Distribute pending root alpha + + let pending_root_alpha = 10_000_000u64; SubtensorModule::drain_pending_emission( netuid, AlphaCurrency::ZERO, @@ -432,36 +490,35 @@ fn test_claim_root_with_removed_stake() { AlphaCurrency::ZERO, ); - // Check claimable (round 2) - - let claimable2 = RootClaimable::::get(hotkey, netuid); - let calculated_rate = (pending_root_alpha as f64) * (1f64 - validator_take_percent) - / (decreased_root_stake as f64); + assert_ok!(SubtensorModule::claim_root(RuntimeOrigin::signed( + alice_coldkey + ),)); + assert_ok!(SubtensorModule::claim_root(RuntimeOrigin::signed( + bob_coldkey + ),)); - assert_abs_diff_eq!( - claimable2.saturating_to_num::(), - calculated_rate + claimable.saturating_to_num::(), - epsilon = 0.001f64, - ); + // Check new stakes - assert_ok!(SubtensorModule::claim_root(RuntimeOrigin::signed(coldkey),)); + let alice_stake3: u64 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &alice_coldkey, + netuid, + ) + .into(); - let new_stake2: u64 = - SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid) - .into(); - let calculated_new_stake2 = (I96F32::from(decreased_root_stake) * claimable2) - .saturating_to_num::() - - (claimed as u64); + let bob_stake3: u64 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &bob_coldkey, + netuid, + ) + .into(); - assert_abs_diff_eq!( - u64::from(new_stake2), - new_stake + calculated_new_stake2, - epsilon = 10u64, - ); + let estimated_stake = (pending_root_alpha as f64) * (1f64 - validator_take_percent) / 2f64; - // Check root claimed value saved (round 2) + let alice_stake_diff2 = alice_stake3 - alice_stake2; + let bob_stake_diff2 = bob_stake3 - bob_stake2; - let claimed2 = RootClaimed::::get((&hotkey, &coldkey, netuid)); - assert_eq!(u128::from(u64::from(new_stake2)), claimed2); + assert_abs_diff_eq!(alice_stake_diff2, bob_stake_diff2, epsilon = 100u64,); + assert_abs_diff_eq!(bob_stake_diff2, estimated_stake as u64, epsilon = 100u64,); }); } From 8d1140db45760c108ba930bd7cd4a4e476369078 Mon Sep 17 00:00:00 2001 From: Shamil Gadelshin Date: Mon, 22 Sep 2025 18:44:22 +0300 Subject: [PATCH 23/54] Fix swap claim root --- pallets/subtensor/src/staking/claim_root.rs | 25 ++- pallets/subtensor/src/tests/claim_root.rs | 197 +++++++++++++++++++- 2 files changed, 213 insertions(+), 9 deletions(-) diff --git a/pallets/subtensor/src/staking/claim_root.rs b/pallets/subtensor/src/staking/claim_root.rs index 32a8b3afe7..f5402d65fb 100644 --- a/pallets/subtensor/src/staking/claim_root.rs +++ b/pallets/subtensor/src/staking/claim_root.rs @@ -50,6 +50,12 @@ impl Pallet { .checked_div(total) .unwrap_or(I96F32::saturating_from_num(0.0)); + if u64::from(amount) > total.saturating_to_num::() { + log::error!("Not enough root stake. NetUID = {netuid}"); + + return; + } + // Increment claimable for this subnet. RootClaimable::::mutate(hotkey, netuid, |total| { *total = total.saturating_add(increment); @@ -137,13 +143,18 @@ impl Pallet { // Increase stake on root RootClaimTypeEnum::Swap => { // Swap the alpha owed to TAO - let Ok(owed_tao) = Self::swap_alpha_for_tao( + let owed_tao = match Self::swap_alpha_for_tao( netuid, owed_u64.into(), - T::SwapInterface::max_price().into(), + T::SwapInterface::min_price().into(), false, - ) else { - return; // no-op + ) { + Ok(owed_tao) => owed_tao, + Err(err) => { + log::error!("Error swapping alpha for TAO: {err:?}"); + + return; + } }; Self::increase_stake_for_hotkey_and_coldkey_on_subnet( @@ -152,6 +163,12 @@ impl Pallet { NetUid::ROOT, owed_tao.amount_paid_out.into(), ); + + Self::add_stake_adjust_root_claimed_for_hotkey_and_coldkey( + hotkey, + coldkey, + owed_tao.amount_paid_out.into(), + ); } RootClaimTypeEnum::Keep => { // Increase the stake with the alpha owned diff --git a/pallets/subtensor/src/tests/claim_root.rs b/pallets/subtensor/src/tests/claim_root.rs index 76626b11f9..db6a553824 100644 --- a/pallets/subtensor/src/tests/claim_root.rs +++ b/pallets/subtensor/src/tests/claim_root.rs @@ -1,11 +1,15 @@ use crate::tests::mock::{RuntimeOrigin, SubtensorModule, Test, add_dynamic_network, new_test_ext}; -use crate::{NetworksAdded, RootClaimable, SubtokenEnabled}; +use crate::{ + NetworksAdded, RootClaimable, SubnetAlphaIn, SubnetMechanism, SubnetTAO, SubtokenEnabled, + pallet, +}; use crate::{RootClaimType, RootClaimTypeEnum, RootClaimed}; use approx::assert_abs_diff_eq; use frame_support::assert_ok; use sp_core::U256; use substrate_fixed::types::I96F32; -use subtensor_runtime_common::{AlphaCurrency, Currency, NetUid}; +use subtensor_runtime_common::{AlphaCurrency, Currency, NetUid, TaoCurrency}; +use subtensor_swap_interface::SwapHandler; #[test] fn test_claim_root_set_claim_type() { @@ -164,6 +168,7 @@ fn test_claim_root_with_drain_emissions() { fn test_claim_root_adding_stake_proportionally_for_two_stakers() { new_test_ext(1).execute_with(|| { let owner_coldkey = U256::from(1001); + let other_coldkey = U256::from(10010); let hotkey = U256::from(1002); let alice_coldkey = U256::from(1003); let bob_coldkey = U256::from(1004); @@ -185,6 +190,14 @@ fn test_claim_root_adding_stake_proportionally_for_two_stakers() { root_stake.into(), ); + let root_stake_rate = 0.1f64; + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &other_coldkey, + NetUid::ROOT, + (8 * root_stake).into(), + ); + let initial_total_hotkey_alpha = 10_000_000u64; SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( &hotkey, @@ -238,7 +251,8 @@ fn test_claim_root_adding_stake_proportionally_for_two_stakers() { ) .into(); - let estimated_stake = (pending_root_alpha as f64) * (1f64 - validator_take_percent) / 2f64; + let estimated_stake = + (pending_root_alpha as f64) * (1f64 - validator_take_percent) * root_stake_rate; assert_eq!(alice_stake, bob_stake); @@ -250,6 +264,7 @@ fn test_claim_root_adding_stake_proportionally_for_two_stakers() { fn test_claim_root_adding_stake_disproportionally_for_two_stakers() { new_test_ext(1).execute_with(|| { let owner_coldkey = U256::from(1001); + let other_coldkey = U256::from(10010); let hotkey = U256::from(1002); let alice_coldkey = U256::from(1003); let bob_coldkey = U256::from(1004); @@ -259,6 +274,9 @@ fn test_claim_root_adding_stake_disproportionally_for_two_stakers() { let alice_root_stake = 1_000_000u64; let bob_root_stake = 2_000_000u64; + let other_root_stake = 7_000_000u64; + + let alice_root_stake_rate = 0.1f64; SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( &hotkey, &alice_coldkey, @@ -272,6 +290,13 @@ fn test_claim_root_adding_stake_disproportionally_for_two_stakers() { bob_root_stake.into(), ); + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &other_coldkey, + NetUid::ROOT, + (other_root_stake).into(), + ); + let initial_total_hotkey_alpha = 10_000_000u64; SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( &hotkey, @@ -325,11 +350,12 @@ fn test_claim_root_adding_stake_disproportionally_for_two_stakers() { ) .into(); - let estimated_stake = (pending_root_alpha as f64) * (1f64 - validator_take_percent) / 3f64; + let alice_estimated_stake = + (pending_root_alpha as f64) * (1f64 - validator_take_percent) * alice_root_stake_rate; assert_eq!(2 * alice_stake, bob_stake); - assert_abs_diff_eq!(alice_stake, estimated_stake as u64, epsilon = 100u64,); + assert_abs_diff_eq!(alice_stake, alice_estimated_stake as u64, epsilon = 100u64,); }); } @@ -522,3 +548,164 @@ fn test_claim_root_with_changed_stake() { assert_abs_diff_eq!(bob_stake_diff2, estimated_stake as u64, epsilon = 100u64,); }); } + +#[test] +fn test_claim_root_with_drain_emissions_and_swap_claim_type() { + new_test_ext(1).execute_with(|| { + let owner_coldkey = U256::from(1001); + let other_coldkey = U256::from(10010); + let hotkey = U256::from(1002); + let coldkey = U256::from(1003); + let netuid = add_dynamic_network(&hotkey, &owner_coldkey); + + SubtensorModule::set_tao_weight(u64::MAX); // Set TAO weight to 1.0 + SubnetMechanism::::insert(netuid, 1); + + // let initial_balance = 10_000_000u64; + // SubtensorModule::add_balance_to_coldkey_account(&coldkey, initial_balance.into()); + + let tao_reserve = TaoCurrency::from(50_000_000_000); + let alpha_in = AlphaCurrency::from(100_000_000_000); + SubnetTAO::::insert(netuid, tao_reserve); + SubnetAlphaIn::::insert(netuid, alpha_in); + let current_price = + ::SwapInterface::current_alpha_price(netuid.into()) + .saturating_to_num::(); + assert_eq!(current_price, 0.5f64); + + let root_stake = 2_000_000u64; + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &coldkey, + NetUid::ROOT, + root_stake.into(), + ); + let root_stake_rate = 0.1f64; + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &other_coldkey, + NetUid::ROOT, + (9 * root_stake).into(), + ); + + let initial_total_hotkey_alpha = 10_000_000u64; + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &owner_coldkey, + netuid, + initial_total_hotkey_alpha.into(), + ); + + // Distribute pending root alpha + + let pending_root_alpha = 10_000_000u64; + SubtensorModule::drain_pending_emission( + netuid, + AlphaCurrency::ZERO, + pending_root_alpha.into(), + AlphaCurrency::ZERO, + ); + + // Claim root alpha + + let validator_take_percent = 0.18f64; + + assert_ok!(SubtensorModule::set_root_claim_type( + RuntimeOrigin::signed(coldkey), + RootClaimTypeEnum::Swap + ),); + assert_eq!(RootClaimType::::get(coldkey), RootClaimTypeEnum::Swap); + + assert_ok!(SubtensorModule::claim_root(RuntimeOrigin::signed(coldkey),)); + + // Check new stake + + let new_stake: u64 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &coldkey, + NetUid::ROOT, + ) + .into(); + + let estimated_stake_increment = (pending_root_alpha as f64) + * (1f64 - validator_take_percent) + * current_price + * root_stake_rate; + + assert_abs_diff_eq!( + new_stake, + root_stake + estimated_stake_increment as u64, + epsilon = 10000u64, + ); + + // Distribute and claim pending root alpha (round 2) + + SubtensorModule::drain_pending_emission( + netuid, + AlphaCurrency::ZERO, + pending_root_alpha.into(), + AlphaCurrency::ZERO, + ); + + assert_ok!(SubtensorModule::claim_root(RuntimeOrigin::signed(coldkey),)); + + // Check new stake (2) + + let new_stake2: u64 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &coldkey, + NetUid::ROOT, + ) + .into(); + + // new root stake / new total stake + let root_stake_rate2 = (root_stake as f64 + estimated_stake_increment) + / (root_stake as f64 / root_stake_rate + estimated_stake_increment); + let estimated_stake_increment2 = (pending_root_alpha as f64) + * (1f64 - validator_take_percent) + * current_price + * root_stake_rate2; + + assert_abs_diff_eq!( + new_stake2, + new_stake + estimated_stake_increment2 as u64, + epsilon = 10000u64, + ); + // Distribute and claim pending root alpha (round 3) + + SubtensorModule::drain_pending_emission( + netuid, + AlphaCurrency::ZERO, + pending_root_alpha.into(), + AlphaCurrency::ZERO, + ); + + assert_ok!(SubtensorModule::claim_root(RuntimeOrigin::signed(coldkey),)); + + // Check new stake (3) + + let new_stake3: u64 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &coldkey, + NetUid::ROOT, + ) + .into(); + + // new root stake / new total stake + let root_stake_rate3 = + (root_stake as f64 + estimated_stake_increment + estimated_stake_increment2) + / (root_stake as f64 / root_stake_rate + + estimated_stake_increment + + estimated_stake_increment2); + let estimated_stake_increment3 = (pending_root_alpha as f64) + * (1f64 - validator_take_percent) + * current_price + * root_stake_rate3; + + assert_abs_diff_eq!( + new_stake3, + new_stake2 + estimated_stake_increment3 as u64, + epsilon = 10000u64, + ); + }); +} From 65bb4f010509be1a56777da23719b43d2f99a4fe Mon Sep 17 00:00:00 2001 From: Shamil Gadelshin Date: Wed, 24 Sep 2025 17:24:18 +0300 Subject: [PATCH 24/54] Add basic run_coinbase test --- pallets/subtensor/src/tests/claim_root.rs | 66 ++++++++++++++++++++++- 1 file changed, 64 insertions(+), 2 deletions(-) diff --git a/pallets/subtensor/src/tests/claim_root.rs b/pallets/subtensor/src/tests/claim_root.rs index db6a553824..7eaeef54f7 100644 --- a/pallets/subtensor/src/tests/claim_root.rs +++ b/pallets/subtensor/src/tests/claim_root.rs @@ -1,13 +1,13 @@ use crate::tests::mock::{RuntimeOrigin, SubtensorModule, Test, add_dynamic_network, new_test_ext}; use crate::{ NetworksAdded, RootClaimable, SubnetAlphaIn, SubnetMechanism, SubnetTAO, SubtokenEnabled, - pallet, + Tempo, pallet, }; use crate::{RootClaimType, RootClaimTypeEnum, RootClaimed}; use approx::assert_abs_diff_eq; use frame_support::assert_ok; use sp_core::U256; -use substrate_fixed::types::I96F32; +use substrate_fixed::types::{I96F32, U96F32}; use subtensor_runtime_common::{AlphaCurrency, Currency, NetUid, TaoCurrency}; use subtensor_swap_interface::SwapHandler; @@ -709,3 +709,65 @@ fn test_claim_root_with_drain_emissions_and_swap_claim_type() { ); }); } + +#[test] +fn test_claim_root_with_run_coinbase() { + new_test_ext(1).execute_with(|| { + let owner_coldkey = U256::from(1001); + let hotkey = U256::from(1002); + let coldkey = U256::from(1003); + let netuid = add_dynamic_network(&hotkey, &owner_coldkey); + + Tempo::::insert(netuid, 1); + SubtensorModule::set_tao_weight(u64::MAX); // Set TAO weight to 1.0 + + let root_stake = 200_000_000u64; + SubnetTAO::::insert(NetUid::ROOT, TaoCurrency::from(root_stake)); + + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &coldkey, + NetUid::ROOT, + root_stake.into(), + ); + + let initial_total_hotkey_alpha = 10_000_000u64; + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &owner_coldkey, + netuid, + initial_total_hotkey_alpha.into(), + ); + + // Distribute pending root alpha + + let initial_stake: u64 = + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid) + .into(); + assert_eq!(initial_stake, 0u64); + + let block_emissions = 1_000_000u64; + SubtensorModule::run_coinbase(U96F32::from(block_emissions)); + + // Claim root alpha + + let initial_stake: u64 = + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid) + .into(); + assert_eq!(initial_stake, 0u64); + + assert_ok!(SubtensorModule::set_root_claim_type( + RuntimeOrigin::signed(coldkey), + RootClaimTypeEnum::Keep + ),); + assert_eq!(RootClaimType::::get(coldkey), RootClaimTypeEnum::Keep); + + assert_ok!(SubtensorModule::claim_root(RuntimeOrigin::signed(coldkey),)); + + let new_stake: u64 = + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid) + .into(); + + assert!(new_stake > 0); + }); +} From 1accdfa3cda5589b630c308aaaabf331f81ee922 Mon Sep 17 00:00:00 2001 From: Shamil Gadelshin Date: Thu, 25 Sep 2025 15:30:37 +0300 Subject: [PATCH 25/54] Fix block indices to claim --- pallets/subtensor/src/lib.rs | 6 +- pallets/subtensor/src/staking/claim_root.rs | 18 +++- pallets/subtensor/src/staking/stake_utils.rs | 1 + pallets/subtensor/src/tests/claim_root.rs | 97 +++++++++++++++++++- 4 files changed, 115 insertions(+), 7 deletions(-) diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index ba48e25544..6e633daab8 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -1865,7 +1865,11 @@ pub mod pallet { >; #[pallet::storage] // --- MAP ( u64 ) --> coldkey | Maps coldkeys that have stake to an index - pub type StakingColdkeys = StorageMap<_, Identity, u64, T::AccountId, OptionQuery>; + pub type StakingColdkeysByIndex = + StorageMap<_, Identity, u64, T::AccountId, OptionQuery>; + + #[pallet::storage] // --- MAP ( coldkey ) --> index | Maps index that have stake to a coldkey + pub type StakingColdkeys = StorageMap<_, Identity, T::AccountId, u64, OptionQuery>; #[pallet::storage] // --- Value --> num_staking_coldkeys pub type NumStakingColdkeys = StorageValue<_, u64, ValueQuery, DefaultZeroU64>; diff --git a/pallets/subtensor/src/staking/claim_root.rs b/pallets/subtensor/src/staking/claim_root.rs index f5402d65fb..8a78cf016b 100644 --- a/pallets/subtensor/src/staking/claim_root.rs +++ b/pallets/subtensor/src/staking/claim_root.rs @@ -1,13 +1,14 @@ use super::*; use frame_support::weights::Weight; use sp_core::Get; +use sp_std::collections::btree_set::BTreeSet; use substrate_fixed::types::I96F32; use subtensor_swap_interface::SwapHandler; impl Pallet { pub fn block_hash_to_indices(block_hash: T::Hash, k: u64, n: u64) -> Vec { let block_hash_bytes = block_hash.as_ref(); - let mut indices: Vec = Vec::new(); + let mut indices: BTreeSet = BTreeSet::new(); // k < n let start_index: u64 = u64::from_be_bytes( block_hash_bytes @@ -30,10 +31,10 @@ impl Pallet { .saturating_add(idx_step) .checked_rem(n) .unwrap_or(0); - indices.push(idx); + indices.insert(idx); last_idx = idx; } - indices + indices.into_iter().collect() } pub fn increase_root_claimable_for_hotkey_and_subnet( @@ -277,6 +278,15 @@ impl Pallet { Weight::default() } + pub fn maybe_add_coldkey_index(coldkey: &T::AccountId) { + if !StakingColdkeys::::contains_key(coldkey) { + let n = NumStakingColdkeys::::get(); + StakingColdkeysByIndex::::insert(n, coldkey.clone()); + StakingColdkeys::::insert(coldkey.clone(), n); + NumStakingColdkeys::::mutate(|n| *n = n.saturating_add(1)); + } + } + pub fn run_auto_claim_root_divs(last_block_hash: T::Hash) -> Weight { let mut weight: Weight = Weight::default(); @@ -289,7 +299,7 @@ impl Pallet { for i in coldkeys_to_claim.iter() { weight.saturating_accrue(T::DbWeight::get().reads(1)); - if let Ok(coldkey) = StakingColdkeys::::try_get(i) { + if let Ok(coldkey) = StakingColdkeysByIndex::::try_get(i) { weight.saturating_accrue(Self::do_root_claim(coldkey.clone())); } diff --git a/pallets/subtensor/src/staking/stake_utils.rs b/pallets/subtensor/src/staking/stake_utils.rs index e38e46fea6..5944488a44 100644 --- a/pallets/subtensor/src/staking/stake_utils.rs +++ b/pallets/subtensor/src/staking/stake_utils.rs @@ -830,6 +830,7 @@ impl Pallet { // Adjust root claimed for this hotkey and coldkey. let alpha = swap_result.amount_paid_out.into(); Self::add_stake_adjust_root_claimed_for_hotkey_and_coldkey(hotkey, coldkey, alpha); + Self::maybe_add_coldkey_index(coldkey); } // Deposit and log the staking event. diff --git a/pallets/subtensor/src/tests/claim_root.rs b/pallets/subtensor/src/tests/claim_root.rs index 7eaeef54f7..7a8e5707a1 100644 --- a/pallets/subtensor/src/tests/claim_root.rs +++ b/pallets/subtensor/src/tests/claim_root.rs @@ -1,4 +1,6 @@ -use crate::tests::mock::{RuntimeOrigin, SubtensorModule, Test, add_dynamic_network, new_test_ext}; +use crate::tests::mock::{ + RuntimeOrigin, SubtensorModule, Test, add_dynamic_network, new_test_ext, run_to_block, +}; use crate::{ NetworksAdded, RootClaimable, SubnetAlphaIn, SubnetMechanism, SubnetTAO, SubtokenEnabled, Tempo, pallet, @@ -6,7 +8,7 @@ use crate::{ use crate::{RootClaimType, RootClaimTypeEnum, RootClaimed}; use approx::assert_abs_diff_eq; use frame_support::assert_ok; -use sp_core::U256; +use sp_core::{H256, U256}; use substrate_fixed::types::{I96F32, U96F32}; use subtensor_runtime_common::{AlphaCurrency, Currency, NetUid, TaoCurrency}; use subtensor_swap_interface::SwapHandler; @@ -771,3 +773,94 @@ fn test_claim_root_with_run_coinbase() { assert!(new_stake > 0); }); } + +#[test] +fn test_claim_root_bloch_hash_indices() { + new_test_ext(1).execute_with(|| { + let k = 15u64; + let n = 15000u64; + + // 1 + let hash = sp_core::keccak_256(b"some"); + let mut indices = SubtensorModule::block_hash_to_indices(H256(hash), k, n); + indices.sort(); + + assert!(indices.len() <= k as usize); + assert!(!indices.iter().any(|i| *i >= n)); + // precomputed values + let expected_result = vec![ + 265, 630, 1286, 1558, 4496, 4861, 5517, 5789, 6803, 8096, 9092, 11034, 11399, 12055, + 12327, + ]; + assert_eq!(indices, expected_result); + + // 2 + let hash = sp_core::keccak_256(b"some2"); + let mut indices = SubtensorModule::block_hash_to_indices(H256(hash), k, n); + indices.sort(); + + assert!(indices.len() <= k as usize); + assert!(!indices.iter().any(|i| *i >= n)); + // precomputed values + let expected_result = vec![ + 61, 246, 1440, 2855, 3521, 5236, 6130, 6615, 8511, 9405, 9890, 11786, 11971, 13165, + 14580, + ]; + assert_eq!(indices, expected_result); + }); +} + +#[test] +fn test_claim_root_with_block_emissions() { + new_test_ext(0).execute_with(|| { + let owner_coldkey = U256::from(1001); + let hotkey = U256::from(1002); + let coldkey = U256::from(1003); + let netuid = add_dynamic_network(&hotkey, &owner_coldkey); + + Tempo::::insert(netuid, 1); + SubtensorModule::set_tao_weight(u64::MAX); // Set TAO weight to 1.0 + + let root_stake = 200_000_000u64; + SubnetTAO::::insert(NetUid::ROOT, TaoCurrency::from(root_stake)); + + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &coldkey, + NetUid::ROOT, + root_stake.into(), + ); + SubtensorModule::maybe_add_coldkey_index(&coldkey); + + let initial_total_hotkey_alpha = 10_000_000u64; + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &owner_coldkey, + netuid, + initial_total_hotkey_alpha.into(), + ); + + assert_ok!(SubtensorModule::set_root_claim_type( + RuntimeOrigin::signed(coldkey), + RootClaimTypeEnum::Keep + ),); + assert_eq!(RootClaimType::::get(coldkey), RootClaimTypeEnum::Keep); + + // Distribute pending root alpha + + let initial_stake: u64 = + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid) + .into(); + assert_eq!(initial_stake, 0u64); + + run_to_block(2); + + // Check stake after block emissions + + let new_stake: u64 = + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid) + .into(); + + assert!(new_stake > 0); + }); +} From c09bcb3533b1ba0973fa38f37e57f3e28c6dcd20 Mon Sep 17 00:00:00 2001 From: Shamil Gadelshin Date: Fri, 26 Sep 2025 14:45:50 +0300 Subject: [PATCH 26/54] Optimize RootClaimable --- pallets/subtensor/src/lib.rs | 13 ++++++----- pallets/subtensor/src/staking/claim_root.rs | 24 ++++++++++++++------- pallets/subtensor/src/tests/claim_root.rs | 8 +++++-- 3 files changed, 28 insertions(+), 17 deletions(-) diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 6e633daab8..127a2e9cd6 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -81,6 +81,7 @@ pub mod pallet { use runtime_common::prod_or_fast; use sp_core::{ConstU32, H160, H256}; use sp_runtime::traits::{Dispatchable, TrailingZeroInput}; + use sp_std::collections::btree_map::BTreeMap; use sp_std::collections::vec_deque::VecDeque; use sp_std::vec; use sp_std::vec::Vec; @@ -896,8 +897,8 @@ pub mod pallet { #[pallet::type_value] /// Default subnet root claimable - pub fn DefaultRootClaimable() -> I96F32 { - I96F32::saturating_from_num(0.0) + pub fn DefaultRootClaimable() -> BTreeMap { + Default::default() } #[pallet::type_value] /// Default value for Share Pool variables @@ -1821,14 +1822,12 @@ pub mod pallet { ValueQuery, >; - #[pallet::storage] // --- DMAP ( hot, netuid ) --> claimable_dividends | Root claimable dividends. - pub type RootClaimable = StorageDoubleMap< + #[pallet::storage] // --- MAP ( hot ) --> MAP(netuid ) --> claimable_dividends | Root claimable dividends. + pub type RootClaimable = StorageMap< _, Blake2_128Concat, T::AccountId, - Identity, - NetUid, - I96F32, + BTreeMap, ValueQuery, DefaultRootClaimable, >; diff --git a/pallets/subtensor/src/staking/claim_root.rs b/pallets/subtensor/src/staking/claim_root.rs index 8a78cf016b..48a3ab78a1 100644 --- a/pallets/subtensor/src/staking/claim_root.rs +++ b/pallets/subtensor/src/staking/claim_root.rs @@ -58,8 +58,11 @@ impl Pallet { } // Increment claimable for this subnet. - RootClaimable::::mutate(hotkey, netuid, |total| { - *total = total.saturating_add(increment); + RootClaimable::::mutate(hotkey, |claimable| { + claimable + .entry(netuid) + .and_modify(|total| *total = total.saturating_add(increment)) + .or_insert(increment); }); } @@ -74,7 +77,9 @@ impl Pallet { ); // Get the total claimable_rate for this hotkey and this network - let claimable_rate: I96F32 = RootClaimable::::get(hotkey, netuid); + let claimable_rate: I96F32 = *RootClaimable::::get(hotkey) + .get(&netuid) + .unwrap_or(&I96F32::from(0)); // Compute the proportion owed to this coldkey via balance. let claimable: I96F32 = claimable_rate.saturating_mul(root_stake); @@ -199,11 +204,12 @@ impl Pallet { let root_claim_type = RootClaimType::::get(coldkey); // Iterate over all the subnets this hotkey has claimable for root. - RootClaimable::::iter_prefix(hotkey).for_each(|(netuid, _)| { + let root_claimable = RootClaimable::::get(hotkey); + root_claimable.iter().for_each(|(netuid, _)| { weight.saturating_accrue(T::DbWeight::get().reads(1)); weight.saturating_accrue(Self::root_claim_on_subnet_weight(root_claim_type.clone())); - Self::root_claim_on_subnet(hotkey, coldkey, netuid, root_claim_type.clone()); + Self::root_claim_on_subnet(hotkey, coldkey, *netuid, root_claim_type.clone()); }); weight @@ -215,7 +221,8 @@ impl Pallet { amount: u64, ) { // Iterate over all the subnets this hotkey is staked on for root. - for (netuid, claimable_rate) in RootClaimable::::iter_prefix(hotkey) { + let root_claimable = RootClaimable::::get(hotkey); + for (netuid, claimable_rate) in root_claimable.iter() { // Get current staker root claimed value. let root_claimed: u128 = RootClaimed::::get((hotkey, coldkey, netuid)); @@ -237,8 +244,9 @@ impl Pallet { amount: AlphaCurrency, ) { // Iterate over all the subnets this hotkey is staked on for root. - for (netuid, claimable_rate) in RootClaimable::::iter_prefix(hotkey) { - if netuid == NetUid::ROOT.into() { + let root_claimable = RootClaimable::::get(hotkey); + for (netuid, claimable_rate) in root_claimable.iter() { + if *netuid == NetUid::ROOT.into() { continue; // Skip the root netuid. } diff --git a/pallets/subtensor/src/tests/claim_root.rs b/pallets/subtensor/src/tests/claim_root.rs index 7a8e5707a1..d28ca3cdd4 100644 --- a/pallets/subtensor/src/tests/claim_root.rs +++ b/pallets/subtensor/src/tests/claim_root.rs @@ -89,7 +89,9 @@ fn test_claim_root_with_drain_emissions() { // Check claimable - let claimable = RootClaimable::::get(hotkey, netuid); + let claimable = *RootClaimable::::get(hotkey) + .get(&netuid) + .expect("claimable must exist at this point"); let calculated_rate = (pending_root_alpha as f64) * (1f64 - validator_take_percent) / (root_stake as f64); @@ -135,7 +137,9 @@ fn test_claim_root_with_drain_emissions() { // Check claimable (round 2) - let claimable2 = RootClaimable::::get(hotkey, netuid); + let claimable2 = *RootClaimable::::get(hotkey) + .get(&netuid) + .expect("claimable must exist at this point"); let calculated_rate = (pending_root_alpha as f64) * (1f64 - validator_take_percent) / (root_stake as f64); From fc161b31f4ac46c1b09017cfaa75dd3d80456c3f Mon Sep 17 00:00:00 2001 From: Shamil Gadelshin Date: Mon, 29 Sep 2025 16:05:12 +0300 Subject: [PATCH 27/54] Update benchmarks --- pallets/subtensor/src/benchmarks.rs | 16 ++++++++++++++++ pallets/subtensor/src/macros/dispatches.rs | 12 ++++++++++-- pallets/subtensor/src/staking/claim_root.rs | 19 ++++++++++--------- 3 files changed, 36 insertions(+), 11 deletions(-) diff --git a/pallets/subtensor/src/benchmarks.rs b/pallets/subtensor/src/benchmarks.rs index 6dd5390992..aff7df65bd 100644 --- a/pallets/subtensor/src/benchmarks.rs +++ b/pallets/subtensor/src/benchmarks.rs @@ -1597,4 +1597,20 @@ mod pallet_benchmarks { #[extrinsic_call] _(RawOrigin::Signed(coldkey.clone()), hot.clone()); } + #[benchmark] + fn set_root_claim_type() { + let coldkey: T::AccountId = whitelisted_caller(); + + #[extrinsic_call] + _(RawOrigin::Signed(coldkey.clone()), RootClaimTypeEnum::Keep); + } + + // TODO: rework after subnets argument + #[benchmark] + fn claim_root() { + let coldkey: T::AccountId = whitelisted_caller(); + + #[extrinsic_call] + _(RawOrigin::Signed(coldkey.clone())); + } } diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index 560c3a8d68..778000cf78 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -2088,7 +2088,11 @@ mod dispatches { /// # Raises: /// #[pallet::call_index(116)] - #[pallet::weight((Weight::from_parts(200_000, 0).saturating_add(T::DbWeight::get().reads_writes(1, 2)), DispatchClass::Normal, Pays::Yes))] + #[pallet::weight(( + Weight::from_parts(13_000_000, 3994).saturating_add(T::DbWeight::get().reads(1_u64)), + DispatchClass::Normal, + Pays::Yes + ))] pub fn claim_root(origin: OriginFor) -> DispatchResultWithPostInfo { let coldkey: T::AccountId = ensure_signed(origin)?; @@ -2106,7 +2110,11 @@ mod dispatches { /// - On the successfully setting the root claim type for the coldkey. /// #[pallet::call_index(117)] - #[pallet::weight((Weight::from_parts(45_000_000, 0).saturating_add(T::DbWeight::get().writes(1)), DispatchClass::Normal, Pays::Yes))] + #[pallet::weight(( + Weight::from_parts(6_000_000, 0).saturating_add(T::DbWeight::get().writes(1_u64)), + DispatchClass::Normal, + Pays::Yes + ))] pub fn set_root_claim_type( origin: OriginFor, new_root_claim_type: RootClaimTypeEnum, diff --git a/pallets/subtensor/src/staking/claim_root.rs b/pallets/subtensor/src/staking/claim_root.rs index 48a3ab78a1..5555275a37 100644 --- a/pallets/subtensor/src/staking/claim_root.rs +++ b/pallets/subtensor/src/staking/claim_root.rs @@ -193,23 +193,24 @@ impl Pallet { }); } - // TODO: weight fn root_claim_on_subnet_weight(_root_claim_type: RootClaimTypeEnum) -> Weight { - Weight::default() + Weight::from_parts(60_000_000, 6987) + .saturating_add(T::DbWeight::get().reads(7_u64)) + .saturating_add(T::DbWeight::get().writes(5_u64)) } pub fn root_claim_all(hotkey: &T::AccountId, coldkey: &T::AccountId) -> Weight { let mut weight = Weight::default(); - weight.saturating_accrue(T::DbWeight::get().reads(1)); let root_claim_type = RootClaimType::::get(coldkey); + weight.saturating_accrue(T::DbWeight::get().reads(1)); // Iterate over all the subnets this hotkey has claimable for root. let root_claimable = RootClaimable::::get(hotkey); - root_claimable.iter().for_each(|(netuid, _)| { - weight.saturating_accrue(T::DbWeight::get().reads(1)); - weight.saturating_accrue(Self::root_claim_on_subnet_weight(root_claim_type.clone())); + weight.saturating_accrue(T::DbWeight::get().reads(1)); + root_claimable.iter().for_each(|(netuid, _)| { Self::root_claim_on_subnet(hotkey, coldkey, *netuid, root_claim_type.clone()); + weight.saturating_accrue(Self::root_claim_on_subnet_weight(root_claim_type.clone())); }); weight @@ -281,9 +282,9 @@ impl Pallet { weight } - // TODO: - fn block_hash_to_indices_weight(_k: u64, _n: u64) -> Weight { - Weight::default() + fn block_hash_to_indices_weight(k: u64, _n: u64) -> Weight { + Weight::from_parts(3_000_000, 1517) + .saturating_add(Weight::from_parts(100_412, 0).saturating_mul(k.into())) } pub fn maybe_add_coldkey_index(coldkey: &T::AccountId) { From b39d51cc46770ee9ccf2a199e573471141918db6 Mon Sep 17 00:00:00 2001 From: Shamil Gadelshin Date: Tue, 30 Sep 2025 16:21:45 +0300 Subject: [PATCH 28/54] Add coldkey to the new map. --- pallets/subtensor/src/macros/dispatches.rs | 4 ++++ pallets/subtensor/src/swap/swap_coldkey.rs | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index 778000cf78..fa68019d56 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -2096,6 +2096,8 @@ mod dispatches { pub fn claim_root(origin: OriginFor) -> DispatchResultWithPostInfo { let coldkey: T::AccountId = ensure_signed(origin)?; + Self::maybe_add_coldkey_index(&coldkey); + let weight = Self::do_root_claim(coldkey); Ok((Some(weight), Pays::Yes).into()) } @@ -2121,6 +2123,8 @@ mod dispatches { ) -> DispatchResult { let coldkey: T::AccountId = ensure_signed(origin)?; + Self::maybe_add_coldkey_index(&coldkey); + Self::change_root_claim_type(&coldkey, new_root_claim_type); Ok(()) } diff --git a/pallets/subtensor/src/swap/swap_coldkey.rs b/pallets/subtensor/src/swap/swap_coldkey.rs index f7f9997183..f5901e0143 100644 --- a/pallets/subtensor/src/swap/swap_coldkey.rs +++ b/pallets/subtensor/src/swap/swap_coldkey.rs @@ -173,6 +173,11 @@ impl Pallet { ); // Remove the value from the old account. Alpha::::remove((&hotkey, old_coldkey, netuid)); + + // Register new coldkey with root stake + if new_alpha > U64F64::from(0u64) && netuid == NetUid::ROOT { + Self::maybe_add_coldkey_index(&new_coldkey); + } } // Add the weight for the read and write. weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); From 57bcae36b7aba747c0fb5a697912271ef9fb6c99 Mon Sep 17 00:00:00 2001 From: Shamil Gadelshin Date: Tue, 30 Sep 2025 16:22:02 +0300 Subject: [PATCH 29/54] Add root staking maps migration. --- pallets/subtensor/src/coinbase/block_step.rs | 2 + pallets/subtensor/src/lib.rs | 13 +++++ pallets/subtensor/src/staking/helpers.rs | 50 +++++++++++++++++++ pallets/subtensor/src/tests/claim_root.rs | 52 ++++++++++++++++++-- 4 files changed, 113 insertions(+), 4 deletions(-) diff --git a/pallets/subtensor/src/coinbase/block_step.rs b/pallets/subtensor/src/coinbase/block_step.rs index 8fd1814961..818235a955 100644 --- a/pallets/subtensor/src/coinbase/block_step.rs +++ b/pallets/subtensor/src/coinbase/block_step.rs @@ -24,6 +24,8 @@ impl Pallet { Self::try_set_pending_children(block_number); // --- 5. Run auto-claim root divs. Self::run_auto_claim_root_divs(last_block_hash); + // --- 6. Populate root coldkey maps. + Self::populate_root_coldkey_staking_maps(); // Return ok. Ok(()) diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 127a2e9cd6..7792482ecc 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -54,6 +54,8 @@ extern crate alloc; pub const MAX_CRV3_COMMIT_SIZE_BYTES: u32 = 5000; +pub const ALPHA_MAP_BATCH_SIZE: usize = 30; + #[allow(deprecated)] #[deny(missing_docs)] #[import_section(errors::errors)] @@ -926,6 +928,12 @@ pub mod pallet { 50400 } + /// Default last Alpha map key for iteration + #[pallet::type_value] + pub fn DefaultAlphaIterationLastKey() -> Option> { + None + } + #[pallet::type_value] /// Default value for ck burn, 18%. pub fn DefaultCKBurn() -> u64 { @@ -1199,6 +1207,11 @@ pub mod pallet { U64F64, // Shares ValueQuery, >; + + #[pallet::storage] // Contains last Alpha storage map key to iterate (check first) + pub type AlphaMapLastKey = + StorageValue<_, Option>, ValueQuery, DefaultAlphaIterationLastKey>; + #[pallet::storage] // --- MAP ( netuid ) --> token_symbol | Returns the token symbol for a subnet. pub type TokenSymbol = StorageMap<_, Identity, NetUid, Vec, ValueQuery, DefaultUnicodeVecU8>; diff --git a/pallets/subtensor/src/staking/helpers.rs b/pallets/subtensor/src/staking/helpers.rs index c89a762f91..db6ea6254c 100644 --- a/pallets/subtensor/src/staking/helpers.rs +++ b/pallets/subtensor/src/staking/helpers.rs @@ -327,4 +327,54 @@ impl Pallet { *total = total.saturating_sub(amount); }); } + + /// The function clears Alpha map in batches. Each run will check ALPHA_MAP_CLEAN_BATCH_SIZE + /// alphas. It keeps the alpha value stored when it's >= than MIN_ALPHA. + /// The function uses AlphaMapCleanLastKey as a storage for key iterator between runs. + pub fn populate_root_coldkey_staking_maps() { + // Get starting key for the batch. Get the first key if we restart the process. + let mut starting_raw_key = AlphaMapLastKey::::get(); + let mut starting_key = None; + if starting_raw_key.is_none() { + starting_key = Alpha::::iter_keys().next(); + starting_raw_key = starting_key.as_ref().map(Alpha::::hashed_key_for); + } + + if let Some(starting_raw_key) = starting_raw_key { + // Get the key batch + let mut keys = Alpha::::iter_keys_from(starting_raw_key) + .take(ALPHA_MAP_BATCH_SIZE) + .collect::>(); + + // New iteration: insert the starting key in the batch if it's a new iteration + // iter_keys_from() skips the starting key + if let Some(starting_key) = starting_key { + if keys.len() == ALPHA_MAP_BATCH_SIZE { + keys.remove(keys.len().saturating_sub(1)); + } + keys.insert(0, starting_key); + } + + let mut new_starting_key = None; + let new_iteration = keys.len() < ALPHA_MAP_BATCH_SIZE; + + // Check and remove alphas if necessary + for key in keys { + let (_, coldkey, netuid) = key.clone(); + + if netuid == NetUid::ROOT { + Self::maybe_add_coldkey_index(&coldkey); + } + + new_starting_key = Some(Alpha::::hashed_key_for(key)); + } + + // Restart the process if it's the last batch + if new_iteration { + new_starting_key = None; + } + + AlphaMapLastKey::::put(new_starting_key); + } + } } diff --git a/pallets/subtensor/src/tests/claim_root.rs b/pallets/subtensor/src/tests/claim_root.rs index d28ca3cdd4..eca6520422 100644 --- a/pallets/subtensor/src/tests/claim_root.rs +++ b/pallets/subtensor/src/tests/claim_root.rs @@ -1,10 +1,7 @@ use crate::tests::mock::{ RuntimeOrigin, SubtensorModule, Test, add_dynamic_network, new_test_ext, run_to_block, }; -use crate::{ - NetworksAdded, RootClaimable, SubnetAlphaIn, SubnetMechanism, SubnetTAO, SubtokenEnabled, - Tempo, pallet, -}; +use crate::{NetworksAdded, RootClaimable, SubnetAlphaIn, SubnetMechanism, SubnetTAO, SubtokenEnabled, Tempo, pallet, NumStakingColdkeys, StakingColdkeys, StakingColdkeysByIndex}; use crate::{RootClaimType, RootClaimTypeEnum, RootClaimed}; use approx::assert_abs_diff_eq; use frame_support::assert_ok; @@ -868,3 +865,50 @@ fn test_claim_root_with_block_emissions() { assert!(new_stake > 0); }); } +#[test] +fn test_populate_staking_maps() { + new_test_ext(1).execute_with(|| { + let owner_coldkey = U256::from(1000); + let coldkey1 = U256::from(1001); + let coldkey2 = U256::from(1002); + let coldkey3 = U256::from(1003); + let hotkey = U256::from(1004); + let _netuid = add_dynamic_network(&hotkey, &owner_coldkey); + let netuid2 = NetUid::from(2); + + let root_stake = 200_000_000u64; + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &coldkey1, + NetUid::ROOT, + root_stake.into(), + ); + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &coldkey2, + NetUid::ROOT, + root_stake.into(), + ); + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &coldkey3, + netuid2, + root_stake.into(), + ); + + assert_eq!(NumStakingColdkeys::::get(), 0); + + // Populate maps through block step + + run_to_block(2); + + assert_eq!(NumStakingColdkeys::::get(), 2); + + assert!(StakingColdkeysByIndex::::contains_key(&0)); + assert!(StakingColdkeysByIndex::::contains_key(&1)); + + assert!(StakingColdkeys::::contains_key(&coldkey1)); + assert!(StakingColdkeys::::contains_key(&coldkey2)); + assert!(!StakingColdkeys::::contains_key(&coldkey3)); + }); +} From cc7a1df05016627d65c810500284ad411f825119 Mon Sep 17 00:00:00 2001 From: Shamil Gadelshin Date: Thu, 2 Oct 2025 18:29:08 +0300 Subject: [PATCH 30/54] Add run_coinbase test for root claim. --- pallets/subtensor/src/tests/claim_root.rs | 80 ++++++++++++++++++++++- 1 file changed, 79 insertions(+), 1 deletion(-) diff --git a/pallets/subtensor/src/tests/claim_root.rs b/pallets/subtensor/src/tests/claim_root.rs index eca6520422..2d7c4b93ca 100644 --- a/pallets/subtensor/src/tests/claim_root.rs +++ b/pallets/subtensor/src/tests/claim_root.rs @@ -1,7 +1,11 @@ use crate::tests::mock::{ RuntimeOrigin, SubtensorModule, Test, add_dynamic_network, new_test_ext, run_to_block, }; -use crate::{NetworksAdded, RootClaimable, SubnetAlphaIn, SubnetMechanism, SubnetTAO, SubtokenEnabled, Tempo, pallet, NumStakingColdkeys, StakingColdkeys, StakingColdkeysByIndex}; +use crate::{ + NetworksAdded, NumStakingColdkeys, PendingRootAlphaDivs, RootClaimable, StakingColdkeys, + StakingColdkeysByIndex, SubnetAlphaIn, SubnetMechanism, SubnetTAO, SubtokenEnabled, Tempo, + pallet, +}; use crate::{RootClaimType, RootClaimTypeEnum, RootClaimed}; use approx::assert_abs_diff_eq; use frame_support::assert_ok; @@ -912,3 +916,77 @@ fn test_populate_staking_maps() { assert!(!StakingColdkeys::::contains_key(&coldkey3)); }); } + +#[test] +fn test_claim_root_coinbase_distribution() { + new_test_ext(1).execute_with(|| { + let owner_coldkey = U256::from(1001); + let hotkey = U256::from(1002); + let coldkey = U256::from(1003); + let netuid = add_dynamic_network(&hotkey, &owner_coldkey); + + Tempo::::insert(netuid, 1); + SubtensorModule::set_tao_weight(u64::MAX); // Set TAO weight to 1.0 + + let root_stake = 200_000_000u64; + let initial_tao = 200_000_000u64; + SubnetTAO::::insert(NetUid::ROOT, TaoCurrency::from(initial_tao)); + + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &coldkey, + NetUid::ROOT, + root_stake.into(), + ); + + let initial_total_hotkey_alpha = 10_000_000u64; + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &owner_coldkey, + netuid, + initial_total_hotkey_alpha.into(), + ); + + let initial_alpha_issuance = SubtensorModule::get_alpha_issuance(netuid); + let alpha_emissions: AlphaCurrency = 1_000_000_000u64.into(); + + // Check total issuance (saved to pending alpha divs) + + run_to_block(2); + + let alpha_issuance = SubtensorModule::get_alpha_issuance(netuid); + assert_eq!(initial_alpha_issuance + alpha_emissions, alpha_issuance); + + let root_prop = initial_tao as f64 / (u64::from(alpha_issuance) + initial_tao) as f64; + let root_validators_share = 0.5f64; + + let expected_pending_root_alpha_divs = + u64::from(alpha_emissions) as f64 * root_prop * root_validators_share; + assert_abs_diff_eq!( + u64::from(PendingRootAlphaDivs::::get(netuid)) as f64, + expected_pending_root_alpha_divs, + epsilon = 100f64 + ); + + // Epoch pending alphas divs is distributed + + run_to_block(3); + + assert_eq!(u64::from(PendingRootAlphaDivs::::get(netuid)), 0u64); + + let claimable = *RootClaimable::::get(hotkey) + .get(&netuid) + .expect("claimable must exist at this point"); + + let validator_take_percent = 0.18f64; + let calculated_rate = (expected_pending_root_alpha_divs * 2f64) + * (1f64 - validator_take_percent) + / (root_stake as f64); + + assert_abs_diff_eq!( + claimable.saturating_to_num::(), + calculated_rate, + epsilon = 0.001f64, + ); + }); +} From 78e45afe36ee8d8b34db1270825614890903b158 Mon Sep 17 00:00:00 2001 From: Shamil Gadelshin Date: Fri, 3 Oct 2025 17:13:13 +0300 Subject: [PATCH 31/54] Refactor root claim code. --- pallets/subtensor/src/staking/claim_root.rs | 8 +++++++- pallets/subtensor/src/swap/swap_coldkey.rs | 2 +- pallets/subtensor/src/tests/claim_root.rs | 10 +++++----- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/pallets/subtensor/src/staking/claim_root.rs b/pallets/subtensor/src/staking/claim_root.rs index 5555275a37..a38e324b9d 100644 --- a/pallets/subtensor/src/staking/claim_root.rs +++ b/pallets/subtensor/src/staking/claim_root.rs @@ -130,7 +130,10 @@ impl Pallet { // Substract the root claimed. let owed: I96F32 = Self::get_root_owed_for_hotkey_coldkey_float(hotkey, coldkey, netuid); - if owed == 0 || owed < I96F32::saturating_from_num(DefaultMinRootClaimAmount::::get()) { + if owed < I96F32::saturating_from_num(DefaultMinRootClaimAmount::::get()) { + log::debug!( + "root claim on subnet {netuid} is skipped: {owed:?} for h={hotkey:?},c={coldkey:?} " + ); return; // no-op } @@ -142,6 +145,9 @@ impl Pallet { }; if owed_u64 == 0 { + log::debug!( + "root claim on subnet {netuid} is skipped: {owed:?} for h={hotkey:?},c={coldkey:?}" + ); return; // no-op } diff --git a/pallets/subtensor/src/swap/swap_coldkey.rs b/pallets/subtensor/src/swap/swap_coldkey.rs index f5901e0143..81f7ee3710 100644 --- a/pallets/subtensor/src/swap/swap_coldkey.rs +++ b/pallets/subtensor/src/swap/swap_coldkey.rs @@ -176,7 +176,7 @@ impl Pallet { // Register new coldkey with root stake if new_alpha > U64F64::from(0u64) && netuid == NetUid::ROOT { - Self::maybe_add_coldkey_index(&new_coldkey); + Self::maybe_add_coldkey_index(new_coldkey); } } // Add the weight for the read and write. diff --git a/pallets/subtensor/src/tests/claim_root.rs b/pallets/subtensor/src/tests/claim_root.rs index 2d7c4b93ca..2cc9a665d9 100644 --- a/pallets/subtensor/src/tests/claim_root.rs +++ b/pallets/subtensor/src/tests/claim_root.rs @@ -908,12 +908,12 @@ fn test_populate_staking_maps() { assert_eq!(NumStakingColdkeys::::get(), 2); - assert!(StakingColdkeysByIndex::::contains_key(&0)); - assert!(StakingColdkeysByIndex::::contains_key(&1)); + assert!(StakingColdkeysByIndex::::contains_key(0)); + assert!(StakingColdkeysByIndex::::contains_key(1)); - assert!(StakingColdkeys::::contains_key(&coldkey1)); - assert!(StakingColdkeys::::contains_key(&coldkey2)); - assert!(!StakingColdkeys::::contains_key(&coldkey3)); + assert!(StakingColdkeys::::contains_key(coldkey1)); + assert!(StakingColdkeys::::contains_key(coldkey2)); + assert!(!StakingColdkeys::::contains_key(coldkey3)); }); } From d0b2dac26fa169f7ba2b785af338e3e83aef4b74 Mon Sep 17 00:00:00 2001 From: Shamil Gadelshin Date: Fri, 3 Oct 2025 17:26:52 +0300 Subject: [PATCH 32/54] Fix merge issues --- pallets/admin-utils/src/tests/mod.rs | 1 - pallets/subtensor/src/coinbase/root.rs | 4 ++-- pallets/subtensor/src/macros/dispatches.rs | 1 - pallets/subtensor/src/staking/helpers.rs | 2 +- pallets/subtensor/src/subnets/uids.rs | 1 - pallets/subtensor/src/tests/networks.rs | 6 ++---- 6 files changed, 5 insertions(+), 10 deletions(-) diff --git a/pallets/admin-utils/src/tests/mod.rs b/pallets/admin-utils/src/tests/mod.rs index 4e534c3210..6c7e76b52d 100644 --- a/pallets/admin-utils/src/tests/mod.rs +++ b/pallets/admin-utils/src/tests/mod.rs @@ -2663,7 +2663,6 @@ fn test_trim_to_max_allowed_uids() { assert!(!AlphaDividendsPerSubnet::::contains_key( netuid, hotkey )); - assert!(!TaoDividendsPerSubnet::::contains_key(netuid, hotkey)); assert!(!Axons::::contains_key(netuid, hotkey)); assert!(!NeuronCertificates::::contains_key(netuid, hotkey)); assert!(!Prometheus::::contains_key(netuid, hotkey)); diff --git a/pallets/subtensor/src/coinbase/root.rs b/pallets/subtensor/src/coinbase/root.rs index 6b09c9ed46..5ed8c979cf 100644 --- a/pallets/subtensor/src/coinbase/root.rs +++ b/pallets/subtensor/src/coinbase/root.rs @@ -477,7 +477,7 @@ impl Pallet { FirstEmissionBlockNumber::::remove(netuid); PendingEmission::::remove(netuid); PendingRootDivs::::remove(netuid); - PendingAlphaSwapped::::remove(netuid); + PendingRootAlphaDivs::::remove(netuid); PendingOwnerCut::::remove(netuid); BlocksSinceLastStep::::remove(netuid); LastMechansimStepBlock::::remove(netuid); @@ -510,6 +510,7 @@ impl Pallet { RAORecycledForRegistration::::remove(netuid); MaxRegistrationsPerBlock::::remove(netuid); WeightsVersionKey::::remove(netuid); + PendingRootAlphaDivs::::remove(netuid); // --- 17. Subtoken / feature flags. LiquidAlphaOn::::remove(netuid); @@ -528,7 +529,6 @@ impl Pallet { let _ = NeuronCertificates::::clear_prefix(netuid, u32::MAX, None); let _ = Prometheus::::clear_prefix(netuid, u32::MAX, None); let _ = AlphaDividendsPerSubnet::::clear_prefix(netuid, u32::MAX, None); - let _ = TaoDividendsPerSubnet::::clear_prefix(netuid, u32::MAX, None); let _ = PendingChildKeys::::clear_prefix(netuid, u32::MAX, None); let _ = AssociatedEvmAddress::::clear_prefix(netuid, u32::MAX, None); diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index b7ebd0500a..a9d55300e1 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -2387,7 +2387,6 @@ mod dispatches { Self::do_dissolve_network(netuid) } - /// --- Claims the root emissions for a coldkey. /// # Args: /// * 'origin': (Origin): diff --git a/pallets/subtensor/src/staking/helpers.rs b/pallets/subtensor/src/staking/helpers.rs index d9101bc40c..ecd2251ac2 100644 --- a/pallets/subtensor/src/staking/helpers.rs +++ b/pallets/subtensor/src/staking/helpers.rs @@ -377,7 +377,7 @@ impl Pallet { AlphaMapLastKey::::put(new_starting_key); } - } + } pub fn burn_subnet_alpha(_netuid: NetUid, _amount: AlphaCurrency) { // Do nothing; TODO: record burned alpha in a tracker diff --git a/pallets/subtensor/src/subnets/uids.rs b/pallets/subtensor/src/subnets/uids.rs index 9d303cc979..669f74bccc 100644 --- a/pallets/subtensor/src/subnets/uids.rs +++ b/pallets/subtensor/src/subnets/uids.rs @@ -200,7 +200,6 @@ impl Pallet { IsNetworkMember::::remove(&hotkey, netuid); LastHotkeyEmissionOnNetuid::::remove(&hotkey, netuid); AlphaDividendsPerSubnet::::remove(netuid, &hotkey); - TaoDividendsPerSubnet::::remove(netuid, &hotkey); Axons::::remove(netuid, &hotkey); NeuronCertificates::::remove(netuid, &hotkey); Prometheus::::remove(netuid, &hotkey); diff --git a/pallets/subtensor/src/tests/networks.rs b/pallets/subtensor/src/tests/networks.rs index 42de84f54f..39b246ad8c 100644 --- a/pallets/subtensor/src/tests/networks.rs +++ b/pallets/subtensor/src/tests/networks.rs @@ -357,7 +357,7 @@ fn dissolve_clears_all_per_subnet_storages() { NetworkPowRegistrationAllowed::::insert(net, true); PendingEmission::::insert(net, AlphaCurrency::from(1)); PendingRootDivs::::insert(net, TaoCurrency::from(1)); - PendingAlphaSwapped::::insert(net, AlphaCurrency::from(1)); + PendingRootAlphaDivs::::insert(net, AlphaCurrency::from(1)); PendingOwnerCut::::insert(net, AlphaCurrency::from(1)); BlocksSinceLastStep::::insert(net, 1u64); LastMechansimStepBlock::::insert(net, 1u64); @@ -405,7 +405,6 @@ fn dissolve_clears_all_per_subnet_storages() { // Per‑subnet dividends AlphaDividendsPerSubnet::::insert(net, owner_hot, AlphaCurrency::from(1)); - TaoDividendsPerSubnet::::insert(net, owner_hot, TaoCurrency::from(1)); // Parent/child topology + takes ChildkeyTake::::insert(owner_hot, net, 1u16); @@ -515,7 +514,7 @@ fn dissolve_clears_all_per_subnet_storages() { assert!(!NetworkPowRegistrationAllowed::::contains_key(net)); assert!(!PendingEmission::::contains_key(net)); assert!(!PendingRootDivs::::contains_key(net)); - assert!(!PendingAlphaSwapped::::contains_key(net)); + assert!(!PendingRootAlphaDivs::::contains_key(net)); assert!(!PendingOwnerCut::::contains_key(net)); assert!(!BlocksSinceLastStep::::contains_key(net)); assert!(!LastMechansimStepBlock::::contains_key(net)); @@ -565,7 +564,6 @@ fn dissolve_clears_all_per_subnet_storages() { assert!(!AlphaDividendsPerSubnet::::contains_key( net, owner_hot )); - assert!(!TaoDividendsPerSubnet::::contains_key(net, owner_hot)); // Parent/child topology + takes assert!(!ChildkeyTake::::contains_key(owner_hot, net)); From 0f823563462cc75963e222338ff78c38a59ddc00 Mon Sep 17 00:00:00 2001 From: Shamil Gadelshin Date: Fri, 3 Oct 2025 18:02:56 +0300 Subject: [PATCH 33/54] Remove obsolete code --- pallets/subtensor/src/lib.rs | 18 ------------------ pallets/subtensor/src/macros/events.rs | 11 ----------- 2 files changed, 29 deletions(-) diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 0d476fba7f..411a1bbe0e 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -359,14 +359,6 @@ pub mod pallet { RootClaimTypeEnum::default() } - #[pallet::type_value] - /// Default root claim frequency. - /// This is the frequency of root claims for a coldkey. - /// This is set by the user. Either auto or manual. - pub fn DefaultRootClaimFrequency() -> RootClaimFrequencyEnum { - RootClaimFrequencyEnum::default() - } - #[pallet::type_value] /// Default number of root claims per claim call. /// Ideally this is calculated using the number of staking coldkey @@ -1929,16 +1921,6 @@ pub mod pallet { ValueQuery, DefaultRootClaimType, >; - #[pallet::storage] // -- MAP ( cold ) --> root_claim_frequency enum - pub type RootClaimFrequency = StorageMap< - _, - Blake2_128Concat, - T::AccountId, - RootClaimFrequencyEnum, - ValueQuery, - DefaultRootClaimFrequency, - >; - #[pallet::storage] // --- MAP ( u64 ) --> coldkey | Maps coldkeys that have stake to an index pub type StakingColdkeysByIndex = StorageMap<_, Identity, u64, T::AccountId, OptionQuery>; diff --git a/pallets/subtensor/src/macros/events.rs b/pallets/subtensor/src/macros/events.rs index 282105662b..442a363ae6 100644 --- a/pallets/subtensor/src/macros/events.rs +++ b/pallets/subtensor/src/macros/events.rs @@ -475,16 +475,5 @@ mod events { /// Claim type root_claim_type: RootClaimTypeEnum, }, - - /// Root claim frequency for a coldkey has been set. - /// Parameters: - /// (coldkey, u8) - RootClaimFrequencySet { - /// Claim coldkey - coldkey: T::AccountId, - - /// Claim type - root_claim_type: RootClaimTypeEnum, - }, } } From 239df822a90fcbfd6fa479aa2a02bc7eb8fe79a0 Mon Sep 17 00:00:00 2001 From: Shamil Gadelshin Date: Fri, 3 Oct 2025 18:08:53 +0300 Subject: [PATCH 34/54] Fix typo --- pallets/subtensor/src/tests/claim_root.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/subtensor/src/tests/claim_root.rs b/pallets/subtensor/src/tests/claim_root.rs index 2cc9a665d9..f455046ff1 100644 --- a/pallets/subtensor/src/tests/claim_root.rs +++ b/pallets/subtensor/src/tests/claim_root.rs @@ -780,7 +780,7 @@ fn test_claim_root_with_run_coinbase() { } #[test] -fn test_claim_root_bloch_hash_indices() { +fn test_claim_root_block_hash_indices() { new_test_ext(1).execute_with(|| { let k = 15u64; let n = 15000u64; From 69e222bd2f01a41cdb5c1b07fc81c4c83f60f15e Mon Sep 17 00:00:00 2001 From: Shamil Gadelshin Date: Fri, 3 Oct 2025 18:52:36 +0300 Subject: [PATCH 35/54] Add sudo_set_num_root_claims extrinsic --- pallets/subtensor/src/benchmarks.rs | 6 ++++ pallets/subtensor/src/lib.rs | 2 ++ pallets/subtensor/src/macros/dispatches.rs | 30 ++++++++++++++++ pallets/subtensor/src/macros/errors.rs | 2 ++ pallets/subtensor/src/tests/claim_root.rs | 42 +++++++++++++++++++--- 5 files changed, 78 insertions(+), 4 deletions(-) diff --git a/pallets/subtensor/src/benchmarks.rs b/pallets/subtensor/src/benchmarks.rs index 7d9441eaaf..900fd11e54 100644 --- a/pallets/subtensor/src/benchmarks.rs +++ b/pallets/subtensor/src/benchmarks.rs @@ -1593,4 +1593,10 @@ mod pallet_benchmarks { #[extrinsic_call] _(RawOrigin::Signed(coldkey.clone())); } + + #[benchmark] + fn sudo_set_num_root_claims() { + #[extrinsic_call] + _(RawOrigin::Root, 100); + } } diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 411a1bbe0e..186e6b713a 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -56,6 +56,8 @@ pub const MAX_CRV3_COMMIT_SIZE_BYTES: u32 = 5000; pub const ALPHA_MAP_BATCH_SIZE: usize = 30; +pub const MAX_NUM_ROOT_CLAIMS: u64 = 50; + #[allow(deprecated)] #[deny(missing_docs)] #[import_section(errors::errors)] diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index a9d55300e1..8b0b946b9e 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -12,6 +12,8 @@ mod dispatches { use sp_core::ecdsa::Signature; use sp_runtime::{Percent, traits::Saturating}; + use crate::MAX_NUM_ROOT_CLAIMS; + use crate::MAX_CRV3_COMMIT_SIZE_BYTES; /// Dispatchable functions allow users to interact with the pallet and invoke state changes. /// These functions materialize as "extrinsics", which are often compared to transactions. @@ -2439,5 +2441,33 @@ mod dispatches { Self::change_root_claim_type(&coldkey, new_root_claim_type); Ok(()) } + + /// --- Sets the root claim type for the coldkey. + /// # Args: + /// * 'origin': (Origin): + /// - The signature of the caller's coldkey. + /// + /// # Event: + /// * RootClaimTypeSet; + /// - On the successfully setting the root claim type for the coldkey. + /// + #[pallet::call_index(123)] + #[pallet::weight(( + Weight::from_parts(4_000_000, 0).saturating_add(T::DbWeight::get().writes(1_u64)), + DispatchClass::Operational, + Pays::Yes + ))] + pub fn sudo_set_num_root_claims(origin: OriginFor, new_value: u64) -> DispatchResult { + ensure_root(origin)?; + + ensure!( + new_value > 0 && new_value <= MAX_NUM_ROOT_CLAIMS, + Error::::InvalidNumRootClaim + ); + + NumRootClaim::::set(new_value); + + Ok(()) + } } } diff --git a/pallets/subtensor/src/macros/errors.rs b/pallets/subtensor/src/macros/errors.rs index e241ff068f..bb40abcc67 100644 --- a/pallets/subtensor/src/macros/errors.rs +++ b/pallets/subtensor/src/macros/errors.rs @@ -262,5 +262,7 @@ mod errors { UidMapCouldNotBeCleared, /// Trimming would exceed the max immune neurons percentage TrimmingWouldExceedMaxImmunePercentage, + /// Invalid number of root claims + InvalidNumRootClaim, } } diff --git a/pallets/subtensor/src/tests/claim_root.rs b/pallets/subtensor/src/tests/claim_root.rs index f455046ff1..98ffc146ba 100644 --- a/pallets/subtensor/src/tests/claim_root.rs +++ b/pallets/subtensor/src/tests/claim_root.rs @@ -2,14 +2,15 @@ use crate::tests::mock::{ RuntimeOrigin, SubtensorModule, Test, add_dynamic_network, new_test_ext, run_to_block, }; use crate::{ - NetworksAdded, NumStakingColdkeys, PendingRootAlphaDivs, RootClaimable, StakingColdkeys, - StakingColdkeysByIndex, SubnetAlphaIn, SubnetMechanism, SubnetTAO, SubtokenEnabled, Tempo, - pallet, + Error, MAX_NUM_ROOT_CLAIMS, NetworksAdded, NumRootClaim, NumStakingColdkeys, + PendingRootAlphaDivs, RootClaimable, StakingColdkeys, StakingColdkeysByIndex, SubnetAlphaIn, + SubnetMechanism, SubnetTAO, SubtokenEnabled, Tempo, pallet, }; use crate::{RootClaimType, RootClaimTypeEnum, RootClaimed}; use approx::assert_abs_diff_eq; -use frame_support::assert_ok; +use frame_support::{assert_noop, assert_ok}; use sp_core::{H256, U256}; +use sp_runtime::DispatchError; use substrate_fixed::types::{I96F32, U96F32}; use subtensor_runtime_common::{AlphaCurrency, Currency, NetUid, TaoCurrency}; use subtensor_swap_interface::SwapHandler; @@ -990,3 +991,36 @@ fn test_claim_root_coinbase_distribution() { ); }); } + +#[test] +fn test_sudo_set_num_root_claims() { + new_test_ext(1).execute_with(|| { + let coldkey = U256::from(1003); + + assert_noop!( + SubtensorModule::sudo_set_num_root_claims(RuntimeOrigin::signed(coldkey), 50u64), + DispatchError::BadOrigin + ); + + assert_noop!( + SubtensorModule::sudo_set_num_root_claims(RuntimeOrigin::root(), 0u64), + Error::::InvalidNumRootClaim + ); + + assert_noop!( + SubtensorModule::sudo_set_num_root_claims( + RuntimeOrigin::root(), + MAX_NUM_ROOT_CLAIMS + 1, + ), + Error::::InvalidNumRootClaim + ); + + let new_value = 27u64; + assert_ok!(SubtensorModule::sudo_set_num_root_claims( + RuntimeOrigin::root(), + new_value, + ),); + + assert_eq!(NumRootClaim::::get(), new_value); + }); +} From c5d9f31337c0f9a47d54a26cea8c232d23cb9241 Mon Sep 17 00:00:00 2001 From: Shamil Gadelshin Date: Mon, 6 Oct 2025 18:27:13 +0300 Subject: [PATCH 36/54] Update claim_root benchmark. --- pallets/subtensor/src/benchmarks.rs | 59 +++++++++++++++++++++- pallets/subtensor/src/macros/dispatches.rs | 4 +- 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/pallets/subtensor/src/benchmarks.rs b/pallets/subtensor/src/benchmarks.rs index 900fd11e54..d7b9e63abc 100644 --- a/pallets/subtensor/src/benchmarks.rs +++ b/pallets/subtensor/src/benchmarks.rs @@ -1585,13 +1585,70 @@ mod pallet_benchmarks { _(RawOrigin::Signed(coldkey.clone()), RootClaimTypeEnum::Keep); } - // TODO: rework after subnets argument #[benchmark] fn claim_root() { let coldkey: T::AccountId = whitelisted_caller(); + let hotkey: T::AccountId = account("A", 0, 1); + + let netuid = Subtensor::::get_next_netuid(); + + let lock_cost = Subtensor::::get_network_lock_cost(); + Subtensor::::add_balance_to_coldkey_account(&coldkey, lock_cost.into()); + + assert_ok!(Subtensor::::register_network( + RawOrigin::Signed(coldkey.clone()).into(), + hotkey.clone() + )); + + SubtokenEnabled::::insert(netuid, true); + Subtensor::::set_network_pow_registration_allowed(netuid, true); + NetworkRegistrationAllowed::::insert(netuid, true); + FirstEmissionBlockNumber::::insert(netuid, 0); + + SubnetMechanism::::insert(netuid, 1); + SubnetworkN::::insert(netuid, 1); + Subtensor::::set_tao_weight(u64::MAX); // Set TAO weight to 1.0 + + let root_stake = 100_000_000u64; + Subtensor::::increase_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &coldkey, + NetUid::ROOT, + root_stake.into(), + ); + + let initial_total_hotkey_alpha = 100_000_000u64; + Subtensor::::increase_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &coldkey, + netuid, + initial_total_hotkey_alpha.into(), + ); + + let pending_root_alpha = 10_000_000u64; + Subtensor::::drain_pending_emission( + netuid, + AlphaCurrency::ZERO, + pending_root_alpha.into(), + AlphaCurrency::ZERO, + ); + + let initial_stake = + Subtensor::::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid); + + assert_ok!(Subtensor::::set_root_claim_type( + RawOrigin::Signed(coldkey.clone()).into(), + RootClaimTypeEnum::Keep + ),); #[extrinsic_call] _(RawOrigin::Signed(coldkey.clone())); + + // Verification + let new_stake = + Subtensor::::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid); + + assert!(new_stake > initial_stake); } #[benchmark] diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index 8b0b946b9e..f5c0802fa9 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -2402,7 +2402,9 @@ mod dispatches { /// #[pallet::call_index(121)] #[pallet::weight(( - Weight::from_parts(13_000_000, 3994).saturating_add(T::DbWeight::get().reads(1_u64)), + Weight::from_parts(117_000_000, 7767) + .saturating_add(T::DbWeight::get().reads(11_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)), DispatchClass::Normal, Pays::Yes ))] From 99345b1432da317ecf7bd3ac1742aeb61da86360 Mon Sep 17 00:00:00 2001 From: Shamil Gadelshin Date: Fri, 10 Oct 2025 15:19:49 +0300 Subject: [PATCH 37/54] Refactor code --- pallets/subtensor/src/staking/helpers.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pallets/subtensor/src/staking/helpers.rs b/pallets/subtensor/src/staking/helpers.rs index ecd2251ac2..f568178877 100644 --- a/pallets/subtensor/src/staking/helpers.rs +++ b/pallets/subtensor/src/staking/helpers.rs @@ -334,14 +334,14 @@ impl Pallet { /// The function uses AlphaMapCleanLastKey as a storage for key iterator between runs. pub fn populate_root_coldkey_staking_maps() { // Get starting key for the batch. Get the first key if we restart the process. - let mut starting_raw_key = AlphaMapLastKey::::get(); + let mut new_starting_raw_key = AlphaMapLastKey::::get(); let mut starting_key = None; - if starting_raw_key.is_none() { + if new_starting_raw_key.is_none() { starting_key = Alpha::::iter_keys().next(); - starting_raw_key = starting_key.as_ref().map(Alpha::::hashed_key_for); + new_starting_raw_key = starting_key.as_ref().map(Alpha::::hashed_key_for); } - if let Some(starting_raw_key) = starting_raw_key { + if let Some(starting_raw_key) = new_starting_raw_key { // Get the key batch let mut keys = Alpha::::iter_keys_from(starting_raw_key) .take(ALPHA_MAP_BATCH_SIZE) From cc324ec9e605a7e19a27c3f1a0d06b9b892067cd Mon Sep 17 00:00:00 2001 From: Shamil Gadelshin Date: Fri, 10 Oct 2025 15:19:58 +0300 Subject: [PATCH 38/54] Add migration --- pallets/subtensor/src/macros/hooks.rs | 4 +- .../migrate_remove_tao_dividends.rs | 60 +++++++++++++++++++ pallets/subtensor/src/migrations/mod.rs | 1 + pallets/subtensor/src/tests/migration.rs | 26 ++++++++ 4 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 pallets/subtensor/src/migrations/migrate_remove_tao_dividends.rs diff --git a/pallets/subtensor/src/macros/hooks.rs b/pallets/subtensor/src/macros/hooks.rs index a3cb7a692f..1ff7924e4a 100644 --- a/pallets/subtensor/src/macros/hooks.rs +++ b/pallets/subtensor/src/macros/hooks.rs @@ -147,7 +147,9 @@ mod hooks { // Migrate Lock Reduction Interval .saturating_add(migrations::migrate_network_lock_reduction_interval::migrate_network_lock_reduction_interval::()) // Migrate subnet locked balances - .saturating_add(migrations::migrate_subnet_locked::migrate_restore_subnet_locked::()); + .saturating_add(migrations::migrate_subnet_locked::migrate_restore_subnet_locked::()) + // Remove obsolete map entries + .saturating_add(migrations::migrate_remove_tao_dividends::migrate_remove_tao_dividends::()); weight } diff --git a/pallets/subtensor/src/migrations/migrate_remove_tao_dividends.rs b/pallets/subtensor/src/migrations/migrate_remove_tao_dividends.rs new file mode 100644 index 0000000000..c556576da7 --- /dev/null +++ b/pallets/subtensor/src/migrations/migrate_remove_tao_dividends.rs @@ -0,0 +1,60 @@ +use crate::{Config, HasMigrationRun}; +use alloc::string::String; +use frame_support::pallet_prelude::Weight; +use frame_support::traits::Get; +use sp_io::KillStorageResult; +use sp_io::hashing::twox_128; +use sp_io::storage::clear_prefix; +use sp_std::vec::Vec; +fn remove_prefix(old_map: &str) -> Weight { + let mut prefix = Vec::new(); + prefix.extend_from_slice(&twox_128("SubtensorModule".as_bytes())); + prefix.extend_from_slice(&twox_128(old_map.as_bytes())); + + let removal_results = clear_prefix(&prefix, Some(u32::MAX)); + + let removed_entries_count = match removal_results { + KillStorageResult::AllRemoved(removed) => removed as u64, + KillStorageResult::SomeRemaining(removed) => { + log::info!("Failed To Remove Some Items During migration"); + removed as u64 + } + }; + + log::info!("Removed {removed_entries_count:?} entries from {old_map:?} map."); + + T::DbWeight::get().writes(removed_entries_count) +} + +pub fn migrate_remove_tao_dividends() -> Weight { + let migration_name = b"migrate_remove_tao_dividends".to_vec(); + let mut weight = T::DbWeight::get().reads(1); + + if HasMigrationRun::::get(&migration_name) { + log::info!( + "Migration '{:?}' has already run. Skipping.", + String::from_utf8_lossy(&migration_name) + ); + return weight; + } + + log::info!( + "Running migration '{}'", + String::from_utf8_lossy(&migration_name) + ); + + // Remove obsolete map entries + let weight1 = remove_prefix::("TaoDividendsPerSubnet"); + let weight2 = remove_prefix::("PendingAlphaSwapped"); + + // Mark Migration as Completed + HasMigrationRun::::insert(&migration_name, true); + weight = weight.saturating_add(T::DbWeight::get().writes(1)); + + log::info!( + "Migration '{:?}' completed successfully.", + String::from_utf8_lossy(&migration_name) + ); + + weight.saturating_add(weight1).saturating_add(weight2) +} diff --git a/pallets/subtensor/src/migrations/mod.rs b/pallets/subtensor/src/migrations/mod.rs index ef2df8bdec..524fba2488 100644 --- a/pallets/subtensor/src/migrations/mod.rs +++ b/pallets/subtensor/src/migrations/mod.rs @@ -29,6 +29,7 @@ pub mod migrate_rate_limiting_last_blocks; pub mod migrate_remove_commitments_rate_limit; pub mod migrate_remove_network_modality; pub mod migrate_remove_stake_map; +pub mod migrate_remove_tao_dividends; pub mod migrate_remove_total_hotkey_coldkey_stakes_this_interval; pub mod migrate_remove_unused_maps_and_values; pub mod migrate_remove_zero_total_hotkey_alpha; diff --git a/pallets/subtensor/src/tests/migration.rs b/pallets/subtensor/src/tests/migration.rs index b37c25f04c..2e0840e1f7 100644 --- a/pallets/subtensor/src/tests/migration.rs +++ b/pallets/subtensor/src/tests/migration.rs @@ -2044,3 +2044,29 @@ fn test_migrate_restore_subnet_locked_65_128() { ); }); } + +#[test] +fn test_migrate_remove_tao_dividends() { + const MIGRATION_NAME: &str = "migrate_remove_tao_dividends"; + let pallet_name = "SubtensorModule"; + let storage_name = "TaoDividendsPerSubnet"; + let migration = + crate::migrations::migrate_remove_tao_dividends::migrate_remove_tao_dividends::; + + test_remove_storage_item( + MIGRATION_NAME, + pallet_name, + storage_name, + migration, + 200_000, + ); + + let storage_name = "PendingAlphaSwapped"; + test_remove_storage_item( + MIGRATION_NAME, + pallet_name, + storage_name, + migration, + 200_000, + ); +} From 1f1c7e73cf9a8591492956c13f10b4abe8360ca5 Mon Sep 17 00:00:00 2001 From: Shamil Gadelshin Date: Tue, 14 Oct 2025 12:45:48 +0300 Subject: [PATCH 39/54] Fix merge conflicts --- pallets/subtensor/src/staking/claim_root.rs | 4 ++-- pallets/subtensor/src/tests/claim_root.rs | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/pallets/subtensor/src/staking/claim_root.rs b/pallets/subtensor/src/staking/claim_root.rs index a38e324b9d..0df66762bc 100644 --- a/pallets/subtensor/src/staking/claim_root.rs +++ b/pallets/subtensor/src/staking/claim_root.rs @@ -158,7 +158,7 @@ impl Pallet { let owed_tao = match Self::swap_alpha_for_tao( netuid, owed_u64.into(), - T::SwapInterface::min_price().into(), + T::SwapInterface::min_price::(), false, ) { Ok(owed_tao) => owed_tao, @@ -173,7 +173,7 @@ impl Pallet { hotkey, coldkey, NetUid::ROOT, - owed_tao.amount_paid_out.into(), + owed_tao.amount_paid_out.to_u64().into(), ); Self::add_stake_adjust_root_claimed_for_hotkey_and_coldkey( diff --git a/pallets/subtensor/src/tests/claim_root.rs b/pallets/subtensor/src/tests/claim_root.rs index 98ffc146ba..4d5fe3352c 100644 --- a/pallets/subtensor/src/tests/claim_root.rs +++ b/pallets/subtensor/src/tests/claim_root.rs @@ -1,3 +1,5 @@ +#![allow(clippy::expect_used)] + use crate::tests::mock::{ RuntimeOrigin, SubtensorModule, Test, add_dynamic_network, new_test_ext, run_to_block, }; From 3d3ef0dc98f86b22d48c555a1b996d8b04985cf0 Mon Sep 17 00:00:00 2001 From: shamil-gadelshin Date: Tue, 14 Oct 2025 16:38:11 +0400 Subject: [PATCH 40/54] Update pallets/subtensor/src/staking/claim_root.rs Co-authored-by: Cameron Fairchild --- pallets/subtensor/src/staking/claim_root.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/subtensor/src/staking/claim_root.rs b/pallets/subtensor/src/staking/claim_root.rs index 0df66762bc..3a5d0c9e02 100644 --- a/pallets/subtensor/src/staking/claim_root.rs +++ b/pallets/subtensor/src/staking/claim_root.rs @@ -61,7 +61,7 @@ impl Pallet { RootClaimable::::mutate(hotkey, |claimable| { claimable .entry(netuid) - .and_modify(|total| *total = total.saturating_add(increment)) + .and_modify(|claim_total| *claim_total = claim_total.saturating_add(increment)) .or_insert(increment); }); } From ab5a274af81fe54d0a47f6d96930c4dece501727 Mon Sep 17 00:00:00 2001 From: Shamil Gadelshin Date: Tue, 14 Oct 2025 18:16:10 +0300 Subject: [PATCH 41/54] Handle case for stake mismatch. --- pallets/subtensor/src/staking/claim_root.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pallets/subtensor/src/staking/claim_root.rs b/pallets/subtensor/src/staking/claim_root.rs index 3a5d0c9e02..e44ae0d00e 100644 --- a/pallets/subtensor/src/staking/claim_root.rs +++ b/pallets/subtensor/src/staking/claim_root.rs @@ -51,9 +51,12 @@ impl Pallet { .checked_div(total) .unwrap_or(I96F32::saturating_from_num(0.0)); + // Unlikely to happen. This is mostly for test environment sanity checks. if u64::from(amount) > total.saturating_to_num::() { - log::error!("Not enough root stake. NetUID = {netuid}"); + log::warn!("Not enough root stake. NetUID = {netuid}"); + let owner = Owner::::get(hotkey); + Self::increase_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &owner, netuid, amount); return; } From db3a0f091db5f85b5e0b6357a1f0a94b6db0fbcf Mon Sep 17 00:00:00 2001 From: Shamil Gadelshin Date: Wed, 15 Oct 2025 16:05:09 +0300 Subject: [PATCH 42/54] Add proxy type. --- common/src/lib.rs | 1 + pallets/subtensor/src/staking/claim_root.rs | 2 +- runtime/src/lib.rs | 4 ++++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/common/src/lib.rs b/common/src/lib.rs index 590eb7fee3..7fe0624891 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -159,6 +159,7 @@ pub enum ProxyType { SudoUncheckedSetCode, SwapHotkey, SubnetLeaseBeneficiary, // Used to operate the leased subnet + RootClaim, } impl Default for ProxyType { diff --git a/pallets/subtensor/src/staking/claim_root.rs b/pallets/subtensor/src/staking/claim_root.rs index e44ae0d00e..a400ca7fb9 100644 --- a/pallets/subtensor/src/staking/claim_root.rs +++ b/pallets/subtensor/src/staking/claim_root.rs @@ -56,7 +56,7 @@ impl Pallet { log::warn!("Not enough root stake. NetUID = {netuid}"); let owner = Owner::::get(hotkey); - Self::increase_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &owner, netuid, amount); + Self::increase_stake_for_hotkey_and_coldkey_on_subnet(hotkey, &owner, netuid, amount); return; } diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 5654def01c..6d4b8b1f7d 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -881,6 +881,10 @@ impl InstanceFilter for ProxyType { pallet_admin_utils::Call::sudo_set_toggle_transfer { .. } ) ), + ProxyType::RootClaim => matches!( + c, + RuntimeCall::SubtensorModule(pallet_subtensor::Call::claim_root { .. }) + ), } } fn is_superset(&self, o: &Self) -> bool { From c213dd0146ad93535a472901c2561c5957c333bb Mon Sep 17 00:00:00 2001 From: Shamil Gadelshin Date: Thu, 16 Oct 2025 17:41:42 +0300 Subject: [PATCH 43/54] Add swap_coldkey support for root claim --- pallets/subtensor/src/staking/claim_root.rs | 31 ++++++++ pallets/subtensor/src/swap/swap_coldkey.rs | 15 +++- pallets/subtensor/src/tests/claim_root.rs | 86 +++++++++++++++++++++ 3 files changed, 129 insertions(+), 3 deletions(-) diff --git a/pallets/subtensor/src/staking/claim_root.rs b/pallets/subtensor/src/staking/claim_root.rs index a400ca7fb9..4e2b28aed7 100644 --- a/pallets/subtensor/src/staking/claim_root.rs +++ b/pallets/subtensor/src/staking/claim_root.rs @@ -335,4 +335,35 @@ impl Pallet { root_claim_type: new_type, }); } + + pub fn transfer_root_claimed_for_new_coldkey( + netuid: NetUid, + hotkey: &T::AccountId, + old_coldkey: &T::AccountId, + new_coldkey: &T::AccountId, + ) { + let old_root_claimed = RootClaimed::::get((hotkey, old_coldkey, netuid)); + RootClaimed::::remove((hotkey, old_coldkey, netuid)); + + RootClaimed::::mutate((hotkey, new_coldkey, netuid), |new_root_claimed| { + *new_root_claimed = old_root_claimed.saturating_add(*new_root_claimed); + }); + } + pub fn transfer_root_claimable_for_new_hotkey( + old_hotkey: &T::AccountId, + new_hotkey: &T::AccountId, + ) { + let src_root_claimable = RootClaimable::::get(old_hotkey); + let mut dst_root_claimable = RootClaimable::::get(new_hotkey); + RootClaimable::::remove(old_hotkey); + + for (netuid, claimable_rate) in src_root_claimable.into_iter() { + dst_root_claimable + .entry(netuid) + .and_modify(|total| *total = total.saturating_add(claimable_rate)) + .or_insert(claimable_rate); + } + + RootClaimable::::insert(new_hotkey, dst_root_claimable); + } } diff --git a/pallets/subtensor/src/swap/swap_coldkey.rs b/pallets/subtensor/src/swap/swap_coldkey.rs index 0d186c8dbf..4d848fbcfc 100644 --- a/pallets/subtensor/src/swap/swap_coldkey.rs +++ b/pallets/subtensor/src/swap/swap_coldkey.rs @@ -189,9 +189,18 @@ impl Pallet { // Remove the value from the old account. Alpha::::remove((&hotkey, old_coldkey, netuid)); - // Register new coldkey with root stake - if new_alpha > U64F64::from(0u64) && netuid == NetUid::ROOT { - Self::maybe_add_coldkey_index(new_coldkey); + if new_alpha.saturating_add(old_alpha) > U64F64::from(0u64) { + Self::transfer_root_claimed_for_new_coldkey( + netuid, + &hotkey, + old_coldkey, + new_coldkey, + ); + + if netuid == NetUid::ROOT { + // Register new coldkey with root stake + Self::maybe_add_coldkey_index(new_coldkey); + } } } // Add the weight for the read and write. diff --git a/pallets/subtensor/src/tests/claim_root.rs b/pallets/subtensor/src/tests/claim_root.rs index 4d5fe3352c..a11b032853 100644 --- a/pallets/subtensor/src/tests/claim_root.rs +++ b/pallets/subtensor/src/tests/claim_root.rs @@ -10,6 +10,7 @@ use crate::{ }; use crate::{RootClaimType, RootClaimTypeEnum, RootClaimed}; use approx::assert_abs_diff_eq; +use frame_support::pallet_prelude::Weight; use frame_support::{assert_noop, assert_ok}; use sp_core::{H256, U256}; use sp_runtime::DispatchError; @@ -1026,3 +1027,88 @@ fn test_sudo_set_num_root_claims() { assert_eq!(NumRootClaim::::get(), new_value); }); } + +#[test] +fn test_claim_root_with_swap_coldkey() { + new_test_ext(1).execute_with(|| { + let owner_coldkey = U256::from(1001); + let hotkey = U256::from(1002); + let coldkey = U256::from(1003); + let netuid = add_dynamic_network(&hotkey, &owner_coldkey); + + SubtensorModule::set_tao_weight(u64::MAX); // Set TAO weight to 1.0 + + let root_stake = 2_000_000u64; + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &coldkey, + NetUid::ROOT, + root_stake.into(), + ); + + let initial_total_hotkey_alpha = 10_000_000u64; + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &owner_coldkey, + netuid, + initial_total_hotkey_alpha.into(), + ); + + let old_validator_stake = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &owner_coldkey, + netuid, + ); + assert_eq!(old_validator_stake, initial_total_hotkey_alpha.into()); + + // Distribute pending root alpha + + let pending_root_alpha = 1_000_000u64; + SubtensorModule::drain_pending_emission( + netuid, + AlphaCurrency::ZERO, + pending_root_alpha.into(), + AlphaCurrency::ZERO, + ); + + // Claim root alpha + + assert_ok!(SubtensorModule::set_root_claim_type( + RuntimeOrigin::signed(coldkey), + RootClaimTypeEnum::Keep + ),); + assert_eq!(RootClaimType::::get(coldkey), RootClaimTypeEnum::Keep); + + assert_ok!(SubtensorModule::claim_root(RuntimeOrigin::signed(coldkey),)); + + let new_stake: u64 = + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid) + .into(); + + // Check root claimed value saved + let new_coldkey = U256::from(10030); + + let claimed = RootClaimed::::get((&hotkey, &coldkey, netuid)); + assert_eq!(u128::from(new_stake), claimed); + + let claimed = RootClaimed::::get((&hotkey, &new_coldkey, netuid)); + assert_eq!(0u128, claimed); + + // Swap coldkey + let mut weight = Weight::zero(); + + assert_ok!(SubtensorModule::perform_swap_coldkey( + &coldkey, + &new_coldkey, + &mut weight + )); + + // Check swapped keys claimed values + + let claimed = RootClaimed::::get((&hotkey, &coldkey, netuid)); + assert_eq!(0u128, claimed); + + let claimed = RootClaimed::::get((&hotkey, &new_coldkey, netuid)); + assert_eq!(u128::from(new_stake), claimed); + }); +} From d6203c0ac432e0c4c6f67079407dcf42a0df7a78 Mon Sep 17 00:00:00 2001 From: Shamil Gadelshin Date: Thu, 16 Oct 2025 18:36:06 +0300 Subject: [PATCH 44/54] Add swap_hotkey support for root claim --- pallets/subtensor/src/staking/claim_root.rs | 11 +- pallets/subtensor/src/swap/swap_coldkey.rs | 3 +- pallets/subtensor/src/swap/swap_hotkey.rs | 10 +- pallets/subtensor/src/tests/claim_root.rs | 120 ++++++++++++++++++-- 4 files changed, 128 insertions(+), 16 deletions(-) diff --git a/pallets/subtensor/src/staking/claim_root.rs b/pallets/subtensor/src/staking/claim_root.rs index 4e2b28aed7..baa1d460b6 100644 --- a/pallets/subtensor/src/staking/claim_root.rs +++ b/pallets/subtensor/src/staking/claim_root.rs @@ -336,16 +336,17 @@ impl Pallet { }); } - pub fn transfer_root_claimed_for_new_coldkey( + pub fn transfer_root_claimed_for_new_keys( netuid: NetUid, - hotkey: &T::AccountId, + old_hotkey: &T::AccountId, + new_hotkey: &T::AccountId, old_coldkey: &T::AccountId, new_coldkey: &T::AccountId, ) { - let old_root_claimed = RootClaimed::::get((hotkey, old_coldkey, netuid)); - RootClaimed::::remove((hotkey, old_coldkey, netuid)); + let old_root_claimed = RootClaimed::::get((old_hotkey, old_coldkey, netuid)); + RootClaimed::::remove((old_hotkey, old_coldkey, netuid)); - RootClaimed::::mutate((hotkey, new_coldkey, netuid), |new_root_claimed| { + RootClaimed::::mutate((new_hotkey, new_coldkey, netuid), |new_root_claimed| { *new_root_claimed = old_root_claimed.saturating_add(*new_root_claimed); }); } diff --git a/pallets/subtensor/src/swap/swap_coldkey.rs b/pallets/subtensor/src/swap/swap_coldkey.rs index 4d848fbcfc..c81138b58c 100644 --- a/pallets/subtensor/src/swap/swap_coldkey.rs +++ b/pallets/subtensor/src/swap/swap_coldkey.rs @@ -190,9 +190,10 @@ impl Pallet { Alpha::::remove((&hotkey, old_coldkey, netuid)); if new_alpha.saturating_add(old_alpha) > U64F64::from(0u64) { - Self::transfer_root_claimed_for_new_coldkey( + Self::transfer_root_claimed_for_new_keys( netuid, &hotkey, + &hotkey, old_coldkey, new_coldkey, ); diff --git a/pallets/subtensor/src/swap/swap_hotkey.rs b/pallets/subtensor/src/swap/swap_hotkey.rs index 481740dd94..970fe8abb1 100644 --- a/pallets/subtensor/src/swap/swap_hotkey.rs +++ b/pallets/subtensor/src/swap/swap_hotkey.rs @@ -525,9 +525,17 @@ impl Pallet { weight.saturating_accrue(T::DbWeight::get().reads(old_alpha_values.len() as u64)); weight.saturating_accrue(T::DbWeight::get().writes(old_alpha_values.len() as u64)); - // Insert the new alpha values. + // 9.1. Transfer root claimable + + Self::transfer_root_claimable_for_new_hotkey(old_hotkey, new_hotkey); + + // 9.2. Insert the new alpha values. for ((coldkey, netuid_alpha), alpha) in old_alpha_values { if netuid == netuid_alpha { + Self::transfer_root_claimed_for_new_keys( + netuid, old_hotkey, new_hotkey, &coldkey, &coldkey, + ); + let new_alpha = Alpha::::take((new_hotkey, &coldkey, netuid)); Alpha::::remove((old_hotkey, &coldkey, netuid)); Alpha::::insert( diff --git a/pallets/subtensor/src/tests/claim_root.rs b/pallets/subtensor/src/tests/claim_root.rs index a11b032853..8290019ec7 100644 --- a/pallets/subtensor/src/tests/claim_root.rs +++ b/pallets/subtensor/src/tests/claim_root.rs @@ -1088,11 +1088,14 @@ fn test_claim_root_with_swap_coldkey() { // Check root claimed value saved let new_coldkey = U256::from(10030); - let claimed = RootClaimed::::get((&hotkey, &coldkey, netuid)); - assert_eq!(u128::from(new_stake), claimed); - - let claimed = RootClaimed::::get((&hotkey, &new_coldkey, netuid)); - assert_eq!(0u128, claimed); + assert_eq!( + u128::from(new_stake), + RootClaimed::::get((&hotkey, &coldkey, netuid)) + ); + assert_eq!( + 0u128, + RootClaimed::::get((&hotkey, &new_coldkey, netuid)) + ); // Swap coldkey let mut weight = Weight::zero(); @@ -1105,10 +1108,109 @@ fn test_claim_root_with_swap_coldkey() { // Check swapped keys claimed values - let claimed = RootClaimed::::get((&hotkey, &coldkey, netuid)); - assert_eq!(0u128, claimed); + assert_eq!(0u128, RootClaimed::::get((&hotkey, &coldkey, netuid))); + assert_eq!( + u128::from(new_stake), + RootClaimed::::get((&hotkey, &new_coldkey, netuid)) + ); + }); +} +#[test] +fn test_claim_root_with_swap_hotkey() { + new_test_ext(1).execute_with(|| { + let owner_coldkey = U256::from(1001); + let hotkey = U256::from(1002); + let coldkey = U256::from(1003); + let netuid = add_dynamic_network(&hotkey, &owner_coldkey); - let claimed = RootClaimed::::get((&hotkey, &new_coldkey, netuid)); - assert_eq!(u128::from(new_stake), claimed); + SubtensorModule::set_tao_weight(u64::MAX); // Set TAO weight to 1.0 + + let root_stake = 2_000_000u64; + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &coldkey, + NetUid::ROOT, + root_stake.into(), + ); + + let initial_total_hotkey_alpha = 10_000_000u64; + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &owner_coldkey, + netuid, + initial_total_hotkey_alpha.into(), + ); + + let old_validator_stake = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &owner_coldkey, + netuid, + ); + assert_eq!(old_validator_stake, initial_total_hotkey_alpha.into()); + + // Distribute pending root alpha + + let pending_root_alpha = 1_000_000u64; + SubtensorModule::drain_pending_emission( + netuid, + AlphaCurrency::ZERO, + pending_root_alpha.into(), + AlphaCurrency::ZERO, + ); + + // Claim root alpha + + assert_ok!(SubtensorModule::set_root_claim_type( + RuntimeOrigin::signed(coldkey), + RootClaimTypeEnum::Keep + ),); + assert_eq!(RootClaimType::::get(coldkey), RootClaimTypeEnum::Keep); + + assert_ok!(SubtensorModule::claim_root(RuntimeOrigin::signed(coldkey),)); + + let new_stake: u64 = + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid) + .into(); + + // Check root claimed value saved + let new_hotkey = U256::from(10030); + + assert_eq!( + u128::from(new_stake), + RootClaimed::::get((&hotkey, &coldkey, netuid)) + ); + assert_eq!( + 0u128, + RootClaimed::::get((&new_hotkey, &coldkey, netuid)) + ); + + let _old_claimable = *RootClaimable::::get(hotkey) + .get(&netuid) + .expect("claimable must exist at this point"); + + assert!(!RootClaimable::::get(new_hotkey).contains_key(&netuid)); + + // Swap hotkey + let mut weight = Weight::zero(); + assert_ok!(SubtensorModule::perform_hotkey_swap_on_one_subnet( + &hotkey, + &new_hotkey, + &mut weight, + netuid + )); + + // Check swapped keys claimed values + + assert_eq!(0u128, RootClaimed::::get((&hotkey, &coldkey, netuid))); + assert_eq!( + u128::from(new_stake), + RootClaimed::::get((&new_hotkey, &coldkey, netuid)) + ); + + assert!(!RootClaimable::::get(hotkey).contains_key(&netuid)); + + let _new_claimable = *RootClaimable::::get(new_hotkey) + .get(&netuid) + .expect("claimable must exist at this point"); }); } From 7a2ebae00c88b37aa67b11967e140f5f45875673 Mon Sep 17 00:00:00 2001 From: Shamil Gadelshin Date: Fri, 17 Oct 2025 12:50:43 +0300 Subject: [PATCH 45/54] Remove PendingRootDivs map --- pallets/subtensor/src/coinbase/root.rs | 1 - pallets/subtensor/src/lib.rs | 5 ----- .../src/migrations/migrate_remove_tao_dividends.rs | 6 +++++- pallets/subtensor/src/rpc_info/dynamic_info.rs | 2 +- pallets/subtensor/src/rpc_info/metagraph.rs | 4 ++-- pallets/subtensor/src/tests/migration.rs | 9 +++++++++ pallets/subtensor/src/tests/networks.rs | 2 -- 7 files changed, 17 insertions(+), 12 deletions(-) diff --git a/pallets/subtensor/src/coinbase/root.rs b/pallets/subtensor/src/coinbase/root.rs index 5ed8c979cf..05856b79bc 100644 --- a/pallets/subtensor/src/coinbase/root.rs +++ b/pallets/subtensor/src/coinbase/root.rs @@ -476,7 +476,6 @@ impl Pallet { // --- 15. Mechanism step / emissions bookkeeping. FirstEmissionBlockNumber::::remove(netuid); PendingEmission::::remove(netuid); - PendingRootDivs::::remove(netuid); PendingRootAlphaDivs::::remove(netuid); PendingOwnerCut::::remove(netuid); BlocksSinceLastStep::::remove(netuid); diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 18dbdd3957..07d0c50ad8 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -1401,11 +1401,6 @@ pub mod pallet { /// --- MAP ( netuid ) --> pending_emission pub type PendingEmission = StorageMap<_, Identity, NetUid, AlphaCurrency, ValueQuery, DefaultPendingEmission>; - #[pallet::storage] - /// --- MAP ( netuid ) --> pending_root_emission - pub type PendingRootDivs = - StorageMap<_, Identity, NetUid, TaoCurrency, ValueQuery, DefaultZeroTao>; - /// --- MAP ( netuid ) --> pending_root_emission #[pallet::storage] pub type PendingRootAlphaDivs = diff --git a/pallets/subtensor/src/migrations/migrate_remove_tao_dividends.rs b/pallets/subtensor/src/migrations/migrate_remove_tao_dividends.rs index c556576da7..b93df22339 100644 --- a/pallets/subtensor/src/migrations/migrate_remove_tao_dividends.rs +++ b/pallets/subtensor/src/migrations/migrate_remove_tao_dividends.rs @@ -46,6 +46,7 @@ pub fn migrate_remove_tao_dividends() -> Weight { // Remove obsolete map entries let weight1 = remove_prefix::("TaoDividendsPerSubnet"); let weight2 = remove_prefix::("PendingAlphaSwapped"); + let weight3 = remove_prefix::("PendingRootDivs"); // Mark Migration as Completed HasMigrationRun::::insert(&migration_name, true); @@ -56,5 +57,8 @@ pub fn migrate_remove_tao_dividends() -> Weight { String::from_utf8_lossy(&migration_name) ); - weight.saturating_add(weight1).saturating_add(weight2) + weight + .saturating_add(weight1) + .saturating_add(weight2) + .saturating_add(weight3) } diff --git a/pallets/subtensor/src/rpc_info/dynamic_info.rs b/pallets/subtensor/src/rpc_info/dynamic_info.rs index 28b3990082..3bfbda8676 100644 --- a/pallets/subtensor/src/rpc_info/dynamic_info.rs +++ b/pallets/subtensor/src/rpc_info/dynamic_info.rs @@ -63,7 +63,7 @@ impl Pallet { alpha_in_emission: SubnetAlphaInEmission::::get(netuid).into(), tao_in_emission: SubnetTaoInEmission::::get(netuid).into(), pending_alpha_emission: PendingEmission::::get(netuid).into(), - pending_root_emission: PendingRootDivs::::get(netuid).into(), + pending_root_emission: TaoCurrency::from(0u64).into(), subnet_volume: SubnetVolume::::get(netuid).into(), network_registered_at: NetworkRegisteredAt::::get(netuid).into(), subnet_identity: SubnetIdentitiesV3::::get(netuid), diff --git a/pallets/subtensor/src/rpc_info/metagraph.rs b/pallets/subtensor/src/rpc_info/metagraph.rs index c0c3d95202..df0c8023b0 100644 --- a/pallets/subtensor/src/rpc_info/metagraph.rs +++ b/pallets/subtensor/src/rpc_info/metagraph.rs @@ -695,7 +695,7 @@ impl Pallet { alpha_in_emission: SubnetAlphaInEmission::::get(netuid).into(), // amount injected outstanding per block tao_in_emission: SubnetTaoInEmission::::get(netuid).into(), // amount of tao injected per block pending_alpha_emission: PendingEmission::::get(netuid).into(), // pending alpha to be distributed - pending_root_emission: PendingRootDivs::::get(netuid).into(), // panding tao for root divs to be distributed + pending_root_emission: TaoCurrency::from(0u64).into(), // panding tao for root divs to be distributed subnet_volume: subnet_volume.into(), moving_price: SubnetMovingPrice::::get(netuid), @@ -1005,7 +1005,7 @@ impl Pallet { }, Some(SelectiveMetagraphIndex::PendingRootEmission) => SelectiveMetagraph { netuid: netuid.into(), - pending_root_emission: Some(PendingRootDivs::::get(netuid).into()), + pending_root_emission: Some(TaoCurrency::from(0u64).into()), ..Default::default() }, Some(SelectiveMetagraphIndex::SubnetVolume) => SelectiveMetagraph { diff --git a/pallets/subtensor/src/tests/migration.rs b/pallets/subtensor/src/tests/migration.rs index cee8e5ff40..b3825c1470 100644 --- a/pallets/subtensor/src/tests/migration.rs +++ b/pallets/subtensor/src/tests/migration.rs @@ -2196,4 +2196,13 @@ fn test_migrate_remove_tao_dividends() { migration, 200_000, ); + + let storage_name = "PendingRootDivs"; + test_remove_storage_item( + MIGRATION_NAME, + pallet_name, + storage_name, + migration, + 200_000, + ); } diff --git a/pallets/subtensor/src/tests/networks.rs b/pallets/subtensor/src/tests/networks.rs index 5f0fe244f1..d5d3620bf1 100644 --- a/pallets/subtensor/src/tests/networks.rs +++ b/pallets/subtensor/src/tests/networks.rs @@ -370,7 +370,6 @@ fn dissolve_clears_all_per_subnet_storages() { NetworkRegistrationAllowed::::insert(net, true); NetworkPowRegistrationAllowed::::insert(net, true); PendingEmission::::insert(net, AlphaCurrency::from(1)); - PendingRootDivs::::insert(net, TaoCurrency::from(1)); PendingRootAlphaDivs::::insert(net, AlphaCurrency::from(1)); PendingOwnerCut::::insert(net, AlphaCurrency::from(1)); BlocksSinceLastStep::::insert(net, 1u64); @@ -527,7 +526,6 @@ fn dissolve_clears_all_per_subnet_storages() { assert!(!NetworkRegistrationAllowed::::contains_key(net)); assert!(!NetworkPowRegistrationAllowed::::contains_key(net)); assert!(!PendingEmission::::contains_key(net)); - assert!(!PendingRootDivs::::contains_key(net)); assert!(!PendingRootAlphaDivs::::contains_key(net)); assert!(!PendingOwnerCut::::contains_key(net)); assert!(!BlocksSinceLastStep::::contains_key(net)); From b0e346e3acc814bb04c2143e73ef92b0f80aef04 Mon Sep 17 00:00:00 2001 From: Shamil Gadelshin Date: Fri, 17 Oct 2025 15:01:34 +0300 Subject: [PATCH 46/54] Claim all during network deregistration. --- pallets/subtensor/src/coinbase/root.rs | 10 ++- pallets/subtensor/src/staking/claim_root.rs | 43 ++++++++++- pallets/subtensor/src/tests/claim_root.rs | 86 +++++++++++++++++++++ 3 files changed, 132 insertions(+), 7 deletions(-) diff --git a/pallets/subtensor/src/coinbase/root.rs b/pallets/subtensor/src/coinbase/root.rs index 05856b79bc..9267c75d1f 100644 --- a/pallets/subtensor/src/coinbase/root.rs +++ b/pallets/subtensor/src/coinbase/root.rs @@ -366,22 +366,24 @@ impl Pallet { /// * 'NotSubnetOwner': If the caller does not own the specified subnet. /// pub fn do_dissolve_network(netuid: NetUid) -> dispatch::DispatchResult { - // 1. --- The network exists? + // --- The network exists? ensure!( Self::if_subnet_exist(netuid) && netuid != NetUid::ROOT, Error::::SubnetNotExists ); - // 2. --- Perform the cleanup before removing the network. + Self::finalize_all_subnet_root_dividends(netuid); + + // --- Perform the cleanup before removing the network. T::SwapInterface::dissolve_all_liquidity_providers(netuid)?; Self::destroy_alpha_in_out_stakes(netuid)?; T::SwapInterface::clear_protocol_liquidity(netuid)?; T::CommitmentsInterface::purge_netuid(netuid); - // 3. --- Remove the network + // --- Remove the network Self::remove_network(netuid); - // 4. --- Emit the NetworkRemoved event + // --- Emit the NetworkRemoved event log::info!("NetworkRemoved( netuid:{netuid:?} )"); Self::deposit_event(Event::NetworkRemoved(netuid)); diff --git a/pallets/subtensor/src/staking/claim_root.rs b/pallets/subtensor/src/staking/claim_root.rs index baa1d460b6..1171ae24a4 100644 --- a/pallets/subtensor/src/staking/claim_root.rs +++ b/pallets/subtensor/src/staking/claim_root.rs @@ -2,7 +2,7 @@ use super::*; use frame_support::weights::Weight; use sp_core::Get; use sp_std::collections::btree_set::BTreeSet; -use substrate_fixed::types::I96F32; +use substrate_fixed::types::{I96F32, U64F64}; use subtensor_swap_interface::SwapHandler; impl Pallet { @@ -129,11 +129,14 @@ impl Pallet { coldkey: &T::AccountId, netuid: NetUid, root_claim_type: RootClaimTypeEnum, + ignore_minimum_condition: bool, ) { // Substract the root claimed. let owed: I96F32 = Self::get_root_owed_for_hotkey_coldkey_float(hotkey, coldkey, netuid); - if owed < I96F32::saturating_from_num(DefaultMinRootClaimAmount::::get()) { + if !ignore_minimum_condition + && owed < I96F32::saturating_from_num(DefaultMinRootClaimAmount::::get()) + { log::debug!( "root claim on subnet {netuid} is skipped: {owed:?} for h={hotkey:?},c={coldkey:?} " ); @@ -218,7 +221,7 @@ impl Pallet { weight.saturating_accrue(T::DbWeight::get().reads(1)); root_claimable.iter().for_each(|(netuid, _)| { - Self::root_claim_on_subnet(hotkey, coldkey, *netuid, root_claim_type.clone()); + Self::root_claim_on_subnet(hotkey, coldkey, *netuid, root_claim_type.clone(), false); weight.saturating_accrue(Self::root_claim_on_subnet_weight(root_claim_type.clone())); }); @@ -367,4 +370,38 @@ impl Pallet { RootClaimable::::insert(new_hotkey, dst_root_claimable); } + + /// Claim all root dividends for subnet and remove all associated data. + pub fn finalize_all_subnet_root_dividends(netuid: NetUid) { + let mut hotkeys_to_clear = BTreeSet::new(); + for (hotkey, root_claimable) in RootClaimable::::iter() { + if root_claimable.contains_key(&netuid) { + let alpha_values: Vec<((T::AccountId, NetUid), U64F64)> = + Alpha::::iter_prefix((&hotkey,)).collect(); + + for ((coldkey, alpha_netuid), _) in alpha_values.into_iter() { + if alpha_netuid == NetUid::ROOT { + Self::root_claim_on_subnet( + &hotkey, + &coldkey, + netuid, + RootClaimTypeEnum::Swap, + true, + ); + + RootClaimed::::remove((hotkey.clone(), coldkey, netuid)); + if !hotkeys_to_clear.contains(&hotkey) { + hotkeys_to_clear.insert(hotkey.clone()); + } + } + } + } + } + + for hotkey in hotkeys_to_clear.into_iter() { + RootClaimable::::mutate(&hotkey, |claimable| { + claimable.remove(&netuid); + }); + } + } } diff --git a/pallets/subtensor/src/tests/claim_root.rs b/pallets/subtensor/src/tests/claim_root.rs index 8290019ec7..d93419b05e 100644 --- a/pallets/subtensor/src/tests/claim_root.rs +++ b/pallets/subtensor/src/tests/claim_root.rs @@ -1214,3 +1214,89 @@ fn test_claim_root_with_swap_hotkey() { .expect("claimable must exist at this point"); }); } + +#[test] +fn test_claim_root_on_network_deregistration() { + new_test_ext(1).execute_with(|| { + let owner_coldkey = U256::from(1001); + let other_coldkey = U256::from(10010); + let hotkey = U256::from(1002); + let coldkey = U256::from(1003); + let netuid = add_dynamic_network(&hotkey, &owner_coldkey); + + SubtensorModule::set_tao_weight(u64::MAX); // Set TAO weight to 1.0 + SubnetMechanism::::insert(netuid, 1); + + let tao_reserve = TaoCurrency::from(50_000_000_000); + let alpha_in = AlphaCurrency::from(100_000_000_000); + SubnetTAO::::insert(netuid, tao_reserve); + SubnetAlphaIn::::insert(netuid, alpha_in); + let current_price = + ::SwapInterface::current_alpha_price(netuid.into()) + .saturating_to_num::(); + assert_eq!(current_price, 0.5f64); + + let root_stake = 2_000_000u64; + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &coldkey, + NetUid::ROOT, + root_stake.into(), + ); + let root_stake_rate = 0.1f64; + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &other_coldkey, + NetUid::ROOT, + (9 * root_stake).into(), + ); + + let initial_total_hotkey_alpha = 10_000_000u64; + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &owner_coldkey, + netuid, + initial_total_hotkey_alpha.into(), + ); + + // Distribute pending root alpha + + let pending_root_alpha = 10_000_000u64; + SubtensorModule::drain_pending_emission( + netuid, + AlphaCurrency::ZERO, + pending_root_alpha.into(), + AlphaCurrency::ZERO, + ); + + // Claim root via network deregistration + + assert_ok!(SubtensorModule::do_dissolve_network(netuid)); + + // Check new stake + let validator_take_percent = 0.18f64; + + let new_stake: u64 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &coldkey, + NetUid::ROOT, + ) + .into(); + + let estimated_stake_increment = (pending_root_alpha as f64) + * (1f64 - validator_take_percent) + * current_price + * root_stake_rate; + + assert_abs_diff_eq!( + new_stake, + root_stake + estimated_stake_increment as u64, + epsilon = 10000u64, + ); + + assert!(!RootClaimed::::contains_key(( + &hotkey, &coldkey, netuid + ))); + assert!(!RootClaimable::::get(&hotkey).contains_key(&netuid)); + }); +} From d68d3aa97c8c3f7cb5f3cf81c718ac4632ed3507 Mon Sep 17 00:00:00 2001 From: Shamil Gadelshin Date: Fri, 17 Oct 2025 16:45:54 +0300 Subject: [PATCH 47/54] Modify default claim root key number --- pallets/subtensor/src/lib.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 07d0c50ad8..87175c43ad 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -367,9 +367,8 @@ pub mod pallet { /// Ideally this is calculated using the number of staking coldkey /// and the block time. pub fn DefaultNumRootClaim() -> u64 { - // TODO: replace with size of staking coldkeys / 7200 - // i.e. once per day - 15 + // once per week (+ spare keys for skipped tries) + 5 } #[pallet::type_value] From 29e99d757da4e6473e95bdd0d677c539220696a9 Mon Sep 17 00:00:00 2001 From: Shamil Gadelshin Date: Mon, 20 Oct 2025 15:23:09 +0300 Subject: [PATCH 48/54] Introduce root claim threshold for subnets. --- pallets/subtensor/src/benchmarks.rs | 19 ++++++ pallets/subtensor/src/lib.rs | 10 ++- pallets/subtensor/src/macros/dispatches.rs | 36 ++++++++++- pallets/subtensor/src/macros/errors.rs | 2 + pallets/subtensor/src/staking/claim_root.rs | 2 +- pallets/subtensor/src/tests/claim_root.rs | 68 +++++++++++++++++++-- 6 files changed, 127 insertions(+), 10 deletions(-) diff --git a/pallets/subtensor/src/benchmarks.rs b/pallets/subtensor/src/benchmarks.rs index d7b9e63abc..8378a82154 100644 --- a/pallets/subtensor/src/benchmarks.rs +++ b/pallets/subtensor/src/benchmarks.rs @@ -1656,4 +1656,23 @@ mod pallet_benchmarks { #[extrinsic_call] _(RawOrigin::Root, 100); } + + #[benchmark] + fn sudo_set_root_claim_threshold() { + let coldkey: T::AccountId = whitelisted_caller(); + let hotkey: T::AccountId = account("A", 0, 1); + + let netuid = Subtensor::::get_next_netuid(); + + let lock_cost = Subtensor::::get_network_lock_cost(); + Subtensor::::add_balance_to_coldkey_account(&coldkey, lock_cost.into()); + + assert_ok!(Subtensor::::register_network( + RawOrigin::Signed(coldkey.clone()).into(), + hotkey.clone() + )); + + #[extrinsic_call] + _(RawOrigin::Root, netuid, 100); + } } diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 87175c43ad..d1a0ef0828 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -58,6 +58,8 @@ pub const ALPHA_MAP_BATCH_SIZE: usize = 30; pub const MAX_NUM_ROOT_CLAIMS: u64 = 50; +pub const MAX_ROOT_CLAIM_THRESHOLD: u64 = 10_000_000; + #[allow(deprecated)] #[deny(missing_docs)] #[import_section(errors::errors)] @@ -350,8 +352,8 @@ pub mod pallet { /// Default minimum root claim amount. /// This is the minimum amount of root claim that can be made. /// Any amount less than this will not be claimed. - pub fn DefaultMinRootClaimAmount() -> u64 { - 500_000 + pub fn DefaultMinRootClaimAmount() -> I96F32 { + 500_000u64.into() } #[pallet::type_value] @@ -1901,6 +1903,10 @@ pub mod pallet { ValueQuery, >; + #[pallet::storage] // --- MAP(netuid ) --> Root claim threshold + pub type RootClaimableThreshold = + StorageMap<_, Blake2_128Concat, NetUid, I96F32, ValueQuery, DefaultMinRootClaimAmount>; + #[pallet::storage] // --- MAP ( hot ) --> MAP(netuid ) --> claimable_dividends | Root claimable dividends. pub type RootClaimable = StorageMap< _, diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index 9ebb9420cd..91e66f6760 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -12,9 +12,9 @@ mod dispatches { use sp_core::ecdsa::Signature; use sp_runtime::{Percent, traits::Saturating}; - use crate::MAX_NUM_ROOT_CLAIMS; - use crate::MAX_CRV3_COMMIT_SIZE_BYTES; + use crate::MAX_NUM_ROOT_CLAIMS; + use crate::MAX_ROOT_CLAIM_THRESHOLD; /// Dispatchable functions allow users to interact with the pallet and invoke state changes. /// These functions materialize as "extrinsics", which are often compared to transactions. /// Dispatchable functions must be annotated with a weight and must return a DispatchResult. @@ -2484,5 +2484,37 @@ mod dispatches { Ok(()) } + + /// --- Sets the root claim type for the coldkey. + /// # Args: + /// * 'origin': (Origin): + /// - The signature of the caller's coldkey. + /// + /// # Event: + /// * RootClaimTypeSet; + /// - On the successfully setting the root claim type for the coldkey. + /// + #[pallet::call_index(124)] + #[pallet::weight(( + Weight::from_parts(4_000_000, 0).saturating_add(T::DbWeight::get().writes(1_u64)), + DispatchClass::Operational, + Pays::Yes + ))] + pub fn sudo_set_root_claim_threshold( + origin: OriginFor, + netuid: NetUid, + new_value: u64, + ) -> DispatchResult { + Self::ensure_subnet_owner_or_root(origin, netuid)?; + + ensure!( + new_value <= I96F32::from(MAX_ROOT_CLAIM_THRESHOLD), + Error::::InvalidRootClaimThreshold + ); + + RootClaimableThreshold::::set(netuid, new_value.into()); + + Ok(()) + } } } diff --git a/pallets/subtensor/src/macros/errors.rs b/pallets/subtensor/src/macros/errors.rs index bfc5b68375..f3102cc1ca 100644 --- a/pallets/subtensor/src/macros/errors.rs +++ b/pallets/subtensor/src/macros/errors.rs @@ -266,5 +266,7 @@ mod errors { ChildParentInconsistency, /// Invalid number of root claims InvalidNumRootClaim, + /// Invalid value of root claim threshold + InvalidRootClaimThreshold, } } diff --git a/pallets/subtensor/src/staking/claim_root.rs b/pallets/subtensor/src/staking/claim_root.rs index 1171ae24a4..8209e23f8c 100644 --- a/pallets/subtensor/src/staking/claim_root.rs +++ b/pallets/subtensor/src/staking/claim_root.rs @@ -135,7 +135,7 @@ impl Pallet { let owed: I96F32 = Self::get_root_owed_for_hotkey_coldkey_float(hotkey, coldkey, netuid); if !ignore_minimum_condition - && owed < I96F32::saturating_from_num(DefaultMinRootClaimAmount::::get()) + && owed < I96F32::saturating_from_num(RootClaimableThreshold::::get(&netuid)) { log::debug!( "root claim on subnet {netuid} is skipped: {owed:?} for h={hotkey:?},c={coldkey:?} " diff --git a/pallets/subtensor/src/tests/claim_root.rs b/pallets/subtensor/src/tests/claim_root.rs index d93419b05e..f1c1865ea5 100644 --- a/pallets/subtensor/src/tests/claim_root.rs +++ b/pallets/subtensor/src/tests/claim_root.rs @@ -4,14 +4,17 @@ use crate::tests::mock::{ RuntimeOrigin, SubtensorModule, Test, add_dynamic_network, new_test_ext, run_to_block, }; use crate::{ - Error, MAX_NUM_ROOT_CLAIMS, NetworksAdded, NumRootClaim, NumStakingColdkeys, - PendingRootAlphaDivs, RootClaimable, StakingColdkeys, StakingColdkeysByIndex, SubnetAlphaIn, - SubnetMechanism, SubnetTAO, SubtokenEnabled, Tempo, pallet, + DefaultMinRootClaimAmount, Error, MAX_NUM_ROOT_CLAIMS, MAX_ROOT_CLAIM_THRESHOLD, NetworksAdded, + NumRootClaim, NumStakingColdkeys, PendingRootAlphaDivs, RootClaimable, RootClaimableThreshold, + StakingColdkeys, StakingColdkeysByIndex, SubnetAlphaIn, SubnetMechanism, SubnetTAO, + SubtokenEnabled, Tempo, pallet, }; use crate::{RootClaimType, RootClaimTypeEnum, RootClaimed}; use approx::assert_abs_diff_eq; +use frame_support::dispatch::RawOrigin; use frame_support::pallet_prelude::Weight; -use frame_support::{assert_noop, assert_ok}; +use frame_support::traits::Get; +use frame_support::{assert_err, assert_noop, assert_ok}; use sp_core::{H256, U256}; use sp_runtime::DispatchError; use substrate_fixed::types::{I96F32, U96F32}; @@ -1297,6 +1300,61 @@ fn test_claim_root_on_network_deregistration() { assert!(!RootClaimed::::contains_key(( &hotkey, &coldkey, netuid ))); - assert!(!RootClaimable::::get(&hotkey).contains_key(&netuid)); + assert!(!RootClaimable::::get(hotkey).contains_key(&netuid)); + }); +} + +#[test] +fn test_claim_root_threshold() { + new_test_ext(1).execute_with(|| { + let owner_coldkey = U256::from(1001); + let hotkey = U256::from(1002); + let netuid = add_dynamic_network(&hotkey, &owner_coldkey); + + assert_eq!( + RootClaimableThreshold::::get(netuid), + DefaultMinRootClaimAmount::::get() + ); + + let threshold = 1000u64; + assert_ok!(SubtensorModule::sudo_set_root_claim_threshold( + RawOrigin::Root.into(), + netuid, + threshold + )); + assert_eq!( + RootClaimableThreshold::::get(netuid), + I96F32::from(threshold) + ); + + let threshold = 2000u64; + assert_ok!(SubtensorModule::sudo_set_root_claim_threshold( + RawOrigin::Signed(owner_coldkey).into(), + netuid, + threshold + )); + assert_eq!( + RootClaimableThreshold::::get(netuid), + I96F32::from(threshold) + ); + + // Errors + assert_err!( + SubtensorModule::sudo_set_root_claim_threshold( + RawOrigin::Signed(hotkey).into(), + netuid, + threshold + ), + DispatchError::BadOrigin, + ); + + assert_err!( + SubtensorModule::sudo_set_root_claim_threshold( + RawOrigin::Signed(owner_coldkey).into(), + netuid, + MAX_ROOT_CLAIM_THRESHOLD + 1 + ), + Error::::InvalidRootClaimThreshold, + ); }); } From fb17282dd257d1dc21cb9b0dc462fcdc21fca917 Mon Sep 17 00:00:00 2001 From: Shamil Gadelshin Date: Thu, 23 Oct 2025 14:25:20 +0300 Subject: [PATCH 49/54] Fix benchmarks --- pallets/subtensor/src/benchmarks.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/subtensor/src/benchmarks.rs b/pallets/subtensor/src/benchmarks.rs index bbd08dec8e..550b4a68b5 100644 --- a/pallets/subtensor/src/benchmarks.rs +++ b/pallets/subtensor/src/benchmarks.rs @@ -1642,7 +1642,7 @@ mod pallet_benchmarks { #[benchmark] fn sudo_set_num_root_claims() { #[extrinsic_call] - _(RawOrigin::Root, 100); + _(RawOrigin::Root, 40); } #[benchmark] From 55f30b2e480766ad42a641e30d597e70968a95b0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 23 Oct 2025 15:15:22 +0000 Subject: [PATCH 50/54] auto-update benchmark weights --- pallets/admin-utils/src/lib.rs | 2 +- pallets/subtensor/src/macros/dispatches.rs | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pallets/admin-utils/src/lib.rs b/pallets/admin-utils/src/lib.rs index b2fec3602f..f7d59f6bd3 100644 --- a/pallets/admin-utils/src/lib.rs +++ b/pallets/admin-utils/src/lib.rs @@ -1951,7 +1951,7 @@ pub mod pallet { /// Only callable by root. #[pallet::call_index(74)] #[pallet::weight(( - Weight::from_parts(9_418_000, 0) + Weight::from_parts(5_510_000, 0) .saturating_add(::DbWeight::get().reads(0_u64)) .saturating_add(::DbWeight::get().writes(1_u64)), DispatchClass::Operational diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index 3871502add..0d6dba817b 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -82,7 +82,7 @@ mod dispatches { /// - Attempting to set weights with max value exceeding limit. #[pallet::call_index(0)] #[pallet::weight((Weight::from_parts(15_540_000_000, 0) - .saturating_add(T::DbWeight::get().reads(4112_u64)) + .saturating_add(T::DbWeight::get().reads(4111_u64)) .saturating_add(T::DbWeight::get().writes(2)), DispatchClass::Normal, Pays::No))] pub fn set_weights( origin: OriginFor, @@ -1053,8 +1053,8 @@ mod dispatches { /// The extrinsic for user to change its hotkey in subnet or all subnets. #[pallet::call_index(70)] #[pallet::weight((Weight::from_parts(275_300_000, 0) - .saturating_add(T::DbWeight::get().reads(52_u64)) - .saturating_add(T::DbWeight::get().writes(37)), DispatchClass::Normal, Pays::No))] + .saturating_add(T::DbWeight::get().reads(50_u64)) + .saturating_add(T::DbWeight::get().writes(35_u64)), DispatchClass::Normal, Pays::No))] pub fn swap_hotkey( origin: OriginFor, hotkey: T::AccountId, @@ -1229,7 +1229,7 @@ mod dispatches { #[pallet::call_index(59)] #[pallet::weight((Weight::from_parts(235_400_000, 0) .saturating_add(T::DbWeight::get().reads(39_u64)) - .saturating_add(T::DbWeight::get().writes(57_u64)), DispatchClass::Normal, Pays::Yes))] + .saturating_add(T::DbWeight::get().writes(56_u64)), DispatchClass::Normal, Pays::Yes))] pub fn register_network(origin: OriginFor, hotkey: T::AccountId) -> DispatchResult { Self::do_register_network(origin, &hotkey, 1, None) } @@ -1516,7 +1516,7 @@ mod dispatches { #[pallet::call_index(79)] #[pallet::weight((Weight::from_parts(234_200_000, 0) .saturating_add(T::DbWeight::get().reads(38_u64)) - .saturating_add(T::DbWeight::get().writes(56_u64)), DispatchClass::Normal, Pays::Yes))] + .saturating_add(T::DbWeight::get().writes(55_u64)), DispatchClass::Normal, Pays::Yes))] pub fn register_network_with_identity( origin: OriginFor, hotkey: T::AccountId, @@ -1585,8 +1585,8 @@ mod dispatches { /// - Thrown if key has hit transaction rate limit #[pallet::call_index(84)] #[pallet::weight((Weight::from_parts(358_500_000, 0) - .saturating_add(T::DbWeight::get().reads(36_u64)) - .saturating_add(T::DbWeight::get().writes(21_u64)), DispatchClass::Operational, Pays::Yes))] + .saturating_add(T::DbWeight::get().reads(39_u64)) + .saturating_add(T::DbWeight::get().writes(24_u64)), DispatchClass::Operational, Pays::Yes))] pub fn unstake_all_alpha(origin: OriginFor, hotkey: T::AccountId) -> DispatchResult { Self::do_unstake_all_alpha(origin, hotkey) } @@ -2326,7 +2326,7 @@ mod dispatches { #[pallet::call_index(121)] #[pallet::weight(( Weight::from_parts(117_000_000, 7767) - .saturating_add(T::DbWeight::get().reads(11_u64)) + .saturating_add(T::DbWeight::get().reads(12_u64)) .saturating_add(T::DbWeight::get().writes(4_u64)), DispatchClass::Normal, Pays::Yes @@ -2351,7 +2351,7 @@ mod dispatches { /// #[pallet::call_index(122)] #[pallet::weight(( - Weight::from_parts(6_000_000, 0).saturating_add(T::DbWeight::get().writes(1_u64)), + Weight::from_parts(19_420_000, 0).saturating_add(T::DbWeight::get().writes(4_u64)), DispatchClass::Normal, Pays::Yes ))] @@ -2406,7 +2406,7 @@ mod dispatches { /// #[pallet::call_index(124)] #[pallet::weight(( - Weight::from_parts(4_000_000, 0).saturating_add(T::DbWeight::get().writes(1_u64)), + Weight::from_parts(5_711_000, 0).saturating_add(T::DbWeight::get().writes(1_u64)), DispatchClass::Operational, Pays::Yes ))] From 558f90883b6a6b4f41e26a8cf2ce60a7fdc17e17 Mon Sep 17 00:00:00 2001 From: Shamil Gadelshin Date: Fri, 24 Oct 2025 13:52:51 +0300 Subject: [PATCH 51/54] Apply review suggestions --- .../subtensor/src/coinbase/run_coinbase.rs | 12 +++++------ pallets/subtensor/src/lib.rs | 2 +- pallets/subtensor/src/macros/dispatches.rs | 20 ++----------------- pallets/subtensor/src/staking/claim_root.rs | 4 ++-- pallets/subtensor/src/staking/helpers.rs | 4 ++-- pallets/subtensor/src/tests/coinbase.rs | 1 - 6 files changed, 13 insertions(+), 30 deletions(-) diff --git a/pallets/subtensor/src/coinbase/run_coinbase.rs b/pallets/subtensor/src/coinbase/run_coinbase.rs index 232341f93f..53ac3c6ac5 100644 --- a/pallets/subtensor/src/coinbase/run_coinbase.rs +++ b/pallets/subtensor/src/coinbase/run_coinbase.rs @@ -174,7 +174,7 @@ impl Pallet { let tao_weight: U96F32 = root_tao.saturating_mul(Self::get_tao_weight()); log::debug!("tao_weight: {tao_weight:?}"); - // --- 6. Seperate out root dividends in alpha and sell them into tao. + // --- 6. Seperate out root dividends in alpha and keep them. // Then accumulate those dividends for later. for netuid_i in subnets_to_emit_to.iter() { // Get remaining alpha out. @@ -237,7 +237,7 @@ impl Pallet { let pending_alpha = PendingEmission::::get(netuid); PendingEmission::::insert(netuid, AlphaCurrency::ZERO); - // Get and drain the subnet pending root divs. + // Get and drain the subnet pending root alpha divs. let pending_root_alpha = PendingRootAlphaDivs::::get(netuid); PendingRootAlphaDivs::::insert(netuid, AlphaCurrency::ZERO); @@ -245,7 +245,7 @@ impl Pallet { let owner_cut = PendingOwnerCut::::get(netuid); PendingOwnerCut::::insert(netuid, AlphaCurrency::ZERO); - // Drain pending root divs, alpha emission, and owner cut. + // Drain pending root alpha divs, alpha emission, and owner cut. Self::drain_pending_emission(netuid, pending_alpha, pending_root_alpha, owner_cut); } else { // Increment @@ -306,7 +306,7 @@ impl Pallet { // Setup. let zero: U96F32 = asfloat!(0.0); - // Accumulate root divs and alpha_divs. For each hotkey we compute their + // Accumulate root alpha divs and alpha_divs. For each hotkey we compute their // local and root dividend proportion based on their alpha_stake/root_stake let mut total_root_divs: U96F32 = asfloat!(0); let mut total_alpha_divs: U96F32 = asfloat!(0); @@ -532,7 +532,7 @@ impl Pallet { // Get take prop let alpha_take: U96F32 = Self::get_hotkey_take_float(&hotkey).saturating_mul(root_alpha); - // Remove take prop from root_tao + // Remove take prop from root_alpha root_alpha = root_alpha.saturating_sub(alpha_take); // Give the validator their take. log::debug!("hotkey: {hotkey:?} alpha_take: {alpha_take:?}"); @@ -549,7 +549,7 @@ impl Pallet { tou64!(root_alpha).into(), ); - // Record root dividends for this validator on this subnet. + // Record root alpha dividends for this validator on this subnet. AlphaDividendsPerSubnet::::mutate(netuid, hotkey.clone(), |divs| { *divs = divs.saturating_add(tou64!(root_alpha).into()); }); diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 696cbc317e..7e0637dd23 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -1383,7 +1383,7 @@ pub mod pallet { /// --- MAP ( netuid ) --> pending_emission pub type PendingEmission = StorageMap<_, Identity, NetUid, AlphaCurrency, ValueQuery, DefaultPendingEmission>; - /// --- MAP ( netuid ) --> pending_root_emission + /// --- MAP ( netuid ) --> pending_root_alpha_emission #[pallet::storage] pub type PendingRootAlphaDivs = StorageMap<_, Identity, NetUid, AlphaCurrency, ValueQuery, DefaultZeroAlpha>; diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index 0d6dba817b..7792a55579 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -2367,15 +2367,7 @@ mod dispatches { Ok(()) } - /// --- Sets the root claim type for the coldkey. - /// # Args: - /// * 'origin': (Origin): - /// - The signature of the caller's coldkey. - /// - /// # Event: - /// * RootClaimTypeSet; - /// - On the successfully setting the root claim type for the coldkey. - /// + /// --- Sets root claim number (sudo extrinsic). #[pallet::call_index(123)] #[pallet::weight(( Weight::from_parts(4_000_000, 0).saturating_add(T::DbWeight::get().writes(1_u64)), @@ -2395,15 +2387,7 @@ mod dispatches { Ok(()) } - /// --- Sets the root claim type for the coldkey. - /// # Args: - /// * 'origin': (Origin): - /// - The signature of the caller's coldkey. - /// - /// # Event: - /// * RootClaimTypeSet; - /// - On the successfully setting the root claim type for the coldkey. - /// + /// --- Sets root claim threshold for subnet (sudo or owner origin). #[pallet::call_index(124)] #[pallet::weight(( Weight::from_parts(5_711_000, 0).saturating_add(T::DbWeight::get().writes(1_u64)), diff --git a/pallets/subtensor/src/staking/claim_root.rs b/pallets/subtensor/src/staking/claim_root.rs index 8209e23f8c..e43f15b23d 100644 --- a/pallets/subtensor/src/staking/claim_root.rs +++ b/pallets/subtensor/src/staking/claim_root.rs @@ -101,7 +101,7 @@ impl Pallet { let root_claimed: I96F32 = I96F32::saturating_from_num(RootClaimed::::get((hotkey, coldkey, netuid))); - // Substract the already claimed alpha. + // Subtract the already claimed alpha. let owed: I96F32 = claimable.saturating_sub(root_claimed); owed @@ -131,7 +131,7 @@ impl Pallet { root_claim_type: RootClaimTypeEnum, ignore_minimum_condition: bool, ) { - // Substract the root claimed. + // Subtract the root claimed. let owed: I96F32 = Self::get_root_owed_for_hotkey_coldkey_float(hotkey, coldkey, netuid); if !ignore_minimum_condition diff --git a/pallets/subtensor/src/staking/helpers.rs b/pallets/subtensor/src/staking/helpers.rs index 075d5b9d9d..1176064e36 100644 --- a/pallets/subtensor/src/staking/helpers.rs +++ b/pallets/subtensor/src/staking/helpers.rs @@ -326,9 +326,9 @@ impl Pallet { }); } - /// The function clears Alpha map in batches. Each run will check ALPHA_MAP_CLEAN_BATCH_SIZE + /// The function clears Alpha map in batches. Each run will check ALPHA_MAP_BATCH_SIZE /// alphas. It keeps the alpha value stored when it's >= than MIN_ALPHA. - /// The function uses AlphaMapCleanLastKey as a storage for key iterator between runs. + /// The function uses AlphaMapLastKey as a storage for key iterator between runs. pub fn populate_root_coldkey_staking_maps() { // Get starting key for the batch. Get the first key if we restart the process. let mut new_starting_raw_key = AlphaMapLastKey::::get(); diff --git a/pallets/subtensor/src/tests/coinbase.rs b/pallets/subtensor/src/tests/coinbase.rs index 0d4e1337d4..19ce6da28d 100644 --- a/pallets/subtensor/src/tests/coinbase.rs +++ b/pallets/subtensor/src/tests/coinbase.rs @@ -947,7 +947,6 @@ fn test_drain_base_with_subnet_with_two_stakers_registered_and_root_different_am SubtensorModule::drain_pending_emission( netuid, pending_alpha, - // pending_tao, AlphaCurrency::ZERO, AlphaCurrency::ZERO, ); From efbe7825654e76f80bb1a0a7b4909e90a5085874 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 27 Oct 2025 17:22:50 +0000 Subject: [PATCH 52/54] auto-update benchmark weights --- pallets/admin-utils/src/lib.rs | 4 ++-- pallets/subtensor/src/macros/dispatches.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pallets/admin-utils/src/lib.rs b/pallets/admin-utils/src/lib.rs index f7d59f6bd3..c7bb66f4bd 100644 --- a/pallets/admin-utils/src/lib.rs +++ b/pallets/admin-utils/src/lib.rs @@ -1579,7 +1579,7 @@ pub mod pallet { /// Weight is handled by the `#[pallet::weight]` attribute. #[pallet::call_index(62)] #[pallet::weight(( - Weight::from_parts(6_392_000, 3507) + Weight::from_parts(10_020_000, 3507) .saturating_add(T::DbWeight::get().reads(1_u64)), DispatchClass::Operational, Pays::Yes @@ -1661,7 +1661,7 @@ pub mod pallet { /// Weight is handled by the `#[pallet::weight]` attribute. #[pallet::call_index(65)] #[pallet::weight(( - Weight::from_parts(3_918_000, 0) + Weight::from_parts(6_201_000, 0) .saturating_add(T::DbWeight::get().writes(1_u64)), DispatchClass::Operational, Pays::Yes diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index 15ec3ebfff..9d59874f89 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -1585,8 +1585,8 @@ mod dispatches { /// - Thrown if key has hit transaction rate limit #[pallet::call_index(84)] #[pallet::weight((Weight::from_parts(358_500_000, 0) - .saturating_add(T::DbWeight::get().reads(36_u64)) - .saturating_add(T::DbWeight::get().writes(21_u64)), DispatchClass::Normal, Pays::Yes))] + .saturating_add(T::DbWeight::get().reads(39_u64)) + .saturating_add(T::DbWeight::get().writes(24_u64)), DispatchClass::Normal, Pays::Yes))] pub fn unstake_all_alpha(origin: OriginFor, hotkey: T::AccountId) -> DispatchResult { Self::do_unstake_all_alpha(origin, hotkey) } From f08ef461347255495610549fa3ceebd25256180b Mon Sep 17 00:00:00 2001 From: Shamil Gadelshin Date: Tue, 28 Oct 2025 12:36:37 +0300 Subject: [PATCH 53/54] Enable zero as sudo_set_num_root_claims param value. --- pallets/subtensor/src/macros/dispatches.rs | 4 ++-- pallets/subtensor/src/tests/claim_root.rs | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index 9d59874f89..7a470f8dd2 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -2367,7 +2367,7 @@ mod dispatches { Ok(()) } - /// --- Sets root claim number (sudo extrinsic). + /// --- Sets root claim number (sudo extrinsic). Zero disables auto-claim. #[pallet::call_index(123)] #[pallet::weight(( Weight::from_parts(4_000_000, 0).saturating_add(T::DbWeight::get().writes(1_u64)), @@ -2378,7 +2378,7 @@ mod dispatches { ensure_root(origin)?; ensure!( - new_value > 0 && new_value <= MAX_NUM_ROOT_CLAIMS, + new_value <= MAX_NUM_ROOT_CLAIMS, Error::::InvalidNumRootClaim ); diff --git a/pallets/subtensor/src/tests/claim_root.rs b/pallets/subtensor/src/tests/claim_root.rs index f1c1865ea5..5632f53bae 100644 --- a/pallets/subtensor/src/tests/claim_root.rs +++ b/pallets/subtensor/src/tests/claim_root.rs @@ -792,6 +792,11 @@ fn test_claim_root_block_hash_indices() { let k = 15u64; let n = 15000u64; + // 0 + let indices = + SubtensorModule::block_hash_to_indices(H256(sp_core::keccak_256(b"zero")), 0, n); + assert!(indices.is_empty()); + // 1 let hash = sp_core::keccak_256(b"some"); let mut indices = SubtensorModule::block_hash_to_indices(H256(hash), k, n); @@ -1008,11 +1013,6 @@ fn test_sudo_set_num_root_claims() { DispatchError::BadOrigin ); - assert_noop!( - SubtensorModule::sudo_set_num_root_claims(RuntimeOrigin::root(), 0u64), - Error::::InvalidNumRootClaim - ); - assert_noop!( SubtensorModule::sudo_set_num_root_claims( RuntimeOrigin::root(), From 91e5776e4ddea38aef2878e62667c18c9fcb97d6 Mon Sep 17 00:00:00 2001 From: Shamil Gadelshin Date: Tue, 28 Oct 2025 14:32:29 +0300 Subject: [PATCH 54/54] Add subnets to claim_root extrinsic. --- pallets/subtensor/src/benchmarks.rs | 3 +- pallets/subtensor/src/lib.rs | 3 + pallets/subtensor/src/macros/dispatches.rs | 15 +- pallets/subtensor/src/macros/errors.rs | 2 + pallets/subtensor/src/staking/claim_root.rs | 25 ++- pallets/subtensor/src/tests/claim_root.rs | 227 ++++++++++++++++---- 6 files changed, 228 insertions(+), 47 deletions(-) diff --git a/pallets/subtensor/src/benchmarks.rs b/pallets/subtensor/src/benchmarks.rs index 550b4a68b5..2a13a18aa0 100644 --- a/pallets/subtensor/src/benchmarks.rs +++ b/pallets/subtensor/src/benchmarks.rs @@ -14,6 +14,7 @@ use sp_runtime::{ BoundedVec, Percent, traits::{BlakeTwo256, Hash}, }; +use sp_std::collections::btree_set::BTreeSet; use sp_std::vec; use subtensor_runtime_common::{AlphaCurrency, NetUid, TaoCurrency}; @@ -1630,7 +1631,7 @@ mod pallet_benchmarks { ),); #[extrinsic_call] - _(RawOrigin::Signed(coldkey.clone())); + _(RawOrigin::Signed(coldkey.clone()), BTreeSet::from([netuid])); // Verification let new_stake = diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 7e0637dd23..f52e1e078b 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -58,6 +58,8 @@ pub const ALPHA_MAP_BATCH_SIZE: usize = 30; pub const MAX_NUM_ROOT_CLAIMS: u64 = 50; +pub const MAX_SUBNET_CLAIMS: usize = 5; + pub const MAX_ROOT_CLAIM_THRESHOLD: u64 = 10_000_000; #[allow(deprecated)] @@ -89,6 +91,7 @@ pub mod pallet { use sp_core::{ConstU32, H160, H256}; use sp_runtime::traits::{Dispatchable, TrailingZeroInput}; use sp_std::collections::btree_map::BTreeMap; + use sp_std::collections::btree_set::BTreeSet; use sp_std::collections::vec_deque::VecDeque; use sp_std::vec; use sp_std::vec::Vec; diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index 7a470f8dd2..d5b2bae419 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -15,6 +15,8 @@ mod dispatches { use crate::MAX_CRV3_COMMIT_SIZE_BYTES; use crate::MAX_NUM_ROOT_CLAIMS; use crate::MAX_ROOT_CLAIM_THRESHOLD; + use crate::MAX_SUBNET_CLAIMS; + /// Dispatchable functions allow users to interact with the pallet and invoke state changes. /// These functions materialize as "extrinsics", which are often compared to transactions. /// Dispatchable functions must be annotated with a weight and must return a DispatchResult. @@ -2331,12 +2333,21 @@ mod dispatches { DispatchClass::Normal, Pays::Yes ))] - pub fn claim_root(origin: OriginFor) -> DispatchResultWithPostInfo { + pub fn claim_root( + origin: OriginFor, + subnets: BTreeSet, + ) -> DispatchResultWithPostInfo { let coldkey: T::AccountId = ensure_signed(origin)?; + ensure!(!subnets.is_empty(), Error::::InvalidSubnetNumber); + ensure!( + subnets.len() <= MAX_SUBNET_CLAIMS, + Error::::InvalidSubnetNumber + ); + Self::maybe_add_coldkey_index(&coldkey); - let weight = Self::do_root_claim(coldkey); + let weight = Self::do_root_claim(coldkey, Some(subnets)); Ok((Some(weight), Pays::Yes).into()) } diff --git a/pallets/subtensor/src/macros/errors.rs b/pallets/subtensor/src/macros/errors.rs index 1f0063e4d6..5a15330075 100644 --- a/pallets/subtensor/src/macros/errors.rs +++ b/pallets/subtensor/src/macros/errors.rs @@ -264,5 +264,7 @@ mod errors { InvalidNumRootClaim, /// Invalid value of root claim threshold InvalidRootClaimThreshold, + /// Exceeded subnet limit number or zero. + InvalidSubnetNumber, } } diff --git a/pallets/subtensor/src/staking/claim_root.rs b/pallets/subtensor/src/staking/claim_root.rs index e43f15b23d..0ffbeb4b54 100644 --- a/pallets/subtensor/src/staking/claim_root.rs +++ b/pallets/subtensor/src/staking/claim_root.rs @@ -210,7 +210,11 @@ impl Pallet { .saturating_add(T::DbWeight::get().reads(7_u64)) .saturating_add(T::DbWeight::get().writes(5_u64)) } - pub fn root_claim_all(hotkey: &T::AccountId, coldkey: &T::AccountId) -> Weight { + pub fn root_claim_all( + hotkey: &T::AccountId, + coldkey: &T::AccountId, + subnets: Option>, + ) -> Weight { let mut weight = Weight::default(); let root_claim_type = RootClaimType::::get(coldkey); @@ -220,10 +224,19 @@ impl Pallet { let root_claimable = RootClaimable::::get(hotkey); weight.saturating_accrue(T::DbWeight::get().reads(1)); - root_claimable.iter().for_each(|(netuid, _)| { + for (netuid, _) in root_claimable.iter() { + let skip = subnets + .as_ref() + .map(|subnets| !subnets.contains(netuid)) + .unwrap_or(false); + + if skip { + continue; + } + Self::root_claim_on_subnet(hotkey, coldkey, *netuid, root_claim_type.clone(), false); weight.saturating_accrue(Self::root_claim_on_subnet_weight(root_claim_type.clone())); - }); + } weight } @@ -278,7 +291,7 @@ impl Pallet { } } - pub fn do_root_claim(coldkey: T::AccountId) -> Weight { + pub fn do_root_claim(coldkey: T::AccountId, subnets: Option>) -> Weight { let mut weight = Weight::default(); let hotkeys = StakingHotkeys::::get(&coldkey); @@ -286,7 +299,7 @@ impl Pallet { hotkeys.iter().for_each(|hotkey| { weight.saturating_accrue(T::DbWeight::get().reads(1)); - weight.saturating_accrue(Self::root_claim_all(hotkey, &coldkey)); + weight.saturating_accrue(Self::root_claim_all(hotkey, &coldkey, subnets.clone())); }); Self::deposit_event(Event::RootClaimed { coldkey }); @@ -321,7 +334,7 @@ impl Pallet { for i in coldkeys_to_claim.iter() { weight.saturating_accrue(T::DbWeight::get().reads(1)); if let Ok(coldkey) = StakingColdkeysByIndex::::try_get(i) { - weight.saturating_accrue(Self::do_root_claim(coldkey.clone())); + weight.saturating_accrue(Self::do_root_claim(coldkey.clone(), None)); } continue; diff --git a/pallets/subtensor/src/tests/claim_root.rs b/pallets/subtensor/src/tests/claim_root.rs index 5632f53bae..e8a295ae0d 100644 --- a/pallets/subtensor/src/tests/claim_root.rs +++ b/pallets/subtensor/src/tests/claim_root.rs @@ -17,6 +17,7 @@ use frame_support::traits::Get; use frame_support::{assert_err, assert_noop, assert_ok}; use sp_core::{H256, U256}; use sp_runtime::DispatchError; +use std::collections::BTreeSet; use substrate_fixed::types::{I96F32, U96F32}; use subtensor_runtime_common::{AlphaCurrency, Currency, NetUid, TaoCurrency}; use subtensor_swap_interface::SwapHandler; @@ -117,7 +118,10 @@ fn test_claim_root_with_drain_emissions() { ),); assert_eq!(RootClaimType::::get(coldkey), RootClaimTypeEnum::Keep); - assert_ok!(SubtensorModule::claim_root(RuntimeOrigin::signed(coldkey),)); + assert_ok!(SubtensorModule::claim_root( + RuntimeOrigin::signed(coldkey), + BTreeSet::from([netuid]) + )); let new_stake: u64 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid) @@ -157,7 +161,10 @@ fn test_claim_root_with_drain_emissions() { epsilon = 0.001f64, ); - assert_ok!(SubtensorModule::claim_root(RuntimeOrigin::signed(coldkey),)); + assert_ok!(SubtensorModule::claim_root( + RuntimeOrigin::signed(coldkey), + BTreeSet::from([netuid]) + )); let new_stake2: u64 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid) @@ -241,12 +248,14 @@ fn test_claim_root_adding_stake_proportionally_for_two_stakers() { AlphaCurrency::ZERO, ); - assert_ok!(SubtensorModule::claim_root(RuntimeOrigin::signed( - alice_coldkey - ),)); - assert_ok!(SubtensorModule::claim_root(RuntimeOrigin::signed( - bob_coldkey - ),)); + assert_ok!(SubtensorModule::claim_root( + RuntimeOrigin::signed(alice_coldkey), + BTreeSet::from([netuid]) + )); + assert_ok!(SubtensorModule::claim_root( + RuntimeOrigin::signed(bob_coldkey), + BTreeSet::from([netuid]) + )); // Check stakes let validator_take_percent = 0.18f64; @@ -340,12 +349,14 @@ fn test_claim_root_adding_stake_disproportionally_for_two_stakers() { AlphaCurrency::ZERO, ); - assert_ok!(SubtensorModule::claim_root(RuntimeOrigin::signed( - alice_coldkey - ),)); - assert_ok!(SubtensorModule::claim_root(RuntimeOrigin::signed( - bob_coldkey - ),)); + assert_ok!(SubtensorModule::claim_root( + RuntimeOrigin::signed(alice_coldkey), + BTreeSet::from([netuid]) + )); + assert_ok!(SubtensorModule::claim_root( + RuntimeOrigin::signed(bob_coldkey), + BTreeSet::from([netuid]) + )); // Check stakes let validator_take_percent = 0.18f64; @@ -429,12 +440,14 @@ fn test_claim_root_with_changed_stake() { AlphaCurrency::ZERO, ); - assert_ok!(SubtensorModule::claim_root(RuntimeOrigin::signed( - alice_coldkey - ),)); - assert_ok!(SubtensorModule::claim_root(RuntimeOrigin::signed( - bob_coldkey - ),)); + assert_ok!(SubtensorModule::claim_root( + RuntimeOrigin::signed(alice_coldkey), + BTreeSet::from([netuid]) + )); + assert_ok!(SubtensorModule::claim_root( + RuntimeOrigin::signed(bob_coldkey), + BTreeSet::from([netuid]) + )); // Check stakes let validator_take_percent = 0.18f64; @@ -479,12 +492,14 @@ fn test_claim_root_with_changed_stake() { AlphaCurrency::ZERO, ); - assert_ok!(SubtensorModule::claim_root(RuntimeOrigin::signed( - alice_coldkey - ),)); - assert_ok!(SubtensorModule::claim_root(RuntimeOrigin::signed( - bob_coldkey - ),)); + assert_ok!(SubtensorModule::claim_root( + RuntimeOrigin::signed(alice_coldkey), + BTreeSet::from([netuid]) + )); + assert_ok!(SubtensorModule::claim_root( + RuntimeOrigin::signed(bob_coldkey), + BTreeSet::from([netuid]) + )); // Check new stakes @@ -530,12 +545,14 @@ fn test_claim_root_with_changed_stake() { AlphaCurrency::ZERO, ); - assert_ok!(SubtensorModule::claim_root(RuntimeOrigin::signed( - alice_coldkey - ),)); - assert_ok!(SubtensorModule::claim_root(RuntimeOrigin::signed( - bob_coldkey - ),)); + assert_ok!(SubtensorModule::claim_root( + RuntimeOrigin::signed(alice_coldkey), + BTreeSet::from([netuid]) + )); + assert_ok!(SubtensorModule::claim_root( + RuntimeOrigin::signed(bob_coldkey), + BTreeSet::from([netuid]) + )); // Check new stakes @@ -630,7 +647,10 @@ fn test_claim_root_with_drain_emissions_and_swap_claim_type() { ),); assert_eq!(RootClaimType::::get(coldkey), RootClaimTypeEnum::Swap); - assert_ok!(SubtensorModule::claim_root(RuntimeOrigin::signed(coldkey),)); + assert_ok!(SubtensorModule::claim_root( + RuntimeOrigin::signed(coldkey), + BTreeSet::from([netuid]) + )); // Check new stake @@ -661,7 +681,10 @@ fn test_claim_root_with_drain_emissions_and_swap_claim_type() { AlphaCurrency::ZERO, ); - assert_ok!(SubtensorModule::claim_root(RuntimeOrigin::signed(coldkey),)); + assert_ok!(SubtensorModule::claim_root( + RuntimeOrigin::signed(coldkey), + BTreeSet::from([netuid]) + )); // Check new stake (2) @@ -694,7 +717,10 @@ fn test_claim_root_with_drain_emissions_and_swap_claim_type() { AlphaCurrency::ZERO, ); - assert_ok!(SubtensorModule::claim_root(RuntimeOrigin::signed(coldkey),)); + assert_ok!(SubtensorModule::claim_root( + RuntimeOrigin::signed(coldkey), + BTreeSet::from([netuid]) + )); // Check new stake (3) @@ -776,7 +802,10 @@ fn test_claim_root_with_run_coinbase() { ),); assert_eq!(RootClaimType::::get(coldkey), RootClaimTypeEnum::Keep); - assert_ok!(SubtensorModule::claim_root(RuntimeOrigin::signed(coldkey),)); + assert_ok!(SubtensorModule::claim_root( + RuntimeOrigin::signed(coldkey), + BTreeSet::from([netuid]) + )); let new_stake: u64 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid) @@ -1082,7 +1111,10 @@ fn test_claim_root_with_swap_coldkey() { ),); assert_eq!(RootClaimType::::get(coldkey), RootClaimTypeEnum::Keep); - assert_ok!(SubtensorModule::claim_root(RuntimeOrigin::signed(coldkey),)); + assert_ok!(SubtensorModule::claim_root( + RuntimeOrigin::signed(coldkey), + BTreeSet::from([netuid]) + )); let new_stake: u64 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid) @@ -1169,7 +1201,10 @@ fn test_claim_root_with_swap_hotkey() { ),); assert_eq!(RootClaimType::::get(coldkey), RootClaimTypeEnum::Keep); - assert_ok!(SubtensorModule::claim_root(RuntimeOrigin::signed(coldkey),)); + assert_ok!(SubtensorModule::claim_root( + RuntimeOrigin::signed(coldkey), + BTreeSet::from([netuid]) + )); let new_stake: u64 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid) @@ -1358,3 +1393,119 @@ fn test_claim_root_threshold() { ); }); } + +#[test] +fn test_claim_root_subnet_limits() { + new_test_ext(1).execute_with(|| { + let coldkey = U256::from(1003); + + assert_err!( + SubtensorModule::claim_root(RuntimeOrigin::signed(coldkey), BTreeSet::new()), + Error::::InvalidSubnetNumber + ); + + assert_err!( + SubtensorModule::claim_root( + RuntimeOrigin::signed(coldkey), + BTreeSet::from_iter((0u16..=10u16).into_iter().map(NetUid::from)) + ), + Error::::InvalidSubnetNumber + ); + }); +} + +#[test] +fn test_claim_root_with_unrelated_subnets() { + new_test_ext(1).execute_with(|| { + let owner_coldkey = U256::from(1001); + let hotkey = U256::from(1002); + let coldkey = U256::from(1003); + let netuid = add_dynamic_network(&hotkey, &owner_coldkey); + + SubtensorModule::set_tao_weight(u64::MAX); // Set TAO weight to 1.0 + + let root_stake = 2_000_000u64; + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &coldkey, + NetUid::ROOT, + root_stake.into(), + ); + + let initial_total_hotkey_alpha = 10_000_000u64; + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &owner_coldkey, + netuid, + initial_total_hotkey_alpha.into(), + ); + + let old_validator_stake = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &owner_coldkey, + netuid, + ); + assert_eq!(old_validator_stake, initial_total_hotkey_alpha.into()); + + // Distribute pending root alpha + + let pending_root_alpha = 1_000_000u64; + SubtensorModule::drain_pending_emission( + netuid, + AlphaCurrency::ZERO, + pending_root_alpha.into(), + AlphaCurrency::ZERO, + ); + + // Claim root alpha + + assert_ok!(SubtensorModule::set_root_claim_type( + RuntimeOrigin::signed(coldkey), + RootClaimTypeEnum::Keep + ),); + + // Claim root alpha on unrelated subnets + + let unrelated_subnet_uid = NetUid::from(100u16); + + assert_ok!(SubtensorModule::claim_root( + RuntimeOrigin::signed(coldkey), + BTreeSet::from([unrelated_subnet_uid]) + )); + + let new_stake: u64 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &coldkey, + unrelated_subnet_uid, + ) + .into(); + + assert_eq!(new_stake, 0u64,); + + // Check root claim for correct subnet + + // before + let new_stake: u64 = + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid) + .into(); + + assert_eq!(new_stake, 0u64,); + + assert_ok!(SubtensorModule::claim_root( + RuntimeOrigin::signed(coldkey), + BTreeSet::from([netuid]) + )); + + // after + let new_stake: u64 = + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid) + .into(); + + assert!(new_stake > 0u64); + + // Check root claimed value saved + + let claimed = RootClaimed::::get((&hotkey, &coldkey, netuid)); + assert_eq!(u128::from(new_stake), claimed); + }); +}