diff --git a/common/src/lib.rs b/common/src/lib.rs index 392e8f8080..a98a957ad8 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/admin-utils/src/lib.rs b/pallets/admin-utils/src/lib.rs index b2fec3602f..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 @@ -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/admin-utils/src/tests/mod.rs b/pallets/admin-utils/src/tests/mod.rs index fcf0e33ed2..1aaefc8f8d 100644 --- a/pallets/admin-utils/src/tests/mod.rs +++ b/pallets/admin-utils/src/tests/mod.rs @@ -2634,7 +2634,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/benchmarks.rs b/pallets/subtensor/src/benchmarks.rs index 73c7a8beba..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}; @@ -1565,4 +1566,102 @@ mod pallet_benchmarks { #[extrinsic_call] _(RawOrigin::Signed(coldkey.clone()), netuid, hotkey.clone()); } + #[benchmark] + fn set_root_claim_type() { + let coldkey: T::AccountId = whitelisted_caller(); + + #[extrinsic_call] + _(RawOrigin::Signed(coldkey.clone()), RootClaimTypeEnum::Keep); + } + + #[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()), BTreeSet::from([netuid])); + + // Verification + let new_stake = + Subtensor::::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid); + + assert!(new_stake > initial_stake); + } + + #[benchmark] + fn sudo_set_num_root_claims() { + #[extrinsic_call] + _(RawOrigin::Root, 40); + } + + #[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/coinbase/block_step.rs b/pallets/subtensor/src/coinbase/block_step.rs index 6a96090b05..818235a955 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,11 @@ 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); + // --- 6. Populate root coldkey maps. + Self::populate_root_coldkey_staking_maps(); + // Return ok. Ok(()) } diff --git a/pallets/subtensor/src/coinbase/root.rs b/pallets/subtensor/src/coinbase/root.rs index b220b47604..642a7f18ac 100644 --- a/pallets/subtensor/src/coinbase/root.rs +++ b/pallets/subtensor/src/coinbase/root.rs @@ -204,22 +204,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)); @@ -313,8 +315,7 @@ impl Pallet { // --- 15. Mechanism step / emissions bookkeeping. FirstEmissionBlockNumber::::remove(netuid); PendingEmission::::remove(netuid); - PendingRootDivs::::remove(netuid); - PendingAlphaSwapped::::remove(netuid); + PendingRootAlphaDivs::::remove(netuid); PendingOwnerCut::::remove(netuid); BlocksSinceLastStep::::remove(netuid); LastMechansimStepBlock::::remove(netuid); @@ -347,6 +348,7 @@ impl Pallet { RAORecycledForRegistration::::remove(netuid); MaxRegistrationsPerBlock::::remove(netuid); WeightsVersionKey::::remove(netuid); + PendingRootAlphaDivs::::remove(netuid); // --- 17. Subtoken / feature flags. LiquidAlphaOn::::remove(netuid); @@ -365,7 +367,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/coinbase/run_coinbase.rs b/pallets/subtensor/src/coinbase/run_coinbase.rs index 006edcb2ef..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. @@ -197,27 +197,14 @@ 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(), - true, - ); - if let Ok(ok_result) = swap_result { - let root_tao = ok_result.amount_paid_out; - // Accumulate root divs for subnet. - PendingRootDivs::::mutate(*netuid_i, |total| { - *total = total.saturating_add(root_tao); - }); - } + 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. PendingEmission::::mutate(*netuid_i, |total| { *total = total.saturating_add(tou64!(pending_alpha).into()); @@ -250,26 +237,16 @@ impl Pallet { let pending_alpha = PendingEmission::::get(netuid); PendingEmission::::insert(netuid, AlphaCurrency::ZERO); - // Get and drain the subnet pending root divs. - let pending_tao = PendingRootDivs::::get(netuid); - PendingRootDivs::::insert(netuid, TaoCurrency::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 and drain the subnet pending root alpha divs. + let pending_root_alpha = PendingRootAlphaDivs::::get(netuid); + PendingRootAlphaDivs::::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_tao, - pending_swapped, - 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 BlocksSinceLastStep::::mutate(netuid, |total| *total = total.saturating_add(1)); @@ -312,7 +289,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, @@ -323,13 +300,13 @@ 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. 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); @@ -375,22 +352,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); - 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. - tao_dividends + 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!("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(); @@ -410,7 +387,7 @@ impl Pallet { } log::debug!("prop_alpha_dividends: {prop_alpha_dividends:?}"); - (prop_alpha_dividends, tao_dividends) + (prop_alpha_dividends, root_alpha_dividends) } fn get_owner_hotkeys(netuid: NetUid, coldkey: &T::AccountId) -> Vec { @@ -450,7 +427,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,37 +527,31 @@ 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. + 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); - // Remove take prop from root_tao - root_tao = root_tao.saturating_sub(tao_take); + let alpha_take: U96F32 = + Self::get_hotkey_take_float(&hotkey).saturating_mul(root_alpha); + // Remove take prop from root_alpha + 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( + + Self::increase_root_claimable_for_hotkey_and_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()); + + // Record root alpha dividends for this validator on this subnet. + AlphaDividendsPerSubnet::::mutate(netuid, hotkey.clone(), |divs| { + *divs = divs.saturating_add(tou64!(root_alpha).into()); }); } } @@ -602,7 +573,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,33 +589,32 @@ 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_swapped: AlphaCurrency, + pending_root_alpha: 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:?}, owner_cut: {owner_cut:?}" ); let tao_weight = Self::get_tao_weight(); // Run the epoch. let hotkey_emission: Vec<(T::AccountId, AlphaCurrency, AlphaCurrency)> = - Self::epoch_with_mechanisms(netuid, pending_alpha.saturating_add(pending_swapped)); + Self::epoch_with_mechanisms(netuid, pending_alpha.saturating_add(pending_root_alpha)); log::debug!("hotkey_emission: {hotkey_emission:?}"); // Compute the pending validator alpha. @@ -661,18 +631,18 @@ 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 }; - 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 +653,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 1f764b7baa..f52e1e078b 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -54,6 +54,14 @@ extern crate alloc; 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; + +pub const MAX_SUBNET_CLAIMS: usize = 5; + +pub const MAX_ROOT_CLAIM_THRESHOLD: u64 = 10_000_000; + #[allow(deprecated)] #[deny(missing_docs)] #[import_section(errors::errors)] @@ -82,6 +90,8 @@ 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::btree_set::BTreeSet; use sp_std::collections::vec_deque::VecDeque; use sp_std::vec; use sp_std::vec::Vec; @@ -314,6 +324,53 @@ 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() -> I96F32 { + 500_000u64.into() + } + + #[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 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 { + // once per week (+ spare keys for skipped tries) + 5 + } + #[pallet::type_value] /// Default value for zero. pub fn DefaultZeroU64() -> u64 { @@ -847,6 +904,12 @@ pub mod pallet { pub fn DefaultMovingPrice() -> I96F32 { I96F32::saturating_from_num(0.0) } + + #[pallet::type_value] + /// Default subnet root claimable + pub fn DefaultRootClaimable() -> BTreeMap { + Default::default() + } #[pallet::type_value] /// Default value for Share Pool variables pub fn DefaultSharePoolZero() -> U64F64 { @@ -874,6 +937,12 @@ pub mod pallet { 50400 } + /// Default last Alpha map key for iteration + #[pallet::type_value] + pub fn DefaultAlphaIterationLastKey() -> Option> { + None + } + #[pallet::type_value] /// Default number of terminal blocks in a tempo during which admin operations are prohibited pub fn DefaultAdminFreezeWindow() -> u16 { @@ -1037,17 +1106,6 @@ pub mod pallet { ValueQuery, DefaultZeroAlpha, >; - #[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 ==== @@ -1202,6 +1260,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>; @@ -1323,13 +1386,9 @@ pub mod pallet { /// --- MAP ( netuid ) --> pending_emission pub type PendingEmission = StorageMap<_, Identity, NetUid, AlphaCurrency, ValueQuery, DefaultPendingEmission>; + /// --- MAP ( netuid ) --> pending_root_alpha_emission #[pallet::storage] - /// --- MAP ( netuid ) --> pending_root_emission - pub type PendingRootDivs = - StorageMap<_, Identity, NetUid, TaoCurrency, ValueQuery, DefaultZeroTao>; - #[pallet::storage] - /// --- MAP ( netuid ) --> pending_alpha_swapped - pub type PendingAlphaSwapped = + pub type PendingRootAlphaDivs = StorageMap<_, Identity, NetUid, AlphaCurrency, ValueQuery, DefaultZeroAlpha>; #[pallet::storage] /// --- MAP ( netuid ) --> pending_owner_cut @@ -1833,6 +1892,53 @@ 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< + _, + Blake2_128Concat, + T::AccountId, + BTreeMap, + ValueQuery, + DefaultRootClaimable, + >; + + // Already claimed root alpha. + #[pallet::storage] + pub type RootClaimed = StorageNMap< + _, + ( + NMapKey, // hot + NMapKey, // cold + NMapKey, // subnet + ), + u128, + ValueQuery, + >; + #[pallet::storage] // -- MAP ( cold ) --> root_claim_type enum + pub type RootClaimType = StorageMap< + _, + Blake2_128Concat, + T::AccountId, + RootClaimTypeEnum, + ValueQuery, + DefaultRootClaimType, + >; + #[pallet::storage] // --- MAP ( u64 ) --> coldkey | Maps coldkeys that have stake to an index + 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>; + #[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 112c81ecf6..d5b2bae419 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -13,6 +13,10 @@ mod dispatches { use sp_runtime::{Percent, traits::Saturating}; 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. @@ -80,7 +84,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, @@ -1051,8 +1055,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, @@ -1227,7 +1231,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) } @@ -1514,7 +1518,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, @@ -1583,8 +1587,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) } @@ -2309,5 +2313,113 @@ mod dispatches { ensure_root(origin)?; Self::do_dissolve_network(netuid) } + + /// --- 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(121)] + #[pallet::weight(( + Weight::from_parts(117_000_000, 7767) + .saturating_add(T::DbWeight::get().reads(12_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)), + DispatchClass::Normal, + Pays::Yes + ))] + 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, Some(subnets)); + 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(122)] + #[pallet::weight(( + Weight::from_parts(19_420_000, 0).saturating_add(T::DbWeight::get().writes(4_u64)), + 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::maybe_add_coldkey_index(&coldkey); + + Self::change_root_claim_type(&coldkey, new_root_claim_type); + Ok(()) + } + + /// --- 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)), + DispatchClass::Operational, + Pays::Yes + ))] + pub fn sudo_set_num_root_claims(origin: OriginFor, new_value: u64) -> DispatchResult { + ensure_root(origin)?; + + ensure!( + new_value <= MAX_NUM_ROOT_CLAIMS, + Error::::InvalidNumRootClaim + ); + + NumRootClaim::::set(new_value); + + Ok(()) + } + + /// --- 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)), + 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 80e9c2a3ee..5a15330075 100644 --- a/pallets/subtensor/src/macros/errors.rs +++ b/pallets/subtensor/src/macros/errors.rs @@ -260,5 +260,11 @@ mod errors { TrimmingWouldExceedMaxImmunePercentage, /// Violating the rules of Childkey-Parentkey consistency ChildParentInconsistency, + /// Invalid number of root claims + InvalidNumRootClaim, + /// Invalid value of root claim threshold + InvalidRootClaimThreshold, + /// Exceeded subnet limit number or zero. + InvalidSubnetNumber, } } diff --git a/pallets/subtensor/src/macros/events.rs b/pallets/subtensor/src/macros/events.rs index a784273f5a..c2931024ee 100644 --- a/pallets/subtensor/src/macros/events.rs +++ b/pallets/subtensor/src/macros/events.rs @@ -448,5 +448,24 @@ mod events { /// The account ID of the hotkey. hotkey: 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, + }, } } diff --git a/pallets/subtensor/src/macros/hooks.rs b/pallets/subtensor/src/macros/hooks.rs index 037d27900e..87a87e911c 100644 --- a/pallets/subtensor/src/macros/hooks.rs +++ b/pallets/subtensor/src/macros/hooks.rs @@ -157,7 +157,9 @@ mod hooks { // Migrate AutoStakeDestinationColdkeys .saturating_add(migrations::migrate_auto_stake_destination::migrate_auto_stake_destination::()) // Migrate Kappa to default (0.5) - .saturating_add(migrations::migrate_kappa_map_to_default::migrate_kappa_map_to_default::()); + .saturating_add(migrations::migrate_kappa_map_to_default::migrate_kappa_map_to_default::()) + // 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..b93df22339 --- /dev/null +++ b/pallets/subtensor/src/migrations/migrate_remove_tao_dividends.rs @@ -0,0 +1,64 @@ +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"); + let weight3 = remove_prefix::("PendingRootDivs"); + + // 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) + .saturating_add(weight3) +} diff --git a/pallets/subtensor/src/migrations/mod.rs b/pallets/subtensor/src/migrations/mod.rs index b74c8e8608..d95e4c7bac 100644 --- a/pallets/subtensor/src/migrations/mod.rs +++ b/pallets/subtensor/src/migrations/mod.rs @@ -33,6 +33,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/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 2ec772b2c6..df0c8023b0 100644 --- a/pallets/subtensor/src/rpc_info/metagraph.rs +++ b/pallets/subtensor/src/rpc_info/metagraph.rs @@ -644,7 +644,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())); @@ -694,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), @@ -1004,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 { @@ -1405,7 +1406,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 13789292da..83c0cfed8b 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/staking/claim_root.rs b/pallets/subtensor/src/staking/claim_root.rs new file mode 100644 index 0000000000..0ffbeb4b54 --- /dev/null +++ b/pallets/subtensor/src/staking/claim_root.rs @@ -0,0 +1,420 @@ +use super::*; +use frame_support::weights::Weight; +use sp_core::Get; +use sp_std::collections::btree_set::BTreeSet; +use substrate_fixed::types::{I96F32, U64F64}; +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: BTreeSet = BTreeSet::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.insert(idx); + last_idx = idx; + } + indices.into_iter().collect() + } + + 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::ROOT)); + + // Get increment + let increment: I96F32 = I96F32::saturating_from_num(amount) + .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::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; + } + + // Increment claimable for this subnet. + RootClaimable::::mutate(hotkey, |claimable| { + claimable + .entry(netuid) + .and_modify(|claim_total| *claim_total = claim_total.saturating_add(increment)) + .or_insert(increment); + }); + } + + pub fn get_root_claimable_for_hotkey_coldkey( + hotkey: &T::AccountId, + coldkey: &T::AccountId, + netuid: NetUid, + ) -> I96F32 { + // Get this keys stake balance on root. + 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: 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); + + claimable + } + + pub fn get_root_owed_for_hotkey_coldkey_float( + hotkey: &T::AccountId, + coldkey: &T::AccountId, + netuid: NetUid, + ) -> I96F32 { + let claimable = Self::get_root_claimable_for_hotkey_coldkey(hotkey, coldkey, netuid); + + // Attain the root claimed to avoid overclaiming. + let root_claimed: I96F32 = + I96F32::saturating_from_num(RootClaimed::::get((hotkey, coldkey, netuid))); + + // Subtract the already claimed alpha. + let owed: I96F32 = claimable.saturating_sub(root_claimed); + + 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, + ignore_minimum_condition: bool, + ) { + // Subtract the root claimed. + let owed: I96F32 = Self::get_root_owed_for_hotkey_coldkey_float(hotkey, coldkey, netuid); + + if !ignore_minimum_condition + && owed < I96F32::saturating_from_num(RootClaimableThreshold::::get(&netuid)) + { + log::debug!( + "root claim on subnet {netuid} is skipped: {owed:?} for h={hotkey:?},c={coldkey:?} " + ); + 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 { + log::debug!( + "root claim on subnet {netuid} is skipped: {owed:?} for h={hotkey:?},c={coldkey:?}" + ); + return; // no-op + } + + match root_claim_type { + // Increase stake on root + RootClaimTypeEnum::Swap => { + // Swap the alpha owed to TAO + let owed_tao = match Self::swap_alpha_for_tao( + netuid, + owed_u64.into(), + T::SwapInterface::min_price::(), + false, + ) { + 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( + hotkey, + coldkey, + NetUid::ROOT, + owed_tao.amount_paid_out.to_u64().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 + Self::increase_stake_for_hotkey_and_coldkey_on_subnet( + hotkey, + coldkey, + netuid, + owed_u64.into(), + ); + } + }; + + // Increase root claimed by owed amount. + RootClaimed::::mutate((hotkey, coldkey, netuid), |root_claimed| { + *root_claimed = root_claimed.saturating_add(owed_u64.into()); + }); + } + + fn root_claim_on_subnet_weight(_root_claim_type: RootClaimTypeEnum) -> Weight { + 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, + subnets: Option>, + ) -> Weight { + let mut weight = Weight::default(); + + 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); + weight.saturating_accrue(T::DbWeight::get().reads(1)); + + 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 + } + + pub fn add_stake_adjust_root_claimed_for_hotkey_and_coldkey( + hotkey: &T::AccountId, + coldkey: &T::AccountId, + amount: u64, + ) { + // Iterate over all the subnets this hotkey is staked on for root. + 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)); + + // Increase root claimed based on the claimable rate. + 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); + } + } + + pub fn remove_stake_adjust_root_claimed_for_hotkey_and_coldkey( + hotkey: &T::AccountId, + coldkey: &T::AccountId, + amount: AlphaCurrency, + ) { + // Iterate over all the subnets this hotkey is staked on for root. + let root_claimable = RootClaimable::::get(hotkey); + for (netuid, claimable_rate) in root_claimable.iter() { + if *netuid == NetUid::ROOT.into() { + continue; // Skip the root netuid. + } + + // 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 + .saturating_mul(I96F32::from(u64::from(amount))) + .saturating_to_num(), + ); + + // Set the new root_claimed value. + RootClaimed::::insert((hotkey, coldkey, netuid), new_root_claimed); + } + } + + pub fn do_root_claim(coldkey: T::AccountId, subnets: Option>) -> 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, subnets.clone())); + }); + + Self::deposit_event(Event::RootClaimed { coldkey }); + + weight + } + + 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) { + 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(); + + 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) = StakingColdkeysByIndex::::try_get(i) { + weight.saturating_accrue(Self::do_root_claim(coldkey.clone(), None)); + } + + 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, + }); + } + + pub fn transfer_root_claimed_for_new_keys( + netuid: NetUid, + old_hotkey: &T::AccountId, + new_hotkey: &T::AccountId, + old_coldkey: &T::AccountId, + new_coldkey: &T::AccountId, + ) { + let old_root_claimed = RootClaimed::::get((old_hotkey, old_coldkey, netuid)); + RootClaimed::::remove((old_hotkey, old_coldkey, netuid)); + + RootClaimed::::mutate((new_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); + } + + /// 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/staking/helpers.rs b/pallets/subtensor/src/staking/helpers.rs index cae9579f33..1176064e36 100644 --- a/pallets/subtensor/src/staking/helpers.rs +++ b/pallets/subtensor/src/staking/helpers.rs @@ -326,6 +326,56 @@ impl Pallet { }); } + /// 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 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(); + let mut starting_key = None; + if new_starting_raw_key.is_none() { + starting_key = Alpha::::iter_keys().next(); + new_starting_raw_key = starting_key.as_ref().map(Alpha::::hashed_key_for); + } + + 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) + .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); + } + } + pub fn burn_subnet_alpha(_netuid: NetUid, _amount: AlphaCurrency) { // Do nothing; TODO: record burned alpha in a tracker } 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 7910449e23..671632f320 100644 --- a/pallets/subtensor/src/staking/stake_utils.rs +++ b/pallets/subtensor/src/staking/stake_utils.rs @@ -707,6 +707,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 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 // TODO const: fix. // if Self::get_stake(hotkey, coldkey) == 0 { @@ -795,6 +801,14 @@ impl Pallet { Self::set_stake_operation_limit(hotkey, coldkey, netuid.into()); } + // If this is a root-stake + if netuid == NetUid::ROOT { + // 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. Self::deposit_event(Event::StakeAdded( coldkey.clone(), 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/swap/swap_coldkey.rs b/pallets/subtensor/src/swap/swap_coldkey.rs index 06f5d77f57..c81138b58c 100644 --- a/pallets/subtensor/src/swap/swap_coldkey.rs +++ b/pallets/subtensor/src/swap/swap_coldkey.rs @@ -188,6 +188,21 @@ impl Pallet { ); // Remove the value from the old account. Alpha::::remove((&hotkey, old_coldkey, netuid)); + + if new_alpha.saturating_add(old_alpha) > U64F64::from(0u64) { + Self::transfer_root_claimed_for_new_keys( + netuid, + &hotkey, + &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. weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); diff --git a/pallets/subtensor/src/swap/swap_hotkey.rs b/pallets/subtensor/src/swap/swap_hotkey.rs index 681db3d4ad..38f1f85df8 100644 --- a/pallets/subtensor/src/swap/swap_hotkey.rs +++ b/pallets/subtensor/src/swap/swap_hotkey.rs @@ -495,15 +495,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 @@ -512,9 +504,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 new file mode 100644 index 0000000000..e8a295ae0d --- /dev/null +++ b/pallets/subtensor/src/tests/claim_root.rs @@ -0,0 +1,1511 @@ +#![allow(clippy::expect_used)] + +use crate::tests::mock::{ + RuntimeOrigin, SubtensorModule, Test, add_dynamic_network, new_test_ext, run_to_block, +}; +use crate::{ + 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::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; + +#[test] +fn test_claim_root_set_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_with_drain_emissions() { + 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, + ); + + // Check new validator stake + 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 = (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 as u64, + epsilon = 100u64, + ); + + // Check claimable + + 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); + + 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), + BTreeSet::from([netuid]) + )); + + 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) + + SubtensorModule::drain_pending_emission( + netuid, + AlphaCurrency::ZERO, + pending_root_alpha.into(), + AlphaCurrency::ZERO, + ); + + // Check claimable (round 2) + + 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); + + 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), + BTreeSet::from([netuid]) + )); + + let new_stake2: u64 = + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid) + .into(); + let calculated_new_stake2 = + (I96F32::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); + }); +} + +#[test] +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); + 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 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, + &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), + BTreeSet::from([netuid]) + )); + assert_ok!(SubtensorModule::claim_root( + RuntimeOrigin::signed(bob_coldkey), + BTreeSet::from([netuid]) + )); + + // 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) * root_stake_rate; + + assert_eq!(alice_stake, bob_stake); + + assert_abs_diff_eq!(alice_stake, estimated_stake as u64, epsilon = 100u64,); + }); +} + +#[test] +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); + 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; + 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, + NetUid::ROOT, + alice_root_stake.into(), + ); + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &bob_coldkey, + NetUid::ROOT, + 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, + &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), + BTreeSet::from([netuid]) + )); + assert_ok!(SubtensorModule::claim_root( + RuntimeOrigin::signed(bob_coldkey), + BTreeSet::from([netuid]) + )); + + // 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 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, alice_estimated_stake as u64, epsilon = 100u64,); + }); +} + +#[test] +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 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 + SubtokenEnabled::::insert(NetUid::ROOT, true); + NetworksAdded::::insert(NetUid::ROOT, true); + + let root_stake = 8_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 + + 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), + BTreeSet::from([netuid]) + )); + assert_ok!(SubtensorModule::claim_root( + RuntimeOrigin::signed(bob_coldkey), + BTreeSet::from([netuid]) + )); + + // 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) / 2f64; + + assert_eq!(alice_stake, bob_stake); + + assert_abs_diff_eq!(alice_stake, estimated_stake as u64, epsilon = 100u64,); + + // 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, + ); + + 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 + + let alice_stake2: u64 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &alice_coldkey, + netuid, + ) + .into(); + + let bob_stake2: 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; + + 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, + stake_increment.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, + ); + + 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 + + let alice_stake3: u64 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &alice_coldkey, + netuid, + ) + .into(); + + let bob_stake3: 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) / 2f64; + + let alice_stake_diff2 = alice_stake3 - alice_stake2; + let bob_stake_diff2 = bob_stake3 - bob_stake2; + + 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,); + }); +} + +#[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), + BTreeSet::from([netuid]) + )); + + // 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), + BTreeSet::from([netuid]) + )); + + // 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), + BTreeSet::from([netuid]) + )); + + // 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, + ); + }); +} + +#[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), + BTreeSet::from([netuid]) + )); + + let new_stake: u64 = + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid) + .into(); + + assert!(new_stake > 0); + }); +} + +#[test] +fn test_claim_root_block_hash_indices() { + new_test_ext(1).execute_with(|| { + 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); + 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); + }); +} +#[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)); + }); +} + +#[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, + ); + }); +} + +#[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(), + 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); + }); +} + +#[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), + BTreeSet::from([netuid]) + )); + + 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); + + 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(); + + assert_ok!(SubtensorModule::perform_swap_coldkey( + &coldkey, + &new_coldkey, + &mut weight + )); + + // Check swapped keys claimed values + + 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); + + 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), + BTreeSet::from([netuid]) + )); + + 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"); + }); +} + +#[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)); + }); +} + +#[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, + ); + }); +} + +#[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); + }); +} diff --git a/pallets/subtensor/src/tests/coinbase.rs b/pallets/subtensor/src/tests/coinbase.rs index 39a964a067..19ce6da28d 100644 --- a/pallets/subtensor/src/tests/coinbase.rs +++ b/pallets/subtensor/src/tests/coinbase.rs @@ -538,36 +538,25 @@ 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, epsilon = 1 ); // 1 - swapped. - assert_abs_diff_eq!( - u64::from(PendingRootDivs::::get(netuid)), - 125000000, - epsilon = 1 - ); // swapped * (price = 1) }); } @@ -578,7 +567,6 @@ fn test_drain_base() { SubtensorModule::drain_pending_emission( 0.into(), AlphaCurrency::ZERO, - TaoCurrency::ZERO, AlphaCurrency::ZERO, AlphaCurrency::ZERO, ) @@ -594,7 +582,6 @@ fn test_drain_base_with_subnet() { SubtensorModule::drain_pending_emission( netuid, AlphaCurrency::ZERO, - TaoCurrency::ZERO, AlphaCurrency::ZERO, AlphaCurrency::ZERO, ) @@ -620,7 +607,6 @@ 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, ); @@ -650,7 +636,6 @@ fn test_drain_base_with_subnet_with_single_staker_registered() { SubtensorModule::drain_pending_emission( netuid, pending_alpha, - TaoCurrency::ZERO, AlphaCurrency::ZERO, AlphaCurrency::ZERO, ); @@ -689,14 +674,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, ); let stake_after = @@ -711,12 +695,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() + pending_tao.to_u64(), - root_after.into(), - 10, - ); // Registered gets all tao emission - assert_eq!(SubnetTAO::::get(NetUid::ROOT), pending_tao); + close(stake_before.to_u64(), root_after.into(), 10); // Registered doesn't get tao immediately }); } @@ -748,7 +727,6 @@ fn test_drain_base_with_subnet_with_two_stakers_registered() { SubtensorModule::drain_pending_emission( netuid, pending_alpha, - TaoCurrency::ZERO, AlphaCurrency::ZERO, AlphaCurrency::ZERO, ); @@ -814,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, ); @@ -842,17 +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 - assert_eq!(SubnetTAO::::get(NetUid::ROOT), pending_tao); }); } @@ -901,8 +867,7 @@ fn test_drain_base_with_subnet_with_two_stakers_registered_and_root_different_am SubtensorModule::drain_pending_emission( netuid, pending_alpha, - pending_tao, - 0.into(), + AlphaCurrency::ZERO, 0.into(), ); let stake_after1 = @@ -933,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() - ); }); } @@ -1001,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, ); @@ -1033,27 +978,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() - ); }); } @@ -1084,7 +1008,6 @@ fn test_drain_alpha_childkey_parentkey() { SubtensorModule::drain_pending_emission( netuid, pending_alpha, - TaoCurrency::ZERO, AlphaCurrency::ZERO, AlphaCurrency::ZERO, ); @@ -1310,7 +1233,6 @@ fn test_get_root_children_drain() { SubtensorModule::drain_pending_emission( alpha, pending_alpha, - TaoCurrency::ZERO, AlphaCurrency::ZERO, AlphaCurrency::ZERO, ); @@ -1334,7 +1256,7 @@ fn test_get_root_children_drain() { SubtensorModule::drain_pending_emission( alpha, pending_alpha, - pending_root1, + // pending_root1, AlphaCurrency::ZERO, AlphaCurrency::ZERO, ); @@ -1342,16 +1264,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); @@ -1361,7 +1280,6 @@ fn test_get_root_children_drain() { SubtensorModule::drain_pending_emission( alpha, pending_alpha, - pending_root2, AlphaCurrency::ZERO, AlphaCurrency::ZERO, ); @@ -1371,25 +1289,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), - pending_root1 + pending_root2 - ); }); } @@ -1463,7 +1368,6 @@ fn test_get_root_children_drain_half_proportion() { SubtensorModule::drain_pending_emission( alpha, pending_alpha, - TaoCurrency::ZERO, AlphaCurrency::ZERO, AlphaCurrency::ZERO, ); @@ -1550,7 +1454,6 @@ fn test_get_root_children_drain_with_take() { SubtensorModule::drain_pending_emission( alpha, pending_alpha, - TaoCurrency::ZERO, AlphaCurrency::ZERO, AlphaCurrency::ZERO, ); @@ -1638,7 +1541,6 @@ fn test_get_root_children_drain_with_half_take() { SubtensorModule::drain_pending_emission( alpha, pending_alpha, - TaoCurrency::ZERO, AlphaCurrency::ZERO, AlphaCurrency::ZERO, ); @@ -1940,7 +1842,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)]; @@ -1951,17 +1853,18 @@ 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( - pending_validator_alpha, - pending_tao, - 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::(); - 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::(), @@ -1969,8 +1872,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 ); }); @@ -1983,7 +1886,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)]; @@ -1994,17 +1897,18 @@ 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( - pending_validator_alpha, - pending_tao, - 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::(); - 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::(), @@ -2012,8 +1916,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 ); }); @@ -2039,7 +1943,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, @@ -2082,7 +1987,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, @@ -2136,7 +2042,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, @@ -2186,7 +2093,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, @@ -2369,7 +2277,6 @@ fn test_drain_pending_emission_no_miners_all_drained() { SubtensorModule::drain_pending_emission( netuid, emission, - TaoCurrency::ZERO, AlphaCurrency::ZERO, AlphaCurrency::ZERO, ); @@ -2442,7 +2349,6 @@ fn test_drain_pending_emission_zero_emission() { SubtensorModule::drain_pending_emission( netuid, 0.into(), - TaoCurrency::ZERO, AlphaCurrency::ZERO, AlphaCurrency::ZERO, ); @@ -2736,7 +2642,6 @@ fn test_drain_alpha_childkey_parentkey_with_burn() { SubtensorModule::drain_pending_emission( netuid, pending_alpha, - TaoCurrency::ZERO, AlphaCurrency::ZERO, AlphaCurrency::ZERO, ); diff --git a/pallets/subtensor/src/tests/migration.rs b/pallets/subtensor/src/tests/migration.rs index 13de9501c9..28159d41bc 100644 --- a/pallets/subtensor/src/tests/migration.rs +++ b/pallets/subtensor/src/tests/migration.rs @@ -2362,3 +2362,38 @@ fn test_migrate_kappa_map_to_default() { ); }); } + +#[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, + ); + + let storage_name = "PendingRootDivs"; + test_remove_storage_item( + MIGRATION_NAME, + pallet_name, + storage_name, + migration, + 200_000, + ); +} diff --git a/pallets/subtensor/src/tests/mod.rs b/pallets/subtensor/src/tests/mod.rs index 6e485b9087..ebe7defcda 100644 --- a/pallets/subtensor/src/tests/mod.rs +++ b/pallets/subtensor/src/tests/mod.rs @@ -1,6 +1,7 @@ mod auto_stake_hotkey; mod batch_tx; mod children; +mod claim_root; mod coinbase; mod consensus; mod delegate_info; diff --git a/pallets/subtensor/src/tests/networks.rs b/pallets/subtensor/src/tests/networks.rs index 67d9f90887..0449c67f86 100644 --- a/pallets/subtensor/src/tests/networks.rs +++ b/pallets/subtensor/src/tests/networks.rs @@ -369,8 +369,7 @@ 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)); - 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); @@ -418,7 +417,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); @@ -526,8 +524,7 @@ 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!(!PendingAlphaSwapped::::contains_key(net)); + assert!(!PendingRootAlphaDivs::::contains_key(net)); assert!(!PendingOwnerCut::::contains_key(net)); assert!(!BlocksSinceLastStep::::contains_key(net)); assert!(!LastMechansimStepBlock::::contains_key(net)); @@ -577,7 +574,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)); diff --git a/pallets/subtensor/src/tests/swap_hotkey.rs b/pallets/subtensor/src/tests/swap_hotkey.rs index be67859f5f..71191d1951 100644 --- a/pallets/subtensor/src/tests/swap_hotkey.rs +++ b/pallets/subtensor/src/tests/swap_hotkey.rs @@ -876,7 +876,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( @@ -927,14 +926,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 219bfe1e37..6e423c1269 100644 --- a/pallets/subtensor/src/tests/swap_hotkey_with_subnet.rs +++ b/pallets/subtensor/src/tests/swap_hotkey_with_subnet.rs @@ -930,7 +930,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()); @@ -982,14 +981,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() - ); }); } diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 286c3312b6..fbed4df342 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -723,6 +723,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 {