diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index a2f61fd4d2..93c2aeecdc 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -1190,7 +1190,7 @@ mod dispatches { /// * `BadOrigin` - If the origin is not root. /// #[pallet::call_index(69)] - #[pallet::weight(Weight::from_parts(10_190_000, 0) + #[pallet::weight(Weight::from_parts(5_841_000, 0) .saturating_add(T::DbWeight::get().reads(0)) .saturating_add(T::DbWeight::get().writes(1)))] pub fn sudo_set_tx_childkey_take_rate_limit( @@ -1246,9 +1246,9 @@ mod dispatches { /// User register a new subnetwork #[pallet::call_index(59)] - #[pallet::weight((Weight::from_parts(235_400_000, 0) - .saturating_add(T::DbWeight::get().reads(37_u64)) - .saturating_add(T::DbWeight::get().writes(53_u64)), DispatchClass::Normal, Pays::Yes))] + #[pallet::weight((Weight::from_parts(352_700_000, 0) + .saturating_add(T::DbWeight::get().reads(48_u64)) + .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) } @@ -1456,8 +1456,8 @@ mod dispatches { /// User register a new subnetwork #[pallet::call_index(79)] #[pallet::weight((Weight::from_parts(396_000_000, 0) - .saturating_add(T::DbWeight::get().reads(36_u64)) - .saturating_add(T::DbWeight::get().writes(52_u64)), DispatchClass::Normal, Pays::Yes))] + .saturating_add(T::DbWeight::get().reads(47_u64)) + .saturating_add(T::DbWeight::get().writes(55_u64)), DispatchClass::Normal, Pays::Yes))] pub fn register_network_with_identity( origin: OriginFor, hotkey: T::AccountId, @@ -1849,7 +1849,7 @@ mod dispatches { /// Will charge based on the weight even if the hotkey is already associated with a coldkey. #[pallet::call_index(91)] #[pallet::weight(( - Weight::from_parts(27_150_000, 0).saturating_add(T::DbWeight::get().reads_writes(3, 3)), + Weight::from_parts(26_850_000, 0).saturating_add(T::DbWeight::get().reads_writes(3, 3)), DispatchClass::Normal, Pays::Yes ))] diff --git a/pallets/subtensor/src/staking/stake_utils.rs b/pallets/subtensor/src/staking/stake_utils.rs index cb2d46cef5..5e22cc09ac 100644 --- a/pallets/subtensor/src/staking/stake_utils.rs +++ b/pallets/subtensor/src/staking/stake_utils.rs @@ -1,7 +1,7 @@ use super::*; use safe_math::*; use share_pool::{SafeFloat, SharePool, SharePoolDataOperations}; -use sp_std::ops::Neg; +use sp_std::{collections::btree_map::BTreeMap, ops::Neg}; use substrate_fixed::types::{I64F64, I96F32, U96F32}; use subtensor_runtime_common::{AlphaBalance, AuthorshipInfo, NetUid, TaoBalance, Token}; use subtensor_swap_interface::{Order, SwapHandler, SwapResult}; @@ -69,6 +69,77 @@ impl Pallet { SubnetMovingPrice::::insert(netuid, new_moving); } + /// Gets the Median Subnet Alpha Price + pub fn get_median_subnet_alpha_price() -> U96F32 { + let default_price = U96F32::saturating_from_num(1_u64); + let zero_price = U96F32::saturating_from_num(0_u64); + let two = U96F32::saturating_from_num(2_u64); + + let mut price_counts: BTreeMap = BTreeMap::new(); + let mut total_prices: usize = 0; + + for (netuid, added) in NetworksAdded::::iter() { + if !added || netuid == NetUid::ROOT { + continue; + } + + let price = T::SwapInterface::current_alpha_price(netuid); + if price <= zero_price { + continue; + } + + total_prices = total_prices.saturating_add(1); + + if let Some(count) = price_counts.get_mut(&price) { + *count = count.saturating_add(1); + } else { + price_counts.insert(price, 1usize); + } + } + + if total_prices == 0 { + return default_price; + } + + let Some(last_index) = total_prices.checked_sub(1) else { + return default_price; + }; + let Some(lower_target) = last_index.checked_div(2) else { + return default_price; + }; + let Some(upper_target) = total_prices.checked_div(2) else { + return default_price; + }; + + let mut cumulative: usize = 0; + let mut lower_price: Option = None; + let mut upper_price: Option = None; + + for (price, count) in price_counts.into_iter() { + let next_cumulative = cumulative.saturating_add(count); + + if lower_price.is_none() && lower_target < next_cumulative { + lower_price = Some(price); + } + + if upper_price.is_none() && upper_target < next_cumulative { + upper_price = Some(price); + } + + if lower_price.is_some() && upper_price.is_some() { + break; + } + + cumulative = next_cumulative; + } + + match (lower_price, upper_price) { + (Some(_), Some(upper)) if lower_target == upper_target => upper, + (Some(lower), Some(upper)) => lower.saturating_add(upper).safe_div(two), + _ => default_price, + } + } + /// Retrieves the global global weight as a normalized value between 0 and 1. /// /// This function performs the following steps: diff --git a/pallets/subtensor/src/subnets/subnet.rs b/pallets/subtensor/src/subnets/subnet.rs index d2f445bbd4..9b066c391d 100644 --- a/pallets/subtensor/src/subnets/subnet.rs +++ b/pallets/subtensor/src/subnets/subnet.rs @@ -1,5 +1,7 @@ use super::*; +use safe_math::FixedExt; use sp_core::Get; +use substrate_fixed::types::U96F32; use subtensor_runtime_common::{NetUid, TaoBalance}; impl Pallet { /// Returns true if the subnetwork exists. @@ -183,31 +185,41 @@ impl Pallet { None => Self::get_next_netuid(), }; - // --- 11. Set initial and custom parameters for the network. + // --- 11. Snapshot the current median subnet alpha price before creating the new subnet. + let median_subnet_alpha_price = Self::get_median_subnet_alpha_price(); + + // --- 12. Set initial and custom parameters for the network. let default_tempo = DefaultTempo::::get(); Self::init_new_network(netuid_to_register, default_tempo); log::debug!("init_new_network: {netuid_to_register:?}"); - // --- 12. Add the caller to the neuron set. + // --- 13. Add the caller to the neuron set. Self::create_account_if_non_existent(&coldkey, hotkey); Self::append_neuron(netuid_to_register, hotkey, current_block); log::debug!("Appended neuron for netuid {netuid_to_register:?}, hotkey: {hotkey:?}"); - // --- 13. Set the mechanism. + // --- 14. Set the mechanism. SubnetMechanism::::insert(netuid_to_register, mechid); log::debug!("SubnetMechanism for netuid {netuid_to_register:?} set to: {mechid:?}"); - // --- 14. Set the creation terms. + // --- 15. Set the creation terms. NetworkRegisteredAt::::insert(netuid_to_register, current_block); - // --- 15. Set the symbol. + // --- 16. Set the symbol. let symbol = Self::get_next_available_symbol(netuid_to_register); TokenSymbol::::insert(netuid_to_register, symbol); - // The initial TAO is the locked amount - // Put initial TAO from lock into subnet TAO and produce numerically equal amount of Alpha. + // Seed the new subnet pool at a 1:1 reserve ratio. + // Separately, grant the subnet owner outstanding alpha based on the TAO they actually spent + // on registration converted by the current median subnet alpha price. let pool_initial_tao: TaoBalance = Self::get_network_min_lock(); let pool_initial_alpha: AlphaBalance = pool_initial_tao.to_u64().into(); + let owner_alpha_stake: AlphaBalance = + U96F32::saturating_from_num(actual_tao_lock_amount.to_u64()) + .safe_div(median_subnet_alpha_price) + .saturating_floor() + .saturating_to_num::() + .into(); let actual_tao_lock_amount_less_pool_tao = actual_tao_lock_amount.saturating_sub(pool_initial_tao); @@ -219,13 +231,22 @@ impl Pallet { SubnetLocked::::insert(netuid_to_register, actual_tao_lock_amount); SubnetTaoProvided::::insert(netuid_to_register, TaoBalance::ZERO); SubnetAlphaInProvided::::insert(netuid_to_register, AlphaBalance::ZERO); - SubnetAlphaOut::::insert(netuid_to_register, AlphaBalance::ZERO); + SubnetAlphaOut::::insert(netuid_to_register, owner_alpha_stake); SubnetVolume::::insert(netuid_to_register, 0u128); RAORecycledForRegistration::::insert( netuid_to_register, actual_tao_lock_amount_less_pool_tao, ); + if owner_alpha_stake > AlphaBalance::ZERO { + Self::increase_stake_for_hotkey_and_coldkey_on_subnet( + hotkey, + &coldkey, + netuid_to_register, + owner_alpha_stake, + ); + } + if actual_tao_lock_amount_less_pool_tao > TaoBalance::ZERO { Self::recycle_tao(actual_tao_lock_amount_less_pool_tao); } diff --git a/pallets/subtensor/src/tests/children.rs b/pallets/subtensor/src/tests/children.rs index ac8c2040bd..e438792703 100644 --- a/pallets/subtensor/src/tests/children.rs +++ b/pallets/subtensor/src/tests/children.rs @@ -2978,6 +2978,7 @@ fn test_parent_child_chain_emission() { let subnet_owner_coldkey = U256::from(1001); let subnet_owner_hotkey = U256::from(1002); let netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + remove_owner_registration_stake(netuid); SubtensorModule::set_ck_burn(0); Tempo::::insert(netuid, 1); @@ -3831,6 +3832,7 @@ fn test_do_set_child_as_sn_owner_not_enough_stake() { let proportion: u64 = 1000; let netuid = add_dynamic_network(&sn_owner_hotkey, &coldkey); + remove_owner_registration_stake(netuid); register_ok_neuron(netuid, child_hotkey, child_coldkey, 0); // Verify stake of sn_owner_hotkey is NOT enough diff --git a/pallets/subtensor/src/tests/claim_root.rs b/pallets/subtensor/src/tests/claim_root.rs index 0f628d6b86..e960bfff17 100644 --- a/pallets/subtensor/src/tests/claim_root.rs +++ b/pallets/subtensor/src/tests/claim_root.rs @@ -2,7 +2,8 @@ use crate::RootAlphaDividendsPerSubnet; use crate::tests::mock::{ - RuntimeOrigin, SubtensorModule, Test, add_dynamic_network, new_test_ext, run_to_block, + RuntimeOrigin, SubtensorModule, Test, add_dynamic_network, new_test_ext, + remove_owner_registration_stake, run_to_block, }; use crate::{ DefaultMinRootClaimAmount, Error, MAX_NUM_ROOT_CLAIMS, MAX_ROOT_CLAIM_THRESHOLD, NetworksAdded, @@ -44,6 +45,7 @@ fn test_claim_root_with_drain_emissions() { let hotkey = U256::from(1002); let coldkey = U256::from(1003); let netuid = add_dynamic_network(&hotkey, &owner_coldkey); + remove_owner_registration_stake(netuid); SubtensorModule::set_tao_weight(u64::MAX); // Set TAO weight to 1.0 @@ -765,6 +767,7 @@ fn test_claim_root_with_run_coinbase() { let hotkey = U256::from(1002); let coldkey = U256::from(1003); let netuid = add_dynamic_network(&hotkey, &owner_coldkey); + remove_owner_registration_stake(netuid); Tempo::::insert(netuid, 1); SubtensorModule::set_tao_weight(u64::MAX); // Set TAO weight to 1.0 @@ -883,6 +886,7 @@ fn test_claim_root_with_block_emissions() { let hotkey = U256::from(1002); let coldkey = U256::from(1003); let netuid = add_dynamic_network(&hotkey, &owner_coldkey); + remove_owner_registration_stake(netuid); Tempo::::insert(netuid, 1); SubtensorModule::set_tao_weight(u64::MAX); // Set TAO weight to 1.0 @@ -942,6 +946,7 @@ fn test_claim_root_with_block_emissions() { assert!(new_stake > 0); }); } + #[test] fn test_populate_staking_maps() { new_test_ext(1).execute_with(|| { @@ -1117,6 +1122,7 @@ fn test_claim_root_with_swap_coldkey() { let hotkey = U256::from(1002); let coldkey = U256::from(1003); let netuid = add_dynamic_network(&hotkey, &owner_coldkey); + remove_owner_registration_stake(netuid); SubtensorModule::set_tao_weight(u64::MAX); // Set TAO weight to 1.0 @@ -1195,6 +1201,7 @@ fn test_claim_root_with_swap_coldkey() { ); }); } + #[test] fn test_claim_root_with_swap_hotkey() { new_test_ext(1).execute_with(|| { @@ -1202,6 +1209,7 @@ fn test_claim_root_with_swap_hotkey() { let hotkey = U256::from(1002); let coldkey = U256::from(1003); let netuid = add_dynamic_network(&hotkey, &owner_coldkey); + remove_owner_registration_stake(netuid); SubtensorModule::set_tao_weight(u64::MAX); // Set TAO weight to 1.0 @@ -1461,6 +1469,7 @@ fn test_claim_root_with_unrelated_subnets() { let hotkey = U256::from(1002); let coldkey = U256::from(1003); let netuid = add_dynamic_network(&hotkey, &owner_coldkey); + remove_owner_registration_stake(netuid); SubtensorModule::set_tao_weight(u64::MAX); // Set TAO weight to 1.0 @@ -1639,6 +1648,7 @@ fn test_claim_root_with_keep_subnets() { let hotkey = U256::from(1002); let coldkey = U256::from(1003); let netuid = add_dynamic_network(&hotkey, &owner_coldkey); + remove_owner_registration_stake(netuid); SubtensorModule::set_tao_weight(u64::MAX); // Set TAO weight to 1.0 diff --git a/pallets/subtensor/src/tests/coinbase.rs b/pallets/subtensor/src/tests/coinbase.rs index a11cf317ff..4556b32693 100644 --- a/pallets/subtensor/src/tests/coinbase.rs +++ b/pallets/subtensor/src/tests/coinbase.rs @@ -662,6 +662,7 @@ fn test_pending_emission() { let hotkey = U256::from(1); let coldkey = U256::from(2); let netuid = add_dynamic_network(&hotkey, &coldkey); + remove_owner_registration_stake(netuid); Tempo::::insert(netuid, 1); FirstEmissionBlockNumber::::insert(netuid, 0); @@ -1825,6 +1826,7 @@ fn test_incentive_to_subnet_owner_is_burned() { Owner::::insert(other_hk, other_ck); let netuid = add_dynamic_network(&subnet_owner_hk, &subnet_owner_ck); + remove_owner_registration_stake(netuid); let pending_tao: u64 = 1_000_000_000; let pending_alpha = AlphaBalance::ZERO; // None to valis @@ -1875,6 +1877,7 @@ fn test_incentive_to_subnet_owners_hotkey_is_burned() { OwnedHotkeys::::insert(subnet_owner_ck, vec![subnet_owner_hk, other_hk]); let netuid = add_dynamic_network(&subnet_owner_hk, &subnet_owner_ck); + remove_owner_registration_stake(netuid); Uids::::insert(netuid, other_hk, 1); // Set the burn key limit to 2 @@ -1938,6 +1941,7 @@ fn test_burn_key_sorting() { ); let netuid = add_dynamic_network(&subnet_owner_hk, &subnet_owner_ck); + remove_owner_registration_stake(netuid); // Set block of registration and UIDs for other hotkeys // HK1 has block of registration 2 @@ -2388,6 +2392,7 @@ fn test_calculate_dividends_and_incentives_only_miners() { fn test_distribute_emission_no_miners_all_drained() { new_test_ext(1).execute_with(|| { let netuid = add_dynamic_network(&U256::from(1), &U256::from(2)); + remove_owner_registration_stake(netuid); let hotkey = U256::from(3); let coldkey = U256::from(4); let init_stake = 1; diff --git a/pallets/subtensor/src/tests/mock.rs b/pallets/subtensor/src/tests/mock.rs index 7dfe71c1b7..acf256695f 100644 --- a/pallets/subtensor/src/tests/mock.rs +++ b/pallets/subtensor/src/tests/mock.rs @@ -1038,3 +1038,43 @@ pub fn sf_to_u128(sf: &SafeFloat) -> u128 { pub fn sf_from_u64(val: u64) -> SafeFloat { SafeFloat::from(val) } + +#[allow(dead_code)] +pub fn remove_owner_registration_stake(netuid: NetUid) { + let owner_hotkey = SubnetOwnerHotkey::::get(netuid); + let owner_coldkey = SubnetOwner::::get(netuid); + + let owner_stake = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &owner_hotkey, + &owner_coldkey, + netuid, + ); + + if owner_stake.is_zero() { + return; + } + + let alpha_out_before = SubnetAlphaOut::::get(netuid); + + SubtensorModule::decrease_stake_for_hotkey_and_coldkey_on_subnet( + &owner_hotkey, + &owner_coldkey, + netuid, + owner_stake, + ); + + SubnetAlphaOut::::insert(netuid, alpha_out_before.saturating_sub(owner_stake)); + + assert_eq!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &owner_hotkey, + &owner_coldkey, + netuid, + ), + AlphaBalance::ZERO + ); + assert_eq!( + TotalHotkeyAlpha::::get(owner_hotkey, netuid), + AlphaBalance::ZERO + ); +} diff --git a/pallets/subtensor/src/tests/networks.rs b/pallets/subtensor/src/tests/networks.rs index 859c325c1f..7962429099 100644 --- a/pallets/subtensor/src/tests/networks.rs +++ b/pallets/subtensor/src/tests/networks.rs @@ -97,6 +97,7 @@ fn dissolve_single_alpha_out_staker_gets_all_tao() { let owner_cold = U256::from(10); let owner_hot = U256::from(20); let net = add_dynamic_network(&owner_hot, &owner_cold); + remove_owner_registration_stake(net); // 2. Single α-out staker let (s_hot, s_cold) = (U256::from(100), U256::from(200)); @@ -132,6 +133,7 @@ fn dissolve_two_stakers_pro_rata_distribution() { let oc = U256::from(50); let oh = U256::from(51); let net = add_dynamic_network(&oh, &oc); + remove_owner_registration_stake(net); // Mark this subnet as *legacy* so owner refund path is enabled. let reg_at = NetworkRegisteredAt::::get(net); @@ -212,6 +214,7 @@ fn dissolve_owner_cut_refund_logic() { let oc = U256::from(70); let oh = U256::from(71); let net = add_dynamic_network(&oh, &oc); + remove_owner_registration_stake(net); // Mark this subnet as *legacy* so owner refund path is enabled. let reg_at = NetworkRegisteredAt::::get(net); @@ -276,6 +279,7 @@ fn dissolve_zero_refund_when_emission_exceeds_lock() { let oc = U256::from(1_000); let oh = U256::from(2_000); let net = add_dynamic_network(&oh, &oc); + remove_owner_registration_stake(net); SubtensorModule::set_subnet_locked_balance(net, TaoBalance::from(1_000)); SubnetOwnerCut::::put(u16::MAX); // 100 % @@ -675,6 +679,7 @@ fn dissolve_rounding_remainder_distribution() { let oc = U256::from(61); let oh = U256::from(62); let net = add_dynamic_network(&oh, &oc); + remove_owner_registration_stake(net); let (s1h, s1c) = (U256::from(63), U256::from(64)); let (s2h, s2c) = (U256::from(65), U256::from(66)); @@ -707,6 +712,7 @@ fn dissolve_rounding_remainder_distribution() { assert!(!SubnetTAO::::contains_key(net)); }); } + #[test] fn destroy_alpha_out_multiple_stakers_pro_rata() { new_test_ext(0).execute_with(|| { @@ -714,6 +720,7 @@ fn destroy_alpha_out_multiple_stakers_pro_rata() { let owner_cold = U256::from(10); let owner_hot = U256::from(20); let netuid = add_dynamic_network(&owner_hot, &owner_cold); + remove_owner_registration_stake(netuid); // Mark this subnet as *legacy* so owner refund path is enabled. let reg_at = NetworkRegisteredAt::::get(netuid); @@ -812,6 +819,7 @@ fn destroy_alpha_out_many_stakers_complex_distribution() { let owner_cold = U256::from(1_000); let owner_hot = U256::from(2_000); let netuid = add_dynamic_network(&owner_hot, &owner_cold); + remove_owner_registration_stake(netuid); SubtensorModule::set_max_registrations_per_block(netuid, 1_000u16); SubtensorModule::set_target_registrations_per_interval(netuid, 1_000u16); @@ -954,6 +962,7 @@ fn destroy_alpha_out_refund_gating_by_registration_block() { let owner_cold = U256::from(10_000); let owner_hot = U256::from(20_000); let netuid = add_dynamic_network(&owner_hot, &owner_cold); + remove_owner_registration_stake(netuid); // Mark as *legacy*: registered_at < start_block let reg_at = NetworkRegisteredAt::::get(netuid); @@ -1022,6 +1031,7 @@ fn destroy_alpha_out_refund_gating_by_registration_block() { let owner_cold = U256::from(1_111); let owner_hot = U256::from(2_222); let netuid = add_dynamic_network(&owner_hot, &owner_cold); + remove_owner_registration_stake(netuid); // Explicitly set start_block <= registered_at to make it non‑legacy. let reg_at = NetworkRegisteredAt::::get(netuid); @@ -1068,6 +1078,7 @@ fn destroy_alpha_out_refund_gating_by_registration_block() { let owner_cold = U256::from(9_999); let owner_hot = U256::from(8_888); let netuid = add_dynamic_network(&owner_hot, &owner_cold); + remove_owner_registration_stake(netuid); // Mark as *legacy* let reg_at = NetworkRegisteredAt::::get(netuid); @@ -1822,6 +1833,7 @@ fn massive_dissolve_refund_and_reregistration_flow_is_lossless_and_cleans_state( let owner_hot = U256::from(10_000 + (i as u64)); let owner_cold = U256::from(20_000 + (i as u64)); let net = add_dynamic_network(&owner_hot, &owner_cold); + remove_owner_registration_stake(net); SubtensorModule::set_max_registrations_per_block(net, 1_000u16); SubtensorModule::set_target_registrations_per_interval(net, 1_000u16); Emission::::insert(net, Vec::::new()); @@ -2055,6 +2067,7 @@ fn massive_dissolve_refund_and_reregistration_flow_is_lossless_and_cleans_state( let new_owner_hot = U256::from(99_000); let new_owner_cold = U256::from(99_001); let net_new = add_dynamic_network(&new_owner_hot, &new_owner_cold); + remove_owner_registration_stake(net_new); SubtensorModule::set_max_registrations_per_block(net_new, 1_000u16); SubtensorModule::set_target_registrations_per_interval(net_new, 1_000u16); Emission::::insert(net_new, Vec::::new()); @@ -2218,3 +2231,394 @@ fn dissolve_clears_all_mechanism_scoped_maps_for_all_mechanisms() { assert!(!MechanismCountCurrent::::contains_key(net)); }); } + +fn owner_alpha_from_lock_and_price(lock_cost_u64: u64, price: U96F32) -> u64 { + let alpha = (U96F32::from_num(lock_cost_u64) + .checked_div(price) + .unwrap_or_default()) + .floor(); + + if alpha > U96F32::from_num(u64::MAX) { + u64::MAX + } else { + alpha.to_num::() + } +} + +#[test] +fn median_subnet_alpha_price_returns_one_when_no_eligible_subnet_prices() { + new_test_ext(0).execute_with(|| { + let one = U96F32::from_num(1u64); + + // Empty state. + assert_eq!(SubtensorModule::get_median_subnet_alpha_price(), one); + + // ROOT must be ignored. + NetworksAdded::::insert(NetUid::ROOT, true); + assert_eq!(SubtensorModule::get_median_subnet_alpha_price(), one); + + // Zero-priced subnet must be ignored. + let zero_cold = U256::from(101); + let zero_hot = U256::from(102); + let zero_netuid = add_dynamic_network(&zero_hot, &zero_cold); + setup_reserves(zero_netuid, TaoBalance::ZERO, AlphaBalance::from(100u64)); + assert_eq!( + ::SwapInterface::current_alpha_price(zero_netuid.into()), + U96F32::from_num(0u64) + ); + assert_eq!(SubtensorModule::get_median_subnet_alpha_price(), one); + + // added=false subnet must be ignored as well. + let hidden_cold = U256::from(103); + let hidden_hot = U256::from(104); + let hidden_netuid = add_dynamic_network(&hidden_hot, &hidden_cold); + setup_reserves( + hidden_netuid, + TaoBalance::from(900u64), + AlphaBalance::from(100u64), + ); + NetworksAdded::::insert(hidden_netuid, false); + + assert_eq!(SubtensorModule::get_median_subnet_alpha_price(), one); + }); +} + +#[test] +fn median_subnet_alpha_price_returns_middle_value_for_odd_unsorted_prices() { + new_test_ext(0).execute_with(|| { + let n1 = add_dynamic_network(&U256::from(201), &U256::from(200)); + let n2 = add_dynamic_network(&U256::from(203), &U256::from(202)); + let n3 = add_dynamic_network(&U256::from(205), &U256::from(204)); + + // Unsorted prices: 7, 2, 5 -> median should be 5. + setup_reserves(n1, TaoBalance::from(700u64), AlphaBalance::from(100u64)); + setup_reserves(n2, TaoBalance::from(200u64), AlphaBalance::from(100u64)); + setup_reserves(n3, TaoBalance::from(500u64), AlphaBalance::from(100u64)); + + assert_eq!( + ::SwapInterface::current_alpha_price(n1.into()), + U96F32::from_num(7u64) + ); + assert_eq!( + ::SwapInterface::current_alpha_price(n2.into()), + U96F32::from_num(2u64) + ); + assert_eq!( + ::SwapInterface::current_alpha_price(n3.into()), + U96F32::from_num(5u64) + ); + + assert_eq!( + SubtensorModule::get_median_subnet_alpha_price(), + U96F32::from_num(5u64) + ); + }); +} + +#[test] +fn median_subnet_alpha_price_averages_even_prices_and_ignores_root_zero_and_unadded() { + new_test_ext(0).execute_with(|| { + // If ROOT were included, its price would be 1 and change the median. + NetworksAdded::::insert(NetUid::ROOT, true); + + let n1 = add_dynamic_network(&U256::from(301), &U256::from(300)); // eligible, price 2 + let n2 = add_dynamic_network(&U256::from(303), &U256::from(302)); // hidden, price 4 + let n3 = add_dynamic_network(&U256::from(305), &U256::from(304)); // eligible, price 8 + let n4 = add_dynamic_network(&U256::from(307), &U256::from(306)); // zero, price 0 + + setup_reserves(n1, TaoBalance::from(200u64), AlphaBalance::from(100u64)); + setup_reserves(n2, TaoBalance::from(400u64), AlphaBalance::from(100u64)); + setup_reserves(n3, TaoBalance::from(800u64), AlphaBalance::from(100u64)); + setup_reserves(n4, TaoBalance::ZERO, AlphaBalance::from(100u64)); + + NetworksAdded::::insert(n2, false); + + assert_eq!( + ::SwapInterface::current_alpha_price(n1.into()), + U96F32::from_num(2u64) + ); + assert_eq!( + ::SwapInterface::current_alpha_price(n2.into()), + U96F32::from_num(4u64) + ); + assert_eq!( + ::SwapInterface::current_alpha_price(n3.into()), + U96F32::from_num(8u64) + ); + assert_eq!( + ::SwapInterface::current_alpha_price(n4.into()), + U96F32::from_num(0u64) + ); + + // Eligible prices are only {2, 8}, so the median is (2 + 8) / 2 = 5. + assert_eq!( + SubtensorModule::get_median_subnet_alpha_price(), + U96F32::from_num(5u64) + ); + }); +} + +#[test] +fn register_network_credits_owner_alpha_using_fallback_price_one_on_first_subnet() { + new_test_ext(1).execute_with(|| { + let new_cold = U256::from(1001); + let new_hot = U256::from(1002); + let new_netuid = SubtensorModule::get_next_netuid(); + + let lock_cost_u64: u64 = SubtensorModule::get_network_lock_cost().into(); + let pre_registration_median = SubtensorModule::get_median_subnet_alpha_price(); + let expected_owner_alpha_u64 = + owner_alpha_from_lock_and_price(lock_cost_u64, pre_registration_median); + let expected_owner_alpha: AlphaBalance = expected_owner_alpha_u64.into(); + + let pool_initial_tao = SubtensorModule::get_network_min_lock(); + let pool_initial_tao_u64 = pool_initial_tao.to_u64(); + let expected_pool_alpha: AlphaBalance = pool_initial_tao_u64.into(); + let expected_alpha_issuance: AlphaBalance = pool_initial_tao_u64 + .saturating_add(expected_owner_alpha_u64) + .into(); + let expected_recycled: TaoBalance = + lock_cost_u64.saturating_sub(pool_initial_tao_u64).into(); + + assert_eq!(pre_registration_median, U96F32::from_num(1u64)); + assert_eq!(expected_owner_alpha_u64, lock_cost_u64); + + SubtensorModule::add_balance_to_coldkey_account( + &new_cold, + lock_cost_u64.saturating_mul(2).into(), + ); + + assert_ok!(SubtensorModule::do_register_network( + RuntimeOrigin::signed(new_cold), + &new_hot, + 1, + None, + )); + + assert!(SubtensorModule::if_subnet_exist(new_netuid)); + assert_eq!(TotalNetworks::::get(), 1); + assert_eq!(SubnetOwner::::get(new_netuid), new_cold); + assert_eq!(SubnetOwnerHotkey::::get(new_netuid), new_hot); + assert_eq!( + SubtensorModule::get_subnet_locked_balance(new_netuid), + TaoBalance::from(lock_cost_u64) + ); + assert_eq!(SubnetTAO::::get(new_netuid), pool_initial_tao); + assert_eq!(SubnetAlphaIn::::get(new_netuid), expected_pool_alpha); + assert_eq!( + SubnetAlphaOut::::get(new_netuid), + expected_owner_alpha + ); + assert_eq!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &new_hot, &new_cold, new_netuid, + ), + expected_owner_alpha + ); + assert_eq!( + TotalHotkeyAlpha::::get(new_hot, new_netuid), + expected_owner_alpha + ); + assert_eq!( + SubtensorModule::get_alpha_issuance(new_netuid), + expected_alpha_issuance + ); + assert_eq!( + RAORecycledForRegistration::::get(new_netuid), + expected_recycled + ); + assert_eq!(SubnetTaoProvided::::get(new_netuid), TaoBalance::ZERO); + assert_eq!( + SubnetAlphaInProvided::::get(new_netuid), + AlphaBalance::ZERO + ); + System::assert_last_event(Event::NetworkAdded(new_netuid, 1).into()); + }); +} + +#[test] +fn register_network_credits_owner_alpha_from_even_median_and_excludes_new_subnet_price() { + new_test_ext(0).execute_with(|| { + let n1 = add_dynamic_network(&U256::from(1201), &U256::from(1200)); + let n2 = add_dynamic_network(&U256::from(1203), &U256::from(1202)); + + // Existing prices are {5, 2} -> pre-registration median is 3.5. + setup_reserves(n1, TaoBalance::from(500u64), AlphaBalance::from(100u64)); + setup_reserves(n2, TaoBalance::from(200u64), AlphaBalance::from(100u64)); + + let pre_registration_median = SubtensorModule::get_median_subnet_alpha_price(); + assert_eq!(pre_registration_median, U96F32::from_num(3.5)); + + let new_cold = U256::from(1300); + let new_hot = U256::from(1301); + let new_netuid = SubtensorModule::get_next_netuid(); + let lock_cost_u64: u64 = SubtensorModule::get_network_lock_cost().into(); + + let expected_owner_alpha_u64 = + owner_alpha_from_lock_and_price(lock_cost_u64, pre_registration_median); + let expected_owner_alpha: AlphaBalance = expected_owner_alpha_u64.into(); + + SubtensorModule::add_balance_to_coldkey_account( + &new_cold, + lock_cost_u64.saturating_mul(2).into(), + ); + + assert_ok!(SubtensorModule::do_register_network( + RuntimeOrigin::signed(new_cold), + &new_hot, + 1, + None, + )); + + // After registration, the new subnet exists and is seeded at price 1, + // so the live median becomes median({1, 2, 5}) = 2. + let new_subnet_price = + ::SwapInterface::current_alpha_price(new_netuid.into()); + let post_registration_median = SubtensorModule::get_median_subnet_alpha_price(); + let wrong_post_registration_owner_alpha_u64 = + owner_alpha_from_lock_and_price(lock_cost_u64, post_registration_median); + let wrong_post_registration_owner_alpha: AlphaBalance = + wrong_post_registration_owner_alpha_u64.into(); + + assert_eq!(new_subnet_price, U96F32::from_num(1u64)); + assert_eq!(post_registration_median, U96F32::from_num(2u64)); + assert_ne!(pre_registration_median, post_registration_median); + + // The registration flow must use the pre-registration median snapshot (3.5), + // not the post-init median (2). + assert_eq!( + SubnetAlphaOut::::get(new_netuid), + expected_owner_alpha + ); + assert_eq!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &new_hot, &new_cold, new_netuid, + ), + expected_owner_alpha + ); + assert_eq!( + TotalHotkeyAlpha::::get(new_hot, new_netuid), + expected_owner_alpha + ); + assert_ne!( + SubnetAlphaOut::::get(new_netuid), + wrong_post_registration_owner_alpha + ); + }); +} + +#[test] +fn register_network_fails_without_balance_and_does_not_write_owner_alpha_state() { + new_test_ext(0).execute_with(|| { + let cold = U256::from(2001); + let hot = U256::from(2002); + let would_be_netuid = SubtensorModule::get_next_netuid(); + + assert_eq!( + SubtensorModule::get_coldkey_balance(&cold), + TaoBalance::ZERO + ); + + assert_err!( + SubtensorModule::do_register_network(RuntimeOrigin::signed(cold), &hot, 1, None,), + Error::::CannotAffordLockCost + ); + + assert!(!SubtensorModule::if_subnet_exist(would_be_netuid)); + assert_eq!(TotalNetworks::::get(), 0); + assert_eq!( + SubnetAlphaIn::::get(would_be_netuid), + AlphaBalance::ZERO + ); + assert_eq!( + SubnetAlphaOut::::get(would_be_netuid), + AlphaBalance::ZERO + ); + assert_eq!( + SubtensorModule::get_subnet_locked_balance(would_be_netuid), + TaoBalance::ZERO + ); + assert_eq!( + RAORecycledForRegistration::::get(would_be_netuid), + TaoBalance::ZERO + ); + assert_eq!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hot, + &cold, + would_be_netuid, + ), + AlphaBalance::ZERO + ); + }); +} + +#[test] +fn register_network_non_associated_hotkey_does_not_withdraw_or_write_owner_alpha_state() { + new_test_ext(0).execute_with(|| { + let original_cold = U256::from(3001); + let shared_hot = U256::from(3002); + let existing_netuid = add_dynamic_network(&shared_hot, &original_cold); + + let original_stake_before = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &shared_hot, + &original_cold, + existing_netuid, + ); + let original_alpha_out_before = SubnetAlphaOut::::get(existing_netuid); + + let attacker_cold = U256::from(3003); + let would_be_netuid = SubtensorModule::get_next_netuid(); + let lock_cost_u64: u64 = SubtensorModule::get_network_lock_cost().into(); + + SubtensorModule::add_balance_to_coldkey_account(&attacker_cold, lock_cost_u64.into()); + let attacker_balance_before = SubtensorModule::get_coldkey_balance(&attacker_cold); + + assert_err!( + SubtensorModule::do_register_network( + RuntimeOrigin::signed(attacker_cold), + &shared_hot, + 1, + None, + ), + Error::::NonAssociatedColdKey + ); + + // Attacker was not charged. + assert_eq!( + SubtensorModule::get_coldkey_balance(&attacker_cold), + attacker_balance_before + ); + + // Existing owner state is untouched. + assert_eq!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &shared_hot, + &original_cold, + existing_netuid, + ), + original_stake_before + ); + assert_eq!( + SubnetAlphaOut::::get(existing_netuid), + original_alpha_out_before + ); + assert_eq!(SubnetOwner::::get(existing_netuid), original_cold); + + // No new subnet / owner-alpha state was written. + assert!(!SubtensorModule::if_subnet_exist(would_be_netuid)); + assert_eq!(TotalNetworks::::get(), 1); + assert_eq!( + SubnetAlphaOut::::get(would_be_netuid), + AlphaBalance::ZERO + ); + assert_eq!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &shared_hot, + &attacker_cold, + would_be_netuid, + ), + AlphaBalance::ZERO + ); + }); +} diff --git a/pallets/subtensor/src/tests/recycle_alpha.rs b/pallets/subtensor/src/tests/recycle_alpha.rs index 404967dc74..39fcfcaeaa 100644 --- a/pallets/subtensor/src/tests/recycle_alpha.rs +++ b/pallets/subtensor/src/tests/recycle_alpha.rs @@ -646,6 +646,7 @@ fn test_add_stake_burn_success() { let amount = DefaultMinStake::::get().to_u64() * 10; let netuid = add_dynamic_network(&hotkey_account_id, &coldkey_account_id); + remove_owner_registration_stake(netuid); mock::setup_reserves( netuid, @@ -710,6 +711,7 @@ fn test_add_stake_burn_with_limit_success() { // Add network let netuid = add_dynamic_network(&hotkey_account_id, &coldkey_account_id); + remove_owner_registration_stake(netuid); // Setup reserves with large liquidity to minimize slippage let tao_reserve = TaoBalance::from(1_000_000_000_000_u64); // 1000 TAO diff --git a/pallets/subtensor/src/tests/staking.rs b/pallets/subtensor/src/tests/staking.rs index b1e492e690..3a6967f814 100644 --- a/pallets/subtensor/src/tests/staking.rs +++ b/pallets/subtensor/src/tests/staking.rs @@ -49,6 +49,7 @@ fn test_add_stake_ok_no_emission() { //add network let netuid = add_dynamic_network(&hotkey_account_id, &coldkey_account_id); + remove_owner_registration_stake(netuid); mock::setup_reserves( netuid, @@ -652,6 +653,7 @@ fn test_remove_stake_no_enough_stake() { let hotkey_id = U256::from(54544); let amount = DefaultMinStake::::get().to_u64() * 10; let netuid = add_dynamic_network(&hotkey_id, &coldkey_id); + remove_owner_registration_stake(netuid); assert_eq!( SubtensorModule::get_total_stake_for_hotkey(&hotkey_id), @@ -1092,6 +1094,7 @@ fn test_staking_sets_div_variables() { let coldkey_account_id = U256::from(81337); let amount = 100_000_000_000_u64; let netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + remove_owner_registration_stake(netuid); let tempo = 10; Tempo::::insert(netuid, tempo); register_ok_neuron(netuid, hotkey_account_id, coldkey_account_id, 192213123); @@ -1266,7 +1269,9 @@ fn test_remove_stake_from_hotkey_account_registered_in_various_networks() { let coldkey_id = U256::from(5443433); let amount: u64 = 10_000; let netuid = add_dynamic_network(&hotkey_id, &coldkey_id); + remove_owner_registration_stake(netuid); let netuid_ex = add_dynamic_network(&hotkey_id, &coldkey_id); + remove_owner_registration_stake(netuid_ex); let neuron_uid = match SubtensorModule::get_uid_for_net_and_hotkey(netuid, &hotkey_id) { Ok(k) => k, @@ -1463,6 +1468,7 @@ fn test_has_enough_stake_yes() { let coldkey_id = U256::from(87989); let intial_amount = 10_000; let netuid = NetUid::from(add_dynamic_network(&hotkey_id, &coldkey_id)); + remove_owner_registration_stake(netuid); SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( &hotkey_id, &coldkey_id, @@ -1499,6 +1505,7 @@ fn test_has_enough_stake_no() { let coldkey_id = U256::from(87989); let intial_amount = 10_000; let netuid = add_dynamic_network(&hotkey_id, &coldkey_id); + remove_owner_registration_stake(netuid); SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( &hotkey_id, &coldkey_id, @@ -1538,6 +1545,7 @@ fn test_has_enough_stake_no_for_zero() { let coldkey_id = U256::from(87989); let intial_amount = 0; let netuid = add_dynamic_network(&hotkey_id, &coldkey_id); + remove_owner_registration_stake(netuid); assert_eq!( SubtensorModule::get_total_stake_for_hotkey(&hotkey_id), @@ -2368,6 +2376,7 @@ fn test_get_total_delegated_stake_exclude_owner_stake() { let delegator_stake = DefaultMinStake::::get().to_u64() * 10 - 1; let netuid = add_dynamic_network(&delegate_hotkey, &delegate_coldkey); + remove_owner_registration_stake(netuid); // Add owner stake SubtensorModule::add_balance_to_coldkey_account(&delegate_coldkey, owner_stake.into()); @@ -3743,6 +3752,7 @@ fn test_add_stake_limit_ok() { // add network let netuid = add_dynamic_network(&hotkey_account_id, &coldkey_account_id); + remove_owner_registration_stake(netuid); // Forse-set alpha in and tao reserve to make price equal 1.5 let tao_reserve = TaoBalance::from(150_000_000_000_u64); @@ -5056,6 +5066,7 @@ fn test_remove_stake_full_limit_ok() { // add network let netuid = add_dynamic_network(&hotkey_account_id, &coldkey_account_id); + remove_owner_registration_stake(netuid); // Give the neuron some stake to remove SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( @@ -5146,6 +5157,7 @@ fn test_remove_stake_full_limit_ok_with_no_limit_price() { // add network let netuid = add_dynamic_network(&hotkey_account_id, &coldkey_account_id); + remove_owner_registration_stake(netuid); // Give the neuron some stake to remove SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( @@ -5997,6 +6009,7 @@ fn test_sharepool_dataops_get_value_returns_zero_on_non_existing_v1() { let hotkey = U256::from(2); let netuid = add_dynamic_network(&hotkey, &coldkey); + remove_owner_registration_stake(netuid); let stake = 200_000_u64; // add to deprecated THS map, but no value in Alpha map @@ -6017,6 +6030,7 @@ fn test_sharepool_dataops_get_value_returns_zero_on_non_existing_v2() { let hotkey = U256::from(2); let netuid = add_dynamic_network(&hotkey, &coldkey); + remove_owner_registration_stake(netuid); let stake = 200_000_u64; // add to THSV2 map, but no value in AlphaV2 map @@ -6038,6 +6052,7 @@ fn test_sharepool_dataops_try_get_value_returns_err_on_non_existing_v1() { let hotkey = U256::from(2); let netuid = add_dynamic_network(&hotkey, &coldkey); + remove_owner_registration_stake(netuid); let stake = 200_000_u64; // add to deprecated THS map, but no value in Alpha map @@ -6058,6 +6073,7 @@ fn test_sharepool_dataops_try_get_value_returns_err_on_non_existing_v2() { let hotkey = U256::from(2); let netuid = add_dynamic_network(&hotkey, &coldkey); + remove_owner_registration_stake(netuid); let stake = 200_000_u64; // add to THSV2 map, but no value in AlphaV2 map diff --git a/pallets/subtensor/src/tests/swap_hotkey.rs b/pallets/subtensor/src/tests/swap_hotkey.rs index d0a1de3526..9806bb3790 100644 --- a/pallets/subtensor/src/tests/swap_hotkey.rs +++ b/pallets/subtensor/src/tests/swap_hotkey.rs @@ -75,6 +75,7 @@ fn test_swap_total_hotkey_stake() { //add network let netuid = add_dynamic_network(&old_hotkey, &coldkey); + remove_owner_registration_stake(netuid); let reserve = u64::from(amount) * 100; mock::setup_reserves(netuid, reserve.into(), reserve.into()); diff --git a/pallets/subtensor/src/tests/swap_hotkey_with_subnet.rs b/pallets/subtensor/src/tests/swap_hotkey_with_subnet.rs index eb310d1202..737cf1eaa6 100644 --- a/pallets/subtensor/src/tests/swap_hotkey_with_subnet.rs +++ b/pallets/subtensor/src/tests/swap_hotkey_with_subnet.rs @@ -80,6 +80,7 @@ fn test_swap_total_hotkey_stake() { //add network let netuid = add_dynamic_network(&old_hotkey, &coldkey); + remove_owner_registration_stake(netuid); // Give it some $$$ in his coldkey balance SubtensorModule::add_balance_to_coldkey_account(&coldkey, u64::MAX.into()); @@ -944,6 +945,7 @@ fn test_swap_stake_success() { let subnet_owner_coldkey = U256::from(1001); let subnet_owner_hotkey = U256::from(1002); let netuid = add_dynamic_network(&old_hotkey, &coldkey); + remove_owner_registration_stake(netuid); SubtensorModule::add_balance_to_coldkey_account(&coldkey, u64::MAX.into()); let amount = 10_000; let shares = U64F64::from_num(123456); @@ -2254,7 +2256,9 @@ fn test_revert_hotkey_swap_dividends() { let coldkey = U256::from(3); let netuid = add_dynamic_network(&hk1, &coldkey); + remove_owner_registration_stake(netuid); let netuid2 = add_dynamic_network(&hk1, &coldkey); + remove_owner_registration_stake(netuid2); SubtensorModule::add_balance_to_coldkey_account(&coldkey, u64::MAX.into()); let amount = 10_000; diff --git a/pallets/subtensor/src/tests/weights.rs b/pallets/subtensor/src/tests/weights.rs index 4539b13b9d..5237cca131 100644 --- a/pallets/subtensor/src/tests/weights.rs +++ b/pallets/subtensor/src/tests/weights.rs @@ -6870,6 +6870,7 @@ fn test_subnet_owner_can_validate_without_stake_or_manual_permit() { // Create a real dynamic subnet whose owner hotkey is `owner_hotkey`. let netuid = add_dynamic_network_disable_commit_reveal(&owner_hotkey, &owner_coldkey); + remove_owner_registration_stake(netuid); // Add one non-owner neuron with deterministic subnet stake. register_ok_neuron(netuid, other_hotkey, other_coldkey, 0);