diff --git a/chain-extensions/src/mock.rs b/chain-extensions/src/mock.rs index 46ea71fc39..075e6d205a 100644 --- a/chain-extensions/src/mock.rs +++ b/chain-extensions/src/mock.rs @@ -7,6 +7,7 @@ use core::num::NonZeroU64; use frame_support::dispatch::DispatchResult; +use frame_support::pallet_prelude::Zero; use frame_support::traits::{Contains, Everything, InherentBuilder, InsideBoth}; use frame_support::weights::Weight; use frame_support::weights::constants::RocksDbWeight; @@ -25,7 +26,9 @@ use sp_runtime::{ traits::{BlakeTwo256, Convert, IdentityLookup}, }; use sp_std::{cell::RefCell, cmp::Ordering, sync::OnceLock}; -use subtensor_runtime_common::{AlphaBalance, AuthorshipInfo, NetUid, TaoBalance}; +use subtensor_runtime_common::{ + AlphaBalance, AuthorshipInfo, NetUid, Saturating, TaoBalance, Token, +}; type Block = frame_system::mocking::MockBlock; @@ -654,27 +657,69 @@ pub fn register_ok_neuron( netuid: NetUid, hotkey_account_id: U256, coldkey_account_id: U256, - start_nonce: u64, + _start_nonce: u64, ) { - let block_number: u64 = SubtensorModule::get_current_block_as_u64(); - let (nonce, work): (u64, Vec) = SubtensorModule::create_work_for_block_number( - netuid, - block_number, - start_nonce, - &hotkey_account_id, - ); - let result = SubtensorModule::register( - <::RuntimeOrigin>::signed(hotkey_account_id), - netuid, - block_number, - nonce, - work, - hotkey_account_id, - coldkey_account_id, - ); - assert_ok!(result); + // Ensure reserves exist for swap/burn path, but do NOT clobber reserves if the test already set them. + let reserve: u64 = 1_000_000_000_000; + let tao_reserve = SubnetTAO::::get(netuid); + let alpha_reserve = SubnetAlphaIn::::get(netuid) + .saturating_add(SubnetAlphaInProvided::::get(netuid)); + + if tao_reserve.is_zero() && alpha_reserve.is_zero() { + setup_reserves(netuid, reserve.into(), reserve.into()); + } + + // Ensure coldkey has enough to pay the current burn AND is not fully drained to zero. + // This avoids ZeroBalanceAfterWithdrawn in burned_register. + let top_up_for_burn = |netuid: NetUid, cold: U256| { + let burn: TaoBalance = SubtensorModule::get_burn(netuid); + + // Make sure something remains after withdrawal even if ED is 0 in tests. + let ed: TaoBalance = ExistentialDeposit::get(); + let min_remaining: TaoBalance = ed.max(1.into()); + + // Small buffer for safety (fees / rounding / future changes). + let buffer: TaoBalance = 10.into(); + + let min_balance_needed: TaoBalance = + burn.saturating_add(min_remaining).saturating_add(buffer); + + let bal: TaoBalance = SubtensorModule::get_coldkey_balance(&cold); + if bal < min_balance_needed { + SubtensorModule::add_balance_to_coldkey_account(&cold, min_balance_needed - bal); + } + }; + + top_up_for_burn(netuid, coldkey_account_id); + + let origin = <::RuntimeOrigin>::signed(coldkey_account_id); + let result = SubtensorModule::burned_register(origin.clone(), netuid, hotkey_account_id); + + match result { + Ok(()) => { + // success + } + Err(e) + if e == Error::::TooManyRegistrationsThisInterval.into() + || e == Error::::NotEnoughBalanceToStake.into() + || e == Error::::ZeroBalanceAfterWithdrawn.into() => + { + // Re-top-up and retry once (burn can be state-dependent). + top_up_for_burn(netuid, coldkey_account_id); + + assert_ok!(SubtensorModule::burned_register( + origin, + netuid, + hotkey_account_id + )); + } + Err(e) => { + panic!("Expected Ok(_). Got Err({e:?})"); + } + } + log::info!( - "Register ok neuron: netuid: {netuid:?}, coldkey: {hotkey_account_id:?}, hotkey: {coldkey_account_id:?}" + "Register ok neuron: netuid: {netuid:?}, coldkey: {coldkey_account_id:?}, hotkey: {hotkey_account_id:?}" ); } diff --git a/pallets/admin-utils/src/lib.rs b/pallets/admin-utils/src/lib.rs index 7f882fcf9c..a8d8fab56e 100644 --- a/pallets/admin-utils/src/lib.rs +++ b/pallets/admin-utils/src/lib.rs @@ -90,6 +90,20 @@ pub mod pallet { /// Indicates if the Bonds Reset was enabled or disabled. enabled: bool, }, + /// Event emitted when the burn half-life parameter is set for a subnet. + BurnHalfLifeSet { + /// The network identifier. + netuid: NetUid, + /// The new burn half-life value. + burn_half_life: u16, + }, + /// Event emitted when the burn increase multiplier is set for a subnet. + BurnIncreaseMultSet { + /// The network identifier. + netuid: NetUid, + /// The new burn increase multiplier. + burn_increase_mult: u64, + }, } // Errors inform users that something went wrong. @@ -117,6 +131,8 @@ pub mod pallet { MaxAllowedUidsGreaterThanDefaultMaxAllowedUids, /// Bad parameter value InvalidValue, + /// Operation is not permitted on the root network. + NotPermittedOnRootSubnet, } /// Enum for specifying the type of precompile operation. #[derive( @@ -2134,6 +2150,67 @@ pub mod pallet { log::trace!("ColdkeySwapReannouncementDelaySet( duration: {duration:?} )"); Ok(()) } + /// Set BurnHalfLife for a subnet. + #[pallet::call_index(89)] + #[pallet::weight(( + Weight::from_parts(25_000_000, 0) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(1)), + DispatchClass::Operational, + Pays::Yes, + ))] + pub fn sudo_set_burn_half_life( + origin: OriginFor, + netuid: NetUid, + burn_half_life: u16, + ) -> DispatchResult { + ensure_root(origin)?; + + ensure!( + pallet_subtensor::Pallet::::if_subnet_exist(netuid), + Error::::SubnetDoesNotExist + ); + ensure!(!netuid.is_root(), Error::::NotPermittedOnRootSubnet); + ensure!(burn_half_life > 0, Error::::InvalidValue); + + pallet_subtensor::BurnHalfLife::::insert(netuid, burn_half_life); + Self::deposit_event(Event::BurnHalfLifeSet { + netuid, + burn_half_life, + }); + Ok(()) + } + + /// Set BurnIncreaseMult for a subnet. + #[pallet::call_index(90)] + #[pallet::weight(( + Weight::from_parts(25_000_000, 0) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(1)), + DispatchClass::Operational, + Pays::Yes, + ))] + pub fn sudo_set_burn_increase_mult( + origin: OriginFor, + netuid: NetUid, + burn_increase_mult: u64, + ) -> DispatchResult { + ensure_root(origin)?; + + ensure!( + pallet_subtensor::Pallet::::if_subnet_exist(netuid), + Error::::SubnetDoesNotExist + ); + ensure!(!netuid.is_root(), Error::::NotPermittedOnRootSubnet); + ensure!(burn_increase_mult >= 1, Error::::InvalidValue); + + pallet_subtensor::BurnIncreaseMult::::insert(netuid, burn_increase_mult); + Self::deposit_event(Event::BurnIncreaseMultSet { + netuid, + burn_increase_mult, + }); + Ok(()) + } } } diff --git a/pallets/admin-utils/src/tests/mock.rs b/pallets/admin-utils/src/tests/mock.rs index 35f8f6f784..f6c55897e5 100644 --- a/pallets/admin-utils/src/tests/mock.rs +++ b/pallets/admin-utils/src/tests/mock.rs @@ -511,27 +511,32 @@ pub fn register_ok_neuron( netuid: NetUid, hotkey_account_id: U256, coldkey_account_id: U256, - start_nonce: u64, + _start_nonce: u64, ) { - let block_number: u64 = SubtensorModule::get_current_block_as_u64(); - let (nonce, work): (u64, Vec) = SubtensorModule::create_work_for_block_number( - netuid, - block_number, - start_nonce, - &hotkey_account_id, - ); - let result = SubtensorModule::register( - <::RuntimeOrigin>::signed(hotkey_account_id), + // Ensure reserves exist for swap/burn path. + let reserve: u64 = 1_000_000_000_000; + setup_reserves(netuid, reserve.into(), reserve.into()); + + // Ensure coldkey has enough to pay the current burn. + let burn: TaoBalance = SubtensorModule::get_burn(netuid); + let burn_u64: TaoBalance = burn; + let bal = SubtensorModule::get_coldkey_balance(&coldkey_account_id); + + if bal < burn_u64 { + SubtensorModule::add_balance_to_coldkey_account( + &coldkey_account_id, + burn_u64 - bal + 10.into(), + ); + } + + let result = SubtensorModule::burned_register( + <::RuntimeOrigin>::signed(coldkey_account_id), netuid, - block_number, - nonce, - work, hotkey_account_id, - coldkey_account_id, ); assert_ok!(result); log::info!( - "Register ok neuron: netuid: {netuid:?}, coldkey: {hotkey_account_id:?}, hotkey: {coldkey_account_id:?}" + "Register ok neuron: netuid: {netuid:?}, coldkey: {coldkey_account_id:?}, hotkey: {hotkey_account_id:?}" ); } @@ -539,5 +544,27 @@ pub fn register_ok_neuron( pub fn add_network(netuid: NetUid, tempo: u16) { SubtensorModule::init_new_network(netuid, tempo); SubtensorModule::set_network_registration_allowed(netuid, true); - SubtensorModule::set_network_pow_registration_allowed(netuid, true); + + pallet_subtensor::FirstEmissionBlockNumber::::insert(netuid, 1); + pallet_subtensor::SubtokenEnabled::::insert(netuid, true); + + // make interval 1 block so tests can register by stepping 1 block. + pallet_subtensor::BurnHalfLife::::insert(netuid, 1); + pallet_subtensor::BurnIncreaseMult::::insert(netuid, 1); + pallet_subtensor::BurnLastHalvingBlock::::insert( + netuid, + SubtensorModule::get_current_block_as_u64(), + ); +} + +use subtensor_runtime_common::AlphaBalance; +pub(crate) fn setup_reserves(netuid: NetUid, tao: TaoBalance, alpha: AlphaBalance) { + pallet_subtensor::SubnetTAO::::set(netuid, tao); + pallet_subtensor::SubnetAlphaIn::::set(netuid, alpha); +} + +/// Convenience wrapper for tests that need to advance blocks incrementally. +pub fn step_block(n: u64) { + let current: u64 = frame_system::Pallet::::block_number().into(); + run_to_block(current + n); } diff --git a/pallets/admin-utils/src/tests/mod.rs b/pallets/admin-utils/src/tests/mod.rs index 7977c69c5f..fecf996ffd 100644 --- a/pallets/admin-utils/src/tests/mod.rs +++ b/pallets/admin-utils/src/tests/mod.rs @@ -1,30 +1,23 @@ -use frame_support::sp_runtime::DispatchError; +use crate::{Error, pallet::PrecompileEnable}; use frame_support::{ assert_err, assert_noop, assert_ok, dispatch::{DispatchClass, GetDispatchInfo, Pays}, - traits::Hooks, + sp_runtime::DispatchError, + traits::{Currency as _, Hooks}, }; use frame_system::Config; use pallet_subtensor::{ - Error as SubtensorError, MaxRegistrationsPerBlock, Rank, SubnetOwner, - TargetRegistrationsPerInterval, Tempo, WeightsVersionKeyRateLimit, *, -}; -// use pallet_subtensor::{migrations, Event}; -use pallet_subtensor::{ - Event, subnets::mechanism::MAX_MECHANISM_COUNT_PER_SUBNET, - utils::rate_limiting::TransactionType, + Error as SubtensorError, Event, MaxRegistrationsPerBlock, Rank, SubnetOwner, + TargetRegistrationsPerInterval, Tempo, WeightsVersionKeyRateLimit, + subnets::mechanism::MAX_MECHANISM_COUNT_PER_SUBNET, utils::rate_limiting::TransactionType, *, }; use sp_consensus_grandpa::AuthorityId as GrandpaId; use sp_core::{Get, Pair, U256, ed25519}; use substrate_fixed::types::I96F32; use subtensor_runtime_common::{MechId, NetUid, TaoBalance, Token}; - -use crate::Error; -use crate::pallet::PrecompileEnable; +pub mod mock; use mock::*; -pub(crate) mod mock; - #[test] fn test_sudo_set_default_take() { new_test_ext().execute_with(|| { @@ -488,9 +481,16 @@ fn test_sudo_set_max_allowed_uids() { MaxRegistrationsPerBlock::::insert(netuid, 256); TargetRegistrationsPerInterval::::insert(netuid, 256); - // Register some neurons for i in 0..=8 { - register_ok_neuron(netuid, U256::from(i * 1000), U256::from(i * 1000 + i), 0); + let hotkey = U256::from(i * 1000); + let coldkey = U256::from(i * 1000 + i); + + let funds: u64 = 1_000_000_000_000_000; // 1,000,000 TAO (in RAO) + let _ = Balances::deposit_creating(&coldkey, Balance::from(funds)); + let _ = Balances::deposit_creating(&hotkey, Balance::from(funds)); // defensive + + register_ok_neuron(netuid, hotkey, coldkey, 0); + step_block(1); } // Bad origin that is not root or subnet owner @@ -1699,6 +1699,12 @@ fn test_sets_a_lower_value_clears_small_nominations() { assert!(to_stake > nominator_min_required_stake_0); // Should stay when set assert!(to_stake < nominator_min_required_stake_1); // Should be removed when set + // ---- FIX: fund accounts so burn-based registration + staking doesn't fail. + let funds: u64 = 1_000_000_000_000_000; // 1,000,000 TAO (in RAO) + let _ = Balances::deposit_creating(&owner_coldkey, Balance::from(funds)); + let _ = Balances::deposit_creating(&staker_coldkey, Balance::from(funds)); + let _ = Balances::deposit_creating(&hotkey, Balance::from(funds)); // defensive + // Create network let netuid = NetUid::from(2); add_network(netuid, 10); @@ -1764,49 +1770,6 @@ fn test_sets_a_lower_value_clears_small_nominations() { }); } -// #[test] -// fn test_sudo_set_subnet_owner_hotkey() { -// new_test_ext().execute_with(|| { -// let netuid = NetUid::from(1); - -// let coldkey: U256 = U256::from(1); -// let hotkey: U256 = U256::from(2); -// let new_hotkey: U256 = U256::from(3); - -// let coldkey_origin = <::RuntimeOrigin>::signed(coldkey); -// let root = RuntimeOrigin::root(); -// let random_account = RuntimeOrigin::signed(U256::from(123456)); - -// pallet_subtensor::SubnetOwner::::insert(netuid, coldkey); -// pallet_subtensor::SubnetOwnerHotkey::::insert(netuid, hotkey); -// assert_eq!( -// pallet_subtensor::SubnetOwnerHotkey::::get(netuid), -// hotkey -// ); - -// assert_ok!(AdminUtils::sudo_set_subnet_owner_hotkey( -// coldkey_origin, -// netuid, -// new_hotkey -// )); - -// assert_eq!( -// pallet_subtensor::SubnetOwnerHotkey::::get(netuid), -// new_hotkey -// ); - -// assert_noop!( -// AdminUtils::sudo_set_subnet_owner_hotkey(random_account, netuid, new_hotkey), -// DispatchError::BadOrigin -// ); - -// assert_noop!( -// AdminUtils::sudo_set_subnet_owner_hotkey(root, netuid, new_hotkey), -// DispatchError::BadOrigin -// ); -// }); -// } - // cargo test --package pallet-admin-utils --lib -- tests::test_sudo_set_ema_halving --exact --show-output #[test] fn test_sudo_set_ema_halving() { @@ -2497,10 +2460,11 @@ fn test_trim_to_max_allowed_uids() { let netuid = NetUid::from(1); let sn_owner = U256::from(1); let sn_owner_hotkey1 = U256::from(2); - let sn_owner_hotkey2 = U256::from(3); + add_network(netuid, 10); SubnetOwner::::insert(netuid, sn_owner); SubnetOwnerHotkey::::insert(netuid, sn_owner_hotkey1); + MaxRegistrationsPerBlock::::insert(netuid, 256); TargetRegistrationsPerInterval::::insert(netuid, 256); ImmuneOwnerUidsLimit::::insert(netuid, 2); @@ -2510,38 +2474,42 @@ fn test_trim_to_max_allowed_uids() { let mechanism_count = MechId::from(4); MechanismCountCurrent::::insert(netuid, mechanism_count); - // Add some neurons - let max_n = 16; + // Add some neurons (fund accounts + step blocks between regs). + let max_n: u16 = 16; for i in 1..=max_n { - let n = i * 1000; - register_ok_neuron(netuid, U256::from(n), U256::from(n + i), 0); + let n: u64 = (i as u64) * 1000; + let hotkey = U256::from(n); + let coldkey = U256::from(n + i as u64); + + let funds: u64 = 1_000_000_000_000_000; // 1,000,000 TAO (in RAO) + let _ = Balances::deposit_creating(&coldkey, Balance::from(funds)); + let _ = Balances::deposit_creating(&hotkey, Balance::from(funds)); // defensive + + register_ok_neuron(netuid, hotkey, coldkey, 0); + step_block(1); } // Run some blocks to ensure stake weights are set and that we are past the immunity period // for all neurons - run_to_block((ImmunityPeriod::::get(netuid) + 1).into()); + let immunity_period: u64 = ImmunityPeriod::::get(netuid).into(); + let current_block: u64 = frame_system::Pallet::::block_number().into(); + run_to_block(current_block + immunity_period + 1); // Set some randomized values that we can keep track of let values = vec![ - 17u16, 42u16, 8u16, 56u16, 23u16, 91u16, 34u16, // owner owned - 77u16, // temporally immune - 12u16, 65u16, 3u16, 88u16, // owner owned - 29u16, 51u16, 74u16, // temporally immune - 39u16, + 17u16, 42u16, 8u16, 56u16, 23u16, 91u16, + 34u16, // uid 6 (34) will be forced-immune below + 77u16, 12u16, 65u16, 3u16, 88u16, 29u16, 51u16, 74u16, 39u16, ]; let bool_values = vec![ - false, false, false, true, false, true, true, // owner owned - true, // temporally immune - false, true, false, true, // owner owned - false, true, true, // temporally immune - false, + false, false, false, true, false, true, true, true, false, true, false, true, false, + true, true, false, ]; let alpha_values = values.iter().map(|&v| (v as u64).into()).collect(); let u64_values: Vec = values.iter().map(|&v| v as u64).collect(); Emission::::set(netuid, alpha_values); - // NOTE: `Rank`, `Trust`, and `PruningScores` are *not* trimmed anymore, - // but we can still populate them without asserting on them. + // NOTE: Rank/Trust/PruningScores are *not* trimmed anymore, but we can populate them. Rank::::insert(netuid, values.clone()); Trust::::insert(netuid, values.clone()); Consensus::::insert(netuid, values.clone()); @@ -2559,18 +2527,11 @@ fn test_trim_to_max_allowed_uids() { LastUpdate::::insert(netuid_index, u64_values.clone()); } - // We set some owner immune uids + // Make UID 6 temporally immune so it cannot be trimmed even though it's not a top-8 emitter. let now = frame_system::Pallet::::block_number(); BlockAtRegistration::::set(netuid, 6, now); - BlockAtRegistration::::set(netuid, 11, now); - // And some temporally immune uids - Keys::::insert(netuid, 7, sn_owner_hotkey1); - Uids::::insert(netuid, sn_owner_hotkey1, 7); - Keys::::insert(netuid, 14, sn_owner_hotkey2); - Uids::::insert(netuid, sn_owner_hotkey2, 14); - - // Set some evm addresses + // Set some evm addresses (include both kept + trimmed uids) AssociatedEvmAddress::::insert( netuid, 6, @@ -2593,7 +2554,6 @@ fn test_trim_to_max_allowed_uids() { ); // Populate Weights and Bonds storage items to test trimming - // Create weights and bonds that span across the range that will be trimmed for uid in 0..max_n { let mut weights = Vec::new(); let mut bonds = Vec::new(); @@ -2601,7 +2561,6 @@ fn test_trim_to_max_allowed_uids() { // Add connections to all other uids, including those that will be trimmed for target_uid in 0..max_n { if target_uid != uid { - // Use some non-zero values to make the test more meaningful let weight_value = (uid + target_uid) % 1000; let bond_value = (uid * target_uid) % 1000; weights.push((target_uid, weight_value)); @@ -2628,8 +2587,7 @@ fn test_trim_to_max_allowed_uids() { // Ensure the max allowed uids has been set correctly assert_eq!(MaxAllowedUids::::get(netuid), new_max_n); - // Ensure the emission has been trimmed correctly, keeping the highest emitters - // (after respecting immunity/owner exclusions) and compressed to the left + // Ensure the emission has been trimmed correctly and compressed to the left assert_eq!( Emission::::get(netuid), vec![ @@ -2786,11 +2744,19 @@ fn test_trim_to_max_allowed_uids_too_many_immune() { ImmuneOwnerUidsLimit::::insert(netuid, 2); MinAllowedUids::::set(netuid, 2); - // Add 5 neurons + // Add 5 neurons (fund + step blocks between regs) let max_n = 5; for i in 1..=max_n { let n = i * 1000; - register_ok_neuron(netuid, U256::from(n), U256::from(n + i), 0); + let hotkey = U256::from(n); + let coldkey = U256::from(n + i); + + let funds: u64 = 1_000_000_000_000_000; // 1,000,000 TAO (in RAO) + let _ = Balances::deposit_creating(&coldkey, Balance::from(funds)); + let _ = Balances::deposit_creating(&hotkey, Balance::from(funds)); // defensive + + register_ok_neuron(netuid, hotkey, coldkey, 0); + step_block(1); } // Run some blocks to ensure stake weights are set @@ -2874,9 +2840,16 @@ fn test_sudo_set_min_allowed_uids() { MaxRegistrationsPerBlock::::insert(netuid, 256); TargetRegistrationsPerInterval::::insert(netuid, 256); - // Register some neurons for i in 0..=16 { - register_ok_neuron(netuid, U256::from(i * 1000), U256::from(i * 1000 + i), 0); + let hotkey = U256::from(i * 1000); + let coldkey = U256::from(i * 1000 + i); + + let funds: u64 = 1_000_000_000_000_000; // 1,000,000 TAO (in RAO) + let _ = Balances::deposit_creating(&coldkey, Balance::from(funds)); + let _ = Balances::deposit_creating(&hotkey, Balance::from(funds)); // defensive + + register_ok_neuron(netuid, hotkey, coldkey, 0); + step_block(1); } // Normal case @@ -3001,6 +2974,11 @@ fn test_sudo_set_start_call_delay_permissions_and_zero_delay() { // Test 2: Create a subnet add_network(netuid, tempo); + + if pallet_subtensor::FirstEmissionBlockNumber::::get(netuid).is_some() { + pallet_subtensor::FirstEmissionBlockNumber::::remove(netuid); + } + assert_eq!( pallet_subtensor::FirstEmissionBlockNumber::::get(netuid), None, @@ -3041,7 +3019,6 @@ fn test_sudo_set_start_call_delay_permissions_and_zero_delay() { )); // Test 5: Try to start the subnet again - should be FAILED (first emission block already set) - let current_block = frame_system::Pallet::::block_number(); assert_err!( pallet_subtensor::Pallet::::start_call( <::RuntimeOrigin>::signed(coldkey_account_id), @@ -3052,7 +3029,7 @@ fn test_sudo_set_start_call_delay_permissions_and_zero_delay() { assert_eq!( pallet_subtensor::FirstEmissionBlockNumber::::get(netuid), - Some(current_block + 1), + Some(frame_system::Pallet::::block_number() + 1), "Emission should start at next block" ); diff --git a/pallets/subtensor/src/benchmarks.rs b/pallets/subtensor/src/benchmarks.rs index 98bb64c263..cbbc333db8 100644 --- a/pallets/subtensor/src/benchmarks.rs +++ b/pallets/subtensor/src/benchmarks.rs @@ -43,6 +43,20 @@ mod pallet_benchmarks { TaoBalance::from(1_000_000) } + /// This helper funds an account with: + /// - 2x burn fee + /// - 100x DefaultMinStake + fn fund_for_registration(netuid: NetUid, who: &T::AccountId) { + let burn = Subtensor::::get_burn(netuid); + let min_stake = DefaultMinStake::::get(); + + let deposit = burn + .saturating_mul(2.into()) + .saturating_add(min_stake.saturating_mul(100.into())); + + Subtensor::::add_balance_to_coldkey_account(who, deposit.into()); + } + #[benchmark] fn register() { let netuid = NetUid::from(1); @@ -51,8 +65,17 @@ mod pallet_benchmarks { let coldkey: T::AccountId = account("Test", 0, 2); Subtensor::::init_new_network(netuid, tempo); + Subtensor::::set_max_allowed_uids(netuid, 4096); + SubtokenEnabled::::insert(netuid, true); + Subtensor::::set_network_registration_allowed(netuid, true); Subtensor::::set_network_pow_registration_allowed(netuid, true); + + Subtensor::::set_burn(netuid, benchmark_registration_burn()); + seed_swap_reserves::(netuid); + + fund_for_registration::(netuid, &coldkey); + fund_for_registration::(netuid, &hotkey); Subtensor::::set_difficulty(netuid, 1); let block_number: u64 = Subtensor::::get_current_block_as_u64(); @@ -61,7 +84,7 @@ mod pallet_benchmarks { #[extrinsic_call] _( - RawOrigin::Signed(hotkey.clone()), + RawOrigin::Signed(coldkey.clone()), netuid, block_number, nonce, @@ -98,16 +121,19 @@ mod pallet_benchmarks { let coldkey: T::AccountId = account("Test", 0, seed); seed += 1; - Subtensor::::set_burn(netuid, benchmark_registration_burn()); - seed_swap_reserves::(netuid); - let amount_to_be_staked: u64 = 1_000_000; - Subtensor::::add_balance_to_coldkey_account(&coldkey, amount_to_be_staked.into()); + Subtensor::::set_burn(netuid, 1.into()); + + // Ensure enough for registration + minimum stake. + fund_for_registration::(netuid, &coldkey); - assert_ok!(Subtensor::::do_burned_registration( + RegistrationsThisInterval::::insert(netuid, 0); + + assert_ok!(Subtensor::::burned_register( RawOrigin::Signed(coldkey.clone()).into(), netuid, hotkey.clone() )); + let uid = Subtensor::::get_uid_for_net_and_hotkey(netuid, &hotkey).unwrap(); Subtensor::::set_validator_permit_for_uid(netuid, uid, true); @@ -144,7 +170,8 @@ mod pallet_benchmarks { seed_swap_reserves::(netuid); Subtensor::::add_balance_to_coldkey_account(&coldkey, total_stake.into()); - assert_ok!(Subtensor::::do_burned_registration( + + assert_ok!(Subtensor::::burned_register( RawOrigin::Signed(coldkey.clone()).into(), netuid, hotkey.clone() @@ -173,18 +200,19 @@ mod pallet_benchmarks { Subtensor::::init_new_network(netuid, 1); SubtokenEnabled::::insert(netuid, true); + Subtensor::::set_network_registration_allowed(netuid, true); Subtensor::::set_max_allowed_uids(netuid, 4096); - let reg_fee = Subtensor::::get_burn(netuid); - let deposit = reg_fee.saturating_mul(2.into()); + Subtensor::::set_burn(netuid, benchmark_registration_burn()); seed_swap_reserves::(netuid); - Subtensor::::add_balance_to_coldkey_account(&caller, deposit.into()); + fund_for_registration::(netuid, &caller); - assert_ok!(Subtensor::::do_burned_registration( + assert_ok!(Subtensor::::burned_register( RawOrigin::Signed(caller.clone()).into(), netuid, caller.clone() )); + Subtensor::::set_serving_rate_limit(netuid, 0); #[extrinsic_call] @@ -212,18 +240,19 @@ mod pallet_benchmarks { Subtensor::::init_new_network(netuid, 1); SubtokenEnabled::::insert(netuid, true); + Subtensor::::set_network_registration_allowed(netuid, true); Subtensor::::set_max_allowed_uids(netuid, 4096); - let reg_fee = Subtensor::::get_burn(netuid); - let deposit = reg_fee.saturating_mul(2.into()); + Subtensor::::set_burn(netuid, benchmark_registration_burn()); seed_swap_reserves::(netuid); - Subtensor::::add_balance_to_coldkey_account(&caller, deposit.into()); + fund_for_registration::(netuid, &caller); - assert_ok!(Subtensor::::do_burned_registration( + assert_ok!(Subtensor::::burned_register( RawOrigin::Signed(caller.clone()).into(), netuid, caller.clone() )); + Subtensor::::set_serving_rate_limit(netuid, 0); #[extrinsic_call] @@ -278,7 +307,7 @@ mod pallet_benchmarks { seed_swap_reserves::(netuid); Subtensor::::add_balance_to_coldkey_account(&coldkey, amount.into()); - assert_ok!(Subtensor::::do_burned_registration( + assert_ok!(Subtensor::::burned_register( RawOrigin::Signed(coldkey.clone()).into(), netuid, hotkey.clone() @@ -311,7 +340,6 @@ mod pallet_benchmarks { let weight_values: Vec = vec![10]; let hotkey: T::AccountId = account("hot", 0, 1); let coldkey: T::AccountId = account("cold", 0, 2); - let start_nonce: u64 = 300_000; let commit_hash: H256 = BlakeTwo256::hash_of(&( hotkey.clone(), @@ -322,26 +350,21 @@ mod pallet_benchmarks { )); Subtensor::::init_new_network(netuid, tempo); - Subtensor::::set_network_pow_registration_allowed(netuid, true); + Subtensor::::set_network_registration_allowed(netuid, true); Subtensor::::set_weights_set_rate_limit(netuid, 0); Subtensor::::set_difficulty(netuid, 1); + SubtokenEnabled::::insert(netuid, true); - let block_number: u64 = Subtensor::::get_current_block_as_u64(); - let (nonce, work) = Subtensor::::create_work_for_block_number( - netuid, - block_number, - start_nonce, - &hotkey, - ); - assert_ok!(Subtensor::::register( - RawOrigin::Signed(hotkey.clone()).into(), + Subtensor::::set_burn(netuid, benchmark_registration_burn()); + seed_swap_reserves::(netuid); + fund_for_registration::(netuid, &coldkey); + + assert_ok!(Subtensor::::burned_register( + RawOrigin::Signed(coldkey.clone()).into(), netuid, - block_number, - nonce, - work, - hotkey.clone(), - coldkey.clone() + hotkey.clone() )); + Subtensor::::set_validator_permit_for_uid(netuid, 0, true); Subtensor::::set_commit_reveal_weights_enabled(netuid, true); @@ -362,23 +385,19 @@ mod pallet_benchmarks { Subtensor::::init_new_network(netuid, tempo); Subtensor::::set_network_registration_allowed(netuid, true); - Subtensor::::set_network_pow_registration_allowed(netuid, true); + SubtokenEnabled::::insert(netuid, true); Subtensor::::set_weights_set_rate_limit(netuid, 0); Subtensor::::set_difficulty(netuid, 1); - let block_number: u64 = Subtensor::::get_current_block_as_u64(); - let (nonce, work) = - Subtensor::::create_work_for_block_number(netuid, block_number, 3, &hotkey); + Subtensor::::set_burn(netuid, benchmark_registration_burn()); + seed_swap_reserves::(netuid); + fund_for_registration::(netuid, &coldkey); - let _ = Subtensor::::register( - RawOrigin::Signed(hotkey.clone()).into(), + assert_ok!(Subtensor::::burned_register( + RawOrigin::Signed(coldkey.clone()).into(), netuid, - block_number, - nonce, - work.clone(), - hotkey.clone(), - coldkey.clone(), - ); + hotkey.clone() + )); Subtensor::::set_validator_permit_for_uid(netuid, 0, true); Subtensor::::set_commit_reveal_weights_enabled(netuid, true); @@ -435,12 +454,11 @@ mod pallet_benchmarks { Subtensor::::set_network_registration_allowed(netuid, true); SubtokenEnabled::::insert(netuid, true); - let reg_fee = Subtensor::::get_burn(netuid); - let deposit = reg_fee.saturating_mul(2.into()); + Subtensor::::set_burn(netuid, benchmark_registration_burn()); seed_swap_reserves::(netuid); - Subtensor::::add_balance_to_coldkey_account(&coldkey, deposit.into()); + fund_for_registration::(netuid, &coldkey); - assert_ok!(Subtensor::::do_burned_registration( + assert_ok!(Subtensor::::burned_register( RawOrigin::Signed(coldkey.clone()).into(), netuid, hotkey.clone() @@ -485,12 +503,17 @@ mod pallet_benchmarks { Subtensor::::init_new_network(netuid, 1); Subtensor::::set_network_registration_allowed(netuid, true); Subtensor::::set_network_pow_registration_allowed(netuid, true); + SubtokenEnabled::::insert(netuid, true); + Subtensor::::set_burn(netuid, benchmark_registration_burn()); + seed_swap_reserves::(netuid); + fund_for_registration::(netuid, &old_coldkey); Subtensor::::set_difficulty(netuid, 1); let block_number = Subtensor::::get_current_block_as_u64(); let (nonce, work) = Subtensor::::create_work_for_block_number(netuid, block_number, 3, &hotkey1); - let _ = Subtensor::::register( + + assert_ok!(Subtensor::::register( RawOrigin::Signed(old_coldkey.clone()).into(), netuid, block_number, @@ -498,7 +521,7 @@ mod pallet_benchmarks { work.clone(), hotkey1.clone(), old_coldkey.clone(), - ); + )); #[extrinsic_call] _(RawOrigin::Signed(old_coldkey), new_coldkey); @@ -509,29 +532,38 @@ mod pallet_benchmarks { let old_coldkey: T::AccountId = account("old_coldkey", 0, 0); let new_coldkey: T::AccountId = account("new_coldkey", 0, 0); let hotkey1: T::AccountId = account("hotkey1", 0, 0); + let netuid = NetUid::from(1); - let ed = ::ExistentialDeposit::get(); let swap_cost = Subtensor::::get_key_swap_cost(); - Subtensor::::add_balance_to_coldkey_account(&old_coldkey, swap_cost + ed); + let free_balance_old = swap_cost + TaoBalance::from(12_345_u64); - let netuid = NetUid::from(1); Subtensor::::init_new_network(netuid, 1); Subtensor::::set_network_registration_allowed(netuid, true); - Subtensor::::set_network_pow_registration_allowed(netuid, true); Subtensor::::set_difficulty(netuid, 1); + SubtokenEnabled::::insert(netuid, true); + Subtensor::::set_burn(netuid, benchmark_registration_burn()); + seed_swap_reserves::(netuid); - let block_number = Subtensor::::get_current_block_as_u64(); - let (nonce, work) = - Subtensor::::create_work_for_block_number(netuid, block_number, 3, &hotkey1); - let _ = Subtensor::::register( + fund_for_registration::(netuid, &old_coldkey); + + assert_ok!(Subtensor::::burned_register( RawOrigin::Signed(old_coldkey.clone()).into(), netuid, - block_number, - nonce, - work.clone(), hotkey1.clone(), - old_coldkey.clone(), - ); + )); + + Subtensor::::add_balance_to_coldkey_account(&old_coldkey, free_balance_old); + let name: Vec = b"The fourth Coolest Identity".to_vec(); + let identity = ChainIdentityV2 { + name, + url: vec![], + github_repo: vec![], + image: vec![], + discord: vec![], + description: vec![], + additional: vec![], + }; + IdentitiesV2::::insert(&old_coldkey, identity); #[extrinsic_call] _( @@ -578,24 +610,21 @@ mod pallet_benchmarks { Subtensor::::init_new_network(netuid, tempo); Subtensor::::set_network_registration_allowed(netuid, true); - Subtensor::::set_network_pow_registration_allowed(netuid, true); Subtensor::::set_commit_reveal_weights_enabled(netuid, true); Subtensor::::set_weights_set_rate_limit(netuid, 0); Subtensor::::set_difficulty(netuid, 1); + SubtokenEnabled::::insert(netuid, true); - let block_number: u64 = Subtensor::::get_current_block_as_u64(); - let (nonce, work) = - Subtensor::::create_work_for_block_number(netuid, block_number, 3, &hotkey); - let origin = T::RuntimeOrigin::from(RawOrigin::Signed(hotkey.clone())); - assert_ok!(Subtensor::::register( - origin.clone(), + Subtensor::::set_burn(netuid, benchmark_registration_burn()); + seed_swap_reserves::(netuid); + fund_for_registration::(netuid, &coldkey); + + assert_ok!(Subtensor::::burned_register( + RawOrigin::Signed(coldkey.clone()).into(), netuid, - block_number, - nonce, - work.clone(), - hotkey.clone(), - coldkey.clone() + hotkey.clone() )); + Subtensor::::set_validator_permit_for_uid(netuid, 0, true); let mut uids_list = Vec::new(); @@ -661,10 +690,9 @@ mod pallet_benchmarks { Subtensor::::set_network_registration_allowed(netuid, true); Subtensor::::set_burn(netuid, benchmark_registration_burn()); - let amount_to_be_staked = 1_000_000_000; - seed_swap_reserves::(netuid); - Subtensor::::add_balance_to_coldkey_account(&coldkey, amount_to_be_staked.into()); - assert_ok!(Subtensor::::do_burned_registration( + fund_for_registration::(netuid, &coldkey); + + assert_ok!(Subtensor::::burned_register( RawOrigin::Signed(coldkey.clone()).into(), netuid, hotkey.clone() @@ -705,10 +733,9 @@ mod pallet_benchmarks { Subtensor::::set_network_registration_allowed(netuid, true); Subtensor::::set_burn(netuid, benchmark_registration_burn()); - let amount_to_be_staked: u64 = 1_000_000_000; - seed_swap_reserves::(netuid); - Subtensor::::add_balance_to_coldkey_account(&coldkey, amount_to_be_staked.into()); - assert_ok!(Subtensor::::do_burned_registration( + fund_for_registration::(netuid, &coldkey); + + assert_ok!(Subtensor::::burned_register( RawOrigin::Signed(coldkey.clone()).into(), netuid, hotkey.clone() @@ -722,6 +749,7 @@ mod pallet_benchmarks { netuid, alpha_amount.into(), ); + assert_eq!( TotalHotkeyAlpha::::get(&hotkey, netuid), alpha_amount.into() @@ -747,16 +775,16 @@ mod pallet_benchmarks { Subtensor::::set_network_registration_allowed(netuid, true); Subtensor::::set_burn(netuid, benchmark_registration_burn()); - let amount_to_be_staked = 1_000_000; seed_swap_reserves::(netuid); - Subtensor::::add_balance_to_coldkey_account(&coldkey, amount_to_be_staked.into()); + fund_for_registration::(netuid, &coldkey); SubnetOwner::::set(netuid, coldkey.clone()); - assert_ok!(Subtensor::::do_burned_registration( + assert_ok!(Subtensor::::burned_register( RawOrigin::Signed(coldkey.clone()).into(), netuid, hotkey.clone() )); + assert_eq!(SubnetOwner::::get(netuid), coldkey.clone()); assert_eq!(FirstEmissionBlockNumber::::get(netuid), None); @@ -794,7 +822,7 @@ mod pallet_benchmarks { let alpha_in = AlphaBalance::from(100_000_000_000_000_u64); set_reserves::(netuid, tao_reserve, alpha_in); - assert_ok!(Subtensor::::do_burned_registration( + assert_ok!(Subtensor::::burned_register( RawOrigin::Signed(coldkey.clone()).into(), netuid, hotkey.clone() @@ -830,6 +858,7 @@ mod pallet_benchmarks { SubtokenEnabled::::insert(netuid, true); Subtensor::::init_new_network(netuid, 1); + Subtensor::::set_network_registration_allowed(netuid, true); let burn_fee = Subtensor::::get_burn(netuid); let stake_tao = DefaultMinStake::::get().saturating_mul(10.into()); @@ -859,7 +888,6 @@ mod pallet_benchmarks { Subtensor::::create_account_if_non_existent(&coldkey, &destination); - // Remove stake limit for benchmark StakingOperationRateLimiter::::remove((origin.clone(), coldkey.clone(), netuid)); #[extrinsic_call] @@ -879,7 +907,6 @@ mod pallet_benchmarks { let tempo: u16 = 1; let seed: u32 = 1; - // Set our total stake to 1000 TAO Subtensor::::increase_total_stake(1_000_000_000_000_u64.into()); Subtensor::::init_new_network(netuid, tempo); @@ -900,7 +927,7 @@ mod pallet_benchmarks { let wallet_bal = 1000000u32.into(); Subtensor::::add_balance_to_coldkey_account(&coldkey.clone(), wallet_bal); - assert_ok!(Subtensor::::do_burned_registration( + assert_ok!(Subtensor::::burned_register( RawOrigin::Signed(coldkey.clone()).into(), netuid, hotkey.clone() @@ -916,16 +943,14 @@ mod pallet_benchmarks { staked_amt )); - // Read current price and set limit price 0.01% lower, which is certainly getting hit - // by swapping 100 Alpha + let amount_unstaked = AlphaBalance::from(30_000_000_000_u64); + let current_price = T::SwapInterface::current_alpha_price(netuid); let limit = current_price - .saturating_mul(U96F32::saturating_from_num(999_900_000)) + .saturating_mul(U96F32::saturating_from_num(500_000_000)) .saturating_to_num::() .into(); - let amount_unstaked = AlphaBalance::from(100_000_000_000_u64); - // Remove stake limit for benchmark StakingOperationRateLimiter::::remove((hotkey.clone(), coldkey.clone(), netuid)); #[extrinsic_call] @@ -949,8 +974,11 @@ mod pallet_benchmarks { SubtokenEnabled::::insert(netuid1, true); Subtensor::::init_new_network(netuid1, 1); + Subtensor::::set_network_registration_allowed(netuid1, true); + SubtokenEnabled::::insert(netuid2, true); Subtensor::::init_new_network(netuid2, 1); + Subtensor::::set_network_registration_allowed(netuid2, true); let tao_reserve = TaoBalance::from(150_000_000_000_u64); let alpha_in = AlphaBalance::from(100_000_000_000_u64); @@ -971,7 +999,6 @@ mod pallet_benchmarks { netuid1, hot.clone() )); - assert_ok!(Subtensor::::burned_register( RawOrigin::Signed(coldkey.clone()).into(), netuid2, @@ -987,7 +1014,6 @@ mod pallet_benchmarks { allow )); - // Remove stake limit for benchmark StakingOperationRateLimiter::::remove((hot.clone(), coldkey.clone(), netuid1)); #[extrinsic_call] @@ -1011,6 +1037,7 @@ mod pallet_benchmarks { SubtokenEnabled::::insert(netuid, true); Subtensor::::init_new_network(netuid, 1); + Subtensor::::set_network_registration_allowed(netuid, true); let reg_fee = Subtensor::::get_burn(netuid); let stake_tao = DefaultMinStake::::get().saturating_mul(10.into()); @@ -1040,7 +1067,6 @@ mod pallet_benchmarks { Subtensor::::create_account_if_non_existent(&dest, &hot); - // Remove stake limit for benchmark StakingOperationRateLimiter::::remove((hot.clone(), coldkey.clone(), netuid)); #[extrinsic_call] @@ -1063,8 +1089,11 @@ mod pallet_benchmarks { SubtokenEnabled::::insert(netuid1, true); Subtensor::::init_new_network(netuid1, 1); + Subtensor::::set_network_registration_allowed(netuid1, true); + SubtokenEnabled::::insert(netuid2, true); Subtensor::::init_new_network(netuid2, 1); + Subtensor::::set_network_registration_allowed(netuid2, true); let reg_fee = Subtensor::::get_burn(netuid1); let stake_tao = DefaultMinStake::::get().saturating_mul(10.into()); @@ -1093,7 +1122,6 @@ mod pallet_benchmarks { let alpha_to_swap = Subtensor::::get_stake_for_hotkey_and_coldkey_on_subnet(&hot, &coldkey, netuid1); - // Remove stake limit for benchmark StakingOperationRateLimiter::::remove((hot.clone(), coldkey.clone(), netuid1)); #[extrinsic_call] @@ -1115,12 +1143,13 @@ mod pallet_benchmarks { let mut hashes: Vec = Vec::new(); Subtensor::::init_new_network(netuid, 1); - Subtensor::::set_network_pow_registration_allowed(netuid, true); + Subtensor::::set_network_registration_allowed(netuid, true); SubtokenEnabled::::insert(netuid, true); Subtensor::::set_weights_set_rate_limit(netuid, 0); - let reg_fee = Subtensor::::get_burn(netuid); - Subtensor::::add_balance_to_coldkey_account(&hotkey, reg_fee.saturating_mul(2.into())); + Subtensor::::set_burn(netuid, benchmark_registration_burn()); + seed_swap_reserves::(netuid); + fund_for_registration::(netuid, &hotkey); assert_ok!(Subtensor::::burned_register( RawOrigin::Signed(hotkey.clone()).into(), @@ -1159,8 +1188,12 @@ mod pallet_benchmarks { SubtokenEnabled::::insert(netuid, true); Subtensor::::set_commit_reveal_weights_enabled(netuid, false); - let reg_fee = Subtensor::::get_burn(netuid); - Subtensor::::add_balance_to_coldkey_account(&hotkey, reg_fee.saturating_mul(2.into())); + // Avoid any weights set rate-limit edge cases during benchmark setup. + Subtensor::::set_weights_set_rate_limit(netuid, 0); + + Subtensor::::set_burn(netuid, benchmark_registration_burn()); + seed_swap_reserves::(netuid); + fund_for_registration::(netuid, &hotkey); assert_ok!(Subtensor::::burned_register( RawOrigin::Signed(hotkey.clone()).into(), @@ -1168,6 +1201,9 @@ mod pallet_benchmarks { hotkey.clone() )); + // Batch set weights generally requires validator permit. + Subtensor::::set_validator_permit_for_uid(netuid, 0, true); + #[extrinsic_call] _( RawOrigin::Signed(hotkey.clone()), @@ -1241,9 +1277,9 @@ mod pallet_benchmarks { Subtensor::::set_network_registration_allowed(netuid, true); SubtokenEnabled::::insert(netuid, true); - let reg_fee = Subtensor::::get_burn(netuid); - let deposit = reg_fee.saturating_mul(2.into()); - Subtensor::::add_balance_to_coldkey_account(&caller, deposit.into()); + Subtensor::::set_burn(netuid, benchmark_registration_burn()); + seed_swap_reserves::(netuid); + fund_for_registration::(netuid, &caller); assert_ok!(Subtensor::::burned_register( RawOrigin::Signed(caller.clone()).into(), @@ -1268,6 +1304,7 @@ mod pallet_benchmarks { #[benchmark] fn set_identity() { + let netuid = NetUid::from(1); let coldkey: T::AccountId = whitelisted_caller(); let hotkey: T::AccountId = account("Alice", 0, 5); let name = b"n".to_vec(); @@ -1279,27 +1316,31 @@ mod pallet_benchmarks { let add = vec![]; Subtensor::::create_account_if_non_existent(&coldkey, &hotkey); - Subtensor::::init_new_network(1.into(), 1); + Subtensor::::init_new_network(netuid, 1); + Subtensor::::set_network_registration_allowed(netuid, true); + SubtokenEnabled::::insert(netuid, true); + Subtensor::::set_burn(netuid, benchmark_registration_burn()); + seed_swap_reserves::(netuid); + let deposit: u64 = 1_000_000_000u64.saturating_mul(2); Subtensor::::add_balance_to_coldkey_account(&coldkey, deposit.into()); - SubtokenEnabled::::insert(NetUid::from(1), true); assert_ok!(Subtensor::::burned_register( RawOrigin::Signed(coldkey.clone()).into(), - 1.into(), + netuid, hotkey.clone() )); #[extrinsic_call] _( RawOrigin::Signed(coldkey.clone()), - name.clone(), - url.clone(), - repo.clone(), - img.clone(), - disc.clone(), - descr.clone(), - add.clone(), + name, + url, + repo, + img, + disc, + descr, + add, ); } @@ -1323,14 +1364,14 @@ mod pallet_benchmarks { _( RawOrigin::Signed(coldkey.clone()), netuid, - name.clone(), - repo.clone(), - contact.clone(), - url.clone(), - disc.clone(), - descr.clone(), - logo_url.clone(), - add.clone(), + name, + repo, + contact, + url, + disc, + descr, + logo_url, + add, ); } @@ -1344,12 +1385,7 @@ mod pallet_benchmarks { Subtensor::::add_balance_to_coldkey_account(&coldkey, cost.into()); #[extrinsic_call] - _( - RawOrigin::Signed(coldkey.clone()), - old.clone(), - new.clone(), - None, - ); + _(RawOrigin::Signed(coldkey.clone()), old, new, None); } #[benchmark] @@ -1358,7 +1394,7 @@ mod pallet_benchmarks { let hot: T::AccountId = account("A", 0, 1); #[extrinsic_call] - _(RawOrigin::Signed(coldkey.clone()), hot.clone()); + _(RawOrigin::Signed(coldkey.clone()), hot); } #[benchmark] @@ -1368,7 +1404,7 @@ mod pallet_benchmarks { Subtensor::::create_account_if_non_existent(&coldkey, &hotkey); #[extrinsic_call] - _(RawOrigin::Signed(coldkey.clone()), hotkey.clone()); + _(RawOrigin::Signed(coldkey.clone()), hotkey); } #[benchmark] @@ -1396,7 +1432,7 @@ mod pallet_benchmarks { Subtensor::::add_balance_to_coldkey_account(&coldkey.clone(), 1000000u32.into()); - assert_ok!(Subtensor::::do_burned_registration( + assert_ok!(Subtensor::::burned_register( RawOrigin::Signed(coldkey.clone()).into(), netuid, hotkey.clone() @@ -1412,7 +1448,6 @@ mod pallet_benchmarks { staked_amt )); - // Remove stake limit for benchmark StakingOperationRateLimiter::::remove((hotkey.clone(), coldkey.clone(), netuid)); #[extrinsic_call] @@ -1425,7 +1460,6 @@ mod pallet_benchmarks { let tempo: u16 = 1; let seed: u32 = 1; - // Set our total stake to 1000 TAO Subtensor::::increase_total_stake(1_000_000_000_000_u64.into()); Subtensor::::init_new_network(netuid, tempo); @@ -1446,7 +1480,7 @@ mod pallet_benchmarks { let wallet_bal = 1000000u32.into(); Subtensor::::add_balance_to_coldkey_account(&coldkey.clone(), wallet_bal); - assert_ok!(Subtensor::::do_burned_registration( + assert_ok!(Subtensor::::burned_register( RawOrigin::Signed(coldkey.clone()).into(), netuid, hotkey.clone() @@ -1510,13 +1544,10 @@ mod pallet_benchmarks { }, ); - // Set the block to the end of the crowdloan frame_system::Pallet::::set_block_number(end); - // Simulate deposit pallet_crowdloan::Contributions::::insert(crowdloan_id, &beneficiary, deposit); - // Simulate k - 1 contributions, the deposit is already taken into account let contributors = k - 1; let amount = (cap - deposit) / TaoBalance::from(contributors); for i in 0..contributors { @@ -1524,7 +1555,6 @@ mod pallet_benchmarks { pallet_crowdloan::Contributions::::insert(crowdloan_id, contributor, amount); } - // Mark the crowdloan as finalizing pallet_crowdloan::CurrentCrowdloanId::::set(Some(0)); let emissions_share = Percent::from_percent(30); @@ -1535,24 +1565,21 @@ mod pallet_benchmarks { None, ); - // Ensure the lease was created let lease_id = 0; let lease = SubnetLeases::::get(lease_id).unwrap(); assert_eq!(lease.beneficiary, beneficiary); assert_eq!(lease.emissions_share, emissions_share); assert_eq!(lease.end_block, None); - // Ensure the subnet exists assert!(SubnetMechanism::::contains_key(lease.netuid)); } #[benchmark(extra)] fn terminate_lease(k: Linear<2, { T::MaxContributors::get() }>) { - // Setup a crowdloan let crowdloan_id = 0; let beneficiary: T::AccountId = whitelisted_caller(); let deposit = TaoBalance::from(20_000_000_000_u64); // 20 TAO - let now = frame_system::Pallet::::block_number(); // not really important here + let now = frame_system::Pallet::::block_number(); let crowdloan_end = now + T::MaximumBlockDuration::get(); let cap = TaoBalance::from(2_000_000_000_000_u64); // 2000 TAO @@ -1576,13 +1603,10 @@ mod pallet_benchmarks { }, ); - // Set the block to the end of the crowdloan frame_system::Pallet::::set_block_number(crowdloan_end); - // Simulate deposit pallet_crowdloan::Contributions::::insert(crowdloan_id, &beneficiary, deposit); - // Simulate k - 1 contributions, the deposit is already taken into account let contributors = k - 1; let amount = (cap - deposit) / TaoBalance::from(contributors); for i in 0..contributors { @@ -1590,10 +1614,8 @@ mod pallet_benchmarks { pallet_crowdloan::Contributions::::insert(crowdloan_id, contributor, amount); } - // Mark the crowdloan as finalizing pallet_crowdloan::CurrentCrowdloanId::::set(Some(0)); - // Register the leased network let emissions_share = Percent::from_percent(30); let lease_end = crowdloan_end + 1000u32.into(); assert_ok!(Subtensor::::register_leased_network( @@ -1602,13 +1624,13 @@ mod pallet_benchmarks { Some(lease_end), )); - // Set the block to the end of the lease frame_system::Pallet::::set_block_number(lease_end); let lease_id = 0; let lease = SubnetLeases::::get(0).unwrap(); let hotkey = account::("beneficiary_hotkey", 0, 0); Subtensor::::create_account_if_non_existent(&beneficiary, &hotkey); + #[extrinsic_call] _( RawOrigin::Signed(beneficiary.clone()), @@ -1616,11 +1638,9 @@ mod pallet_benchmarks { hotkey.clone(), ); - // Ensure the beneficiary is now the owner of the subnet assert_eq!(SubnetOwner::::get(lease.netuid), beneficiary); assert_eq!(SubnetOwnerHotkey::::get(lease.netuid), hotkey); - // Ensure everything has been cleaned up assert_eq!(SubnetLeases::::get(lease_id), None); assert!(!SubnetLeaseShares::::contains_prefix(lease_id)); assert!(!AccumulatedLeaseDividends::::contains_key(lease_id)); @@ -1651,14 +1671,12 @@ mod pallet_benchmarks { let round: u64 = 0; Subtensor::::init_new_network(netuid, 1); - Subtensor::::set_network_pow_registration_allowed(netuid, true); + Subtensor::::set_network_registration_allowed(netuid, true); SubtokenEnabled::::insert(netuid, true); - let reg_fee = Subtensor::::get_burn(netuid); - Subtensor::::add_balance_to_coldkey_account( - &hotkey, - reg_fee.saturating_mul(2.into()).into(), - ); + Subtensor::::set_burn(netuid, benchmark_registration_burn()); + seed_swap_reserves::(netuid); + fund_for_registration::(netuid, &hotkey); assert_ok!(Subtensor::::burned_register( RawOrigin::Signed(hotkey.clone()).into(), @@ -1666,6 +1684,9 @@ mod pallet_benchmarks { hotkey.clone() )); + // Ensure caller is allowed to commit (common requirement for weights ops). + Subtensor::::set_validator_permit_for_uid(netuid, 0, true); + Subtensor::::set_commit_reveal_weights_enabled(netuid, true); #[extrinsic_call] @@ -1683,11 +1704,13 @@ mod pallet_benchmarks { let coldkey: T::AccountId = whitelisted_caller(); let netuid = NetUid::from(1); let hotkey: T::AccountId = account("A", 0, 1); + SubtokenEnabled::::insert(netuid, true); Subtensor::::init_new_network(netuid, 1); - let amount = TaoBalance::from(900_000_000_000_u64); + Subtensor::::set_network_registration_allowed(netuid, true); - Subtensor::::add_balance_to_coldkey_account(&coldkey.clone(), amount); + let amount = 900_000_000_000u64; + Subtensor::::add_balance_to_coldkey_account(&coldkey.clone(), amount.into()); assert_ok!(Subtensor::::burned_register( RawOrigin::Signed(coldkey.clone()).into(), @@ -1698,6 +1721,7 @@ mod pallet_benchmarks { #[extrinsic_call] _(RawOrigin::Signed(coldkey.clone()), netuid, hotkey.clone()); } + #[benchmark] fn set_root_claim_type() { let coldkey: T::AccountId = whitelisted_caller(); @@ -1722,13 +1746,15 @@ mod pallet_benchmarks { )); SubtokenEnabled::::insert(netuid, true); - Subtensor::::set_network_pow_registration_allowed(netuid, true); + + Subtensor::::set_network_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 + Subtensor::::set_tao_weight(u64::MAX); let root_stake = 100_000_000u64; Subtensor::::increase_stake_for_hotkey_and_coldkey_on_subnet( @@ -1761,12 +1787,11 @@ mod pallet_benchmarks { 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); @@ -1824,7 +1849,7 @@ mod pallet_benchmarks { let alpha_in = AlphaBalance::from(100_000_000_000_u64); set_reserves::(netuid, tao_reserve, alpha_in); - assert_ok!(Subtensor::::do_burned_registration( + assert_ok!(Subtensor::::burned_register( RawOrigin::Signed(coldkey.clone()).into(), netuid, hotkey.clone() diff --git a/pallets/subtensor/src/coinbase/block_step.rs b/pallets/subtensor/src/coinbase/block_step.rs index a030607995..f742eadaa0 100644 --- a/pallets/subtensor/src/coinbase/block_step.rs +++ b/pallets/subtensor/src/coinbase/block_step.rs @@ -1,6 +1,5 @@ use super::*; -use safe_math::*; -use substrate_fixed::types::{U96F32, U110F18}; +use substrate_fixed::types::U96F32; use subtensor_runtime_common::{NetUid, TaoBalance}; impl Pallet { @@ -9,8 +8,9 @@ impl Pallet { let block_number: u64 = Self::get_current_block_as_u64(); let last_block_hash: T::Hash = >::parent_hash(); - // --- 1. Adjust difficulties. - Self::adjust_registration_terms_for_networks(); + // --- 1. Update registration burn prices. + Self::update_registration_prices_for_networks(); + // --- 2. Get the current coinbase emission. let block_emission: U96F32 = U96F32::saturating_from_num( Self::get_block_emission() @@ -46,229 +46,6 @@ impl Pallet { } } } - - /// Adjusts the network difficulties/burns of every active network. Resetting state parameters. - /// - pub fn adjust_registration_terms_for_networks() { - log::debug!("adjust_registration_terms_for_networks"); - - // --- 1. Iterate through each network. - for (netuid, _) in NetworksAdded::::iter() { - // --- 2. Pull counters for network difficulty. - let last_adjustment_block: u64 = Self::get_last_adjustment_block(netuid); - let adjustment_interval: u16 = Self::get_adjustment_interval(netuid); - let current_block: u64 = Self::get_current_block_as_u64(); - log::debug!( - "netuid: {netuid:?} last_adjustment_block: {last_adjustment_block:?} adjustment_interval: {adjustment_interval:?} current_block: {current_block:?}" - ); - - // --- 3. Check if we are at the adjustment interval for this network. - // If so, we need to adjust the registration difficulty based on target and actual registrations. - if current_block.saturating_sub(last_adjustment_block) >= adjustment_interval as u64 { - log::debug!("interval reached."); - - // --- 4. Get the current counters for this network w.r.t burn and difficulty values. - let current_burn = Self::get_burn(netuid); - let current_difficulty: u64 = Self::get_difficulty_as_u64(netuid); - let registrations_this_interval: u16 = - Self::get_registrations_this_interval(netuid); - let pow_registrations_this_interval: u16 = - Self::get_pow_registrations_this_interval(netuid); - let burn_registrations_this_interval: u16 = - Self::get_burn_registrations_this_interval(netuid); - let target_registrations_this_interval: u16 = - Self::get_target_registrations_per_interval(netuid); - // --- 5. Adjust burn + pow - // There are six cases to consider. A, B, C, D, E, F - if registrations_this_interval > target_registrations_this_interval { - #[allow(clippy::comparison_chain)] - if pow_registrations_this_interval > burn_registrations_this_interval { - // A. There are too many registrations this interval and most of them are pow registrations - // this triggers an increase in the pow difficulty. - // pow_difficulty ++ - Self::set_difficulty( - netuid, - Self::upgraded_difficulty( - netuid, - current_difficulty, - registrations_this_interval, - target_registrations_this_interval, - ), - ); - } else if pow_registrations_this_interval < burn_registrations_this_interval { - // B. There are too many registrations this interval and most of them are burn registrations - // this triggers an increase in the burn cost. - // burn_cost ++ - Self::set_burn( - netuid, - Self::upgraded_burn( - netuid, - current_burn, - registrations_this_interval, - target_registrations_this_interval, - ), - ); - } else { - // F. There are too many registrations this interval and the pow and burn registrations are equal - // this triggers an increase in the burn cost and pow difficulty - // burn_cost ++ - Self::set_burn( - netuid, - Self::upgraded_burn( - netuid, - current_burn, - registrations_this_interval, - target_registrations_this_interval, - ), - ); - // pow_difficulty ++ - Self::set_difficulty( - netuid, - Self::upgraded_difficulty( - netuid, - current_difficulty, - registrations_this_interval, - target_registrations_this_interval, - ), - ); - } - } else { - // Not enough registrations this interval. - #[allow(clippy::comparison_chain)] - if pow_registrations_this_interval > burn_registrations_this_interval { - // C. There are not enough registrations this interval and most of them are pow registrations - // this triggers a decrease in the burn cost - // burn_cost -- - Self::set_burn( - netuid, - Self::upgraded_burn( - netuid, - current_burn, - registrations_this_interval, - target_registrations_this_interval, - ), - ); - } else if pow_registrations_this_interval < burn_registrations_this_interval { - // D. There are not enough registrations this interval and most of them are burn registrations - // this triggers a decrease in the pow difficulty - // pow_difficulty -- - Self::set_difficulty( - netuid, - Self::upgraded_difficulty( - netuid, - current_difficulty, - registrations_this_interval, - target_registrations_this_interval, - ), - ); - } else { - // E. There are not enough registrations this interval and the pow and burn registrations are equal - // this triggers a decrease in the burn cost and pow difficulty - // burn_cost -- - Self::set_burn( - netuid, - Self::upgraded_burn( - netuid, - current_burn, - registrations_this_interval, - target_registrations_this_interval, - ), - ); - // pow_difficulty -- - Self::set_difficulty( - netuid, - Self::upgraded_difficulty( - netuid, - current_difficulty, - registrations_this_interval, - target_registrations_this_interval, - ), - ); - } - } - - // --- 6. Drain all counters for this network for this interval. - Self::set_last_adjustment_block(netuid, current_block); - Self::set_registrations_this_interval(netuid, 0); - Self::set_pow_registrations_this_interval(netuid, 0); - Self::set_burn_registrations_this_interval(netuid, 0); - } else { - log::debug!("interval not reached."); - } - - // --- 7. Drain block registrations for each network. Needed for registration rate limits. - Self::set_registrations_this_block(netuid, 0); - } - } - - /// Calculates the upgraded difficulty by multiplying the current difficulty by the ratio ( reg_actual + reg_target / reg_target + reg_target ) - /// We use U110F18 to avoid any overflows on u64. Also min_difficulty and max_difficulty bound the range. - /// - pub fn upgraded_difficulty( - netuid: NetUid, - current_difficulty: u64, - registrations_this_interval: u16, - target_registrations_per_interval: u16, - ) -> u64 { - let updated_difficulty: U110F18 = U110F18::saturating_from_num(current_difficulty) - .saturating_mul(U110F18::saturating_from_num( - registrations_this_interval.saturating_add(target_registrations_per_interval), - )) - .safe_div(U110F18::saturating_from_num( - target_registrations_per_interval.saturating_add(target_registrations_per_interval), - )); - let alpha: U110F18 = U110F18::saturating_from_num(Self::get_adjustment_alpha(netuid)) - .safe_div(U110F18::saturating_from_num(u64::MAX)); - let next_value: U110F18 = alpha - .saturating_mul(U110F18::saturating_from_num(current_difficulty)) - .saturating_add( - U110F18::saturating_from_num(1.0) - .saturating_sub(alpha) - .saturating_mul(updated_difficulty), - ); - if next_value >= U110F18::saturating_from_num(Self::get_max_difficulty(netuid)) { - Self::get_max_difficulty(netuid) - } else if next_value <= U110F18::saturating_from_num(Self::get_min_difficulty(netuid)) { - Self::get_min_difficulty(netuid) - } else { - next_value.saturating_to_num::() - } - } - - /// Calculates the upgraded burn by multiplying the current burn by the ratio ( reg_actual + reg_target / reg_target + reg_target ) - /// We use U110F18 to avoid any overflows on u64. Also min_burn and max_burn bound the range. - /// - pub fn upgraded_burn( - netuid: NetUid, - current_burn: TaoBalance, - registrations_this_interval: u16, - target_registrations_per_interval: u16, - ) -> TaoBalance { - let updated_burn: U110F18 = U110F18::saturating_from_num(current_burn) - .saturating_mul(U110F18::saturating_from_num( - registrations_this_interval.saturating_add(target_registrations_per_interval), - )) - .safe_div(U110F18::saturating_from_num( - target_registrations_per_interval.saturating_add(target_registrations_per_interval), - )); - let alpha: U110F18 = U110F18::saturating_from_num(Self::get_adjustment_alpha(netuid)) - .safe_div(U110F18::saturating_from_num(u64::MAX)); - let next_value: U110F18 = alpha - .saturating_mul(U110F18::saturating_from_num(current_burn)) - .saturating_add( - U110F18::saturating_from_num(1.0) - .saturating_sub(alpha) - .saturating_mul(updated_burn), - ); - if next_value >= U110F18::saturating_from_num(Self::get_max_burn(netuid)) { - Self::get_max_burn(netuid) - } else if next_value <= U110F18::saturating_from_num(Self::get_min_burn(netuid)) { - Self::get_min_burn(netuid) - } else { - next_value.saturating_to_num::().into() - } - } - pub fn update_moving_prices() { let subnets_to_emit_to: Vec = Self::get_subnets_to_emit_to(&Self::get_all_subnet_netuids()); diff --git a/pallets/subtensor/src/coinbase/root.rs b/pallets/subtensor/src/coinbase/root.rs index e2714fba1b..3041b317a7 100644 --- a/pallets/subtensor/src/coinbase/root.rs +++ b/pallets/subtensor/src/coinbase/root.rs @@ -330,7 +330,6 @@ impl Pallet { AlphaSigmoidSteepness::::remove(netuid); MaxAllowedValidators::::remove(netuid); - AdjustmentInterval::::remove(netuid); BondsMovingAverage::::remove(netuid); BondsPenalty::::remove(netuid); BondsResetOn::::remove(netuid); @@ -338,9 +337,12 @@ impl Pallet { ValidatorPruneLen::::remove(netuid); ScalingLawPower::::remove(netuid); TargetRegistrationsPerInterval::::remove(netuid); - AdjustmentAlpha::::remove(netuid); CommitRevealWeightsEnabled::::remove(netuid); + BurnHalfLife::::remove(netuid); + BurnIncreaseMult::::remove(netuid); + BurnLastHalvingBlock::::remove(netuid); + Burn::::remove(netuid); MinBurn::::remove(netuid); MaxBurn::::remove(netuid); diff --git a/pallets/subtensor/src/extensions/subtensor.rs b/pallets/subtensor/src/extensions/subtensor.rs index a5640e1736..7455dbb78a 100644 --- a/pallets/subtensor/src/extensions/subtensor.rs +++ b/pallets/subtensor/src/extensions/subtensor.rs @@ -221,19 +221,6 @@ where Err(CustomTransactionError::StakeAmountTooLow.into()) } } - Some(Call::register { netuid, .. } | Call::burned_register { netuid, .. }) => { - let registrations_this_interval = - Pallet::::get_registrations_this_interval(*netuid); - let max_registrations_per_interval = - Pallet::::get_target_registrations_per_interval(*netuid); - if registrations_this_interval >= (max_registrations_per_interval.saturating_mul(3)) - { - // If the registration limit for the interval is exceeded, reject the transaction - return Err(CustomTransactionError::RateLimitExceeded.into()); - } - - Ok((Default::default(), (), origin)) - } Some(Call::serve_axon { netuid, version, diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index f27019989a..0b8d05b5a2 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -345,6 +345,24 @@ pub mod pallet { }, } + /// Default burn half-life (in blocks) for subnet registration price decay. + #[pallet::type_value] + pub fn DefaultBurnHalfLife() -> u16 { + 360 + } + + /// Default multiplier applied to the burn price after a successful registration. + #[pallet::type_value] + pub fn DefaultBurnIncreaseMult() -> u64 { + 2 + } + + /// Default block number used as the initial burn halving anchor. + #[pallet::type_value] + pub fn DefaultBurnLastHalvingBlock() -> u64 { + 0 + } + /// 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. @@ -2401,6 +2419,21 @@ pub mod pallet { pub type MechanismEmissionSplit = StorageMap<_, Twox64Concat, NetUid, Vec, OptionQuery>; + /// --- MAP ( netuid ) --> BurnHalfLife (blocks) + #[pallet::storage] + pub type BurnHalfLife = + StorageMap<_, Identity, NetUid, u16, ValueQuery, DefaultBurnHalfLife>; + + /// --- MAP ( netuid ) --> BurnIncreaseMult + #[pallet::storage] + pub type BurnIncreaseMult = + StorageMap<_, Identity, NetUid, u64, ValueQuery, DefaultBurnIncreaseMult>; + + /// --- MAP ( netuid ) --> last block at which we applied halving + interval reset + #[pallet::storage] + pub type BurnLastHalvingBlock = + StorageMap<_, Identity, NetUid, u64, ValueQuery, DefaultBurnLastHalvingBlock>; + /// ================== /// ==== Genesis ===== /// ================== diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index 93803cfc13..34dd69f718 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -1014,19 +1014,19 @@ mod dispatches { /// - The seal is incorrect. /// #[pallet::call_index(6)] - #[pallet::weight((Weight::from_parts(197_900_000, 0) - .saturating_add(T::DbWeight::get().reads(24_u64)) - .saturating_add(T::DbWeight::get().writes(20_u64)), DispatchClass::Normal, Pays::Yes))] + #[pallet::weight((Weight::from_parts(361_200_000, 0) + .saturating_add(T::DbWeight::get().reads(44_u64)) + .saturating_add(T::DbWeight::get().writes(38_u64)), DispatchClass::Normal, Pays::Yes))] pub fn register( origin: OriginFor, netuid: NetUid, - block_number: u64, - nonce: u64, - work: Vec, + _block_number: u64, + _nonce: u64, + _work: Vec, hotkey: T::AccountId, - coldkey: T::AccountId, + _coldkey: T::AccountId, ) -> DispatchResult { - Self::do_registration(origin, netuid, block_number, nonce, work, hotkey, coldkey) + Self::do_register(origin, netuid, hotkey) } /// Register the hotkey to root network @@ -1041,14 +1041,14 @@ mod dispatches { /// User register a new subnetwork via burning token #[pallet::call_index(7)] #[pallet::weight((Weight::from_parts(354_200_000, 0) - .saturating_add(T::DbWeight::get().reads(47_u64)) - .saturating_add(T::DbWeight::get().writes(39_u64)), DispatchClass::Normal, Pays::Yes))] + .saturating_add(T::DbWeight::get().reads(44_u64)) + .saturating_add(T::DbWeight::get().writes(38_u64)), DispatchClass::Normal, Pays::Yes))] pub fn burned_register( origin: OriginFor, netuid: NetUid, hotkey: T::AccountId, ) -> DispatchResult { - Self::do_burned_registration(origin, netuid, hotkey) + Self::do_register(origin, netuid, hotkey) } /// The extrinsic for user to change its hotkey in subnet or all subnets. @@ -1070,8 +1070,8 @@ mod dispatches { /// Only callable by root as it doesn't require an announcement and can be used to swap any coldkey. #[pallet::call_index(71)] #[pallet::weight(Weight::from_parts(161_700_000, 0) - .saturating_add(T::DbWeight::get().reads(17_u64)) - .saturating_add(T::DbWeight::get().writes(10_u64)))] + .saturating_add(T::DbWeight::get().reads(21_u64)) + .saturating_add(T::DbWeight::get().writes(17_u64)))] pub fn swap_coldkey( origin: OriginFor, old_coldkey: T::AccountId, @@ -2391,9 +2391,9 @@ mod dispatches { /// The `ColdkeySwapped` event is emitted on successful swap. #[pallet::call_index(126)] #[pallet::weight( - Weight::from_parts(110_700_000, 0) - .saturating_add(T::DbWeight::get().reads(16_u64)) - .saturating_add(T::DbWeight::get().writes(6_u64)) + Weight::from_parts(185_600_000, 0) + .saturating_add(T::DbWeight::get().reads(21_u64)) + .saturating_add(T::DbWeight::get().writes(13_u64)) )] pub fn swap_coldkey_announced( origin: OriginFor, @@ -2569,5 +2569,26 @@ mod dispatches { ) -> DispatchResult { Self::do_add_stake_burn(origin, hotkey, netuid, amount, limit) } + + /// User register a new subnetwork via burning token, but only if the + /// on-chain burn price for this block is <= `limit_price`. + /// + /// `limit_price` is expressed in the same TaoCurrency/u64 units as `Burn`. + #[pallet::call_index(133)] + #[pallet::weight(( + Weight::from_parts(354_200_000, 0) + .saturating_add(T::DbWeight::get().reads(47_u64)) + .saturating_add(T::DbWeight::get().writes(40_u64)), + DispatchClass::Normal, + Pays::Yes + ))] + pub fn register_limit( + origin: OriginFor, + netuid: NetUid, + hotkey: T::AccountId, + limit_price: u64, + ) -> DispatchResult { + Self::do_register_limit(origin, netuid, hotkey, limit_price) + } } } diff --git a/pallets/subtensor/src/macros/errors.rs b/pallets/subtensor/src/macros/errors.rs index e6c3aa5e78..e6cd1fef56 100644 --- a/pallets/subtensor/src/macros/errors.rs +++ b/pallets/subtensor/src/macros/errors.rs @@ -286,5 +286,7 @@ mod errors { ColdkeySwapAnnounced, /// A coldkey swap for this account is under dispute. ColdkeySwapDisputed, + /// Registration Price Limit Exceeded + RegistrationPriceLimitExceeded, } } diff --git a/pallets/subtensor/src/macros/events.rs b/pallets/subtensor/src/macros/events.rs index 4363eb3f35..dfd3d997b9 100644 --- a/pallets/subtensor/src/macros/events.rs +++ b/pallets/subtensor/src/macros/events.rs @@ -528,5 +528,10 @@ mod events { /// Alpha burned alpha: AlphaBalance, }, + /// Burn Half Life Set for Neuron Registration. + BurnHalfLifeSet(NetUid, u16), + + /// Burn Increase Multiplier Set for Neuron Registration. + BurnIncreaseMultSet(NetUid, u64), } } diff --git a/pallets/subtensor/src/macros/hooks.rs b/pallets/subtensor/src/macros/hooks.rs index 899e8d32f2..135ea97c6b 100644 --- a/pallets/subtensor/src/macros/hooks.rs +++ b/pallets/subtensor/src/macros/hooks.rs @@ -166,7 +166,9 @@ mod hooks { // Fix staking hot keys .saturating_add(migrations::migrate_fix_staking_hot_keys::migrate_fix_staking_hot_keys::()) // Migrate coldkey swap scheduled to announcements - .saturating_add(migrations::migrate_coldkey_swap_scheduled_to_announcements::migrate_coldkey_swap_scheduled_to_announcements::()); + .saturating_add(migrations::migrate_coldkey_swap_scheduled_to_announcements::migrate_coldkey_swap_scheduled_to_announcements::()) + // Migration for new Neuron Registration + .saturating_add(migrations::migrate_clear_deprecated_registration_maps::migrate_clear_deprecated_registration_maps::()); weight } diff --git a/pallets/subtensor/src/migrations/migrate_clear_deprecated_registration_maps.rs b/pallets/subtensor/src/migrations/migrate_clear_deprecated_registration_maps.rs new file mode 100644 index 0000000000..eef77f35f8 --- /dev/null +++ b/pallets/subtensor/src/migrations/migrate_clear_deprecated_registration_maps.rs @@ -0,0 +1,160 @@ +use super::*; +use crate::AccountIdOf; +use frame_support::{ + IterableStorageMap, + pallet_prelude::{Blake2_128Concat, OptionQuery}, + storage_alias, + traits::Get, + weights::Weight, +}; +use scale_info::prelude::string::String; + +/// Clears deprecated registration-related storage items after moving to the new +/// BurnHalfLife/BurnIncreaseMult model. +/// +/// This migration is **idempotent** via `HasMigrationRun`. +/// +/// Why it “takes into account root_register”: +/// - `root_register()` still relies on `RegistrationsThisInterval`/`RegistrationsThisBlock`. +/// - We **do not reset** those counters here. +/// - We set `BurnLastHalvingBlock(NetUid::ROOT)` to “now” so your new per-interval reset logic +/// does **not** retroactively wipe root’s interval counter (which could temporarily allow extra +/// root registrations). +/// - We migrate the old `AdjustmentInterval` → `BurnHalfLife` for **all** networks (including ROOT), +/// preserving the prior interval length semantics. +/// +/// Deprecated maps cleared: +/// - PoW path: `UsedWork`, `Difficulty`, `MinDifficulty`, `MaxDifficulty`, `NetworkPowRegistrationAllowed` +/// - Old reg accounting: `POWRegistrationsThisInterval`, `BurnRegistrationsThisInterval` +/// - Old adjustment system: `AdjustmentAlpha`, `AdjustmentInterval`, `LastAdjustmentBlock` +pub fn migrate_clear_deprecated_registration_maps() -> Weight { + const RAO_PER_TAO: u64 = 1_000_000_000; + const ONE_TAO_RAO: u64 = 1 * RAO_PER_TAO; + const DEFAULT_BURN_INCREASE_MULT: u64 = 2; + + let migration_name = b"migrate_clear_deprecated_registration_maps_v1".to_vec(); + let mut weight: Weight = T::DbWeight::get().reads(1); + + // --- 0) Skip if already executed + if HasMigrationRun::::get(&migration_name) { + log::info!( + target: "runtime", + "Migration '{}' already run - skipping.", + String::from_utf8_lossy(&migration_name) + ); + return weight; + } + + // Use the current block, but ensure it’s non-zero + let current_block = Pallet::::get_current_block_as_u64(); + let block_to_set = if current_block == 0 { 1 } else { current_block }; + + // --- 1) Initialize new pricing params for *all* networks (including ROOT) + // - BurnHalfLife replaces AdjustmentInterval; migrate old value. + // - BurnIncreaseMult defaults to 2. + // - BurnLastHalvingBlock set to "now" to prevent retroactive halving/interval resets. + // + // We do NOT touch RegistrationsThisInterval/RegistrationsThisBlock here. + let mut networks_seen: u64 = 0; + + for (netuid, added) in NetworksAdded::::iter() { + if !added { + continue; + } + networks_seen = networks_seen.saturating_add(1); + + // 1.a) Migrate old AdjustmentInterval -> BurnHalfLife (guard against 0). + let old_interval: u16 = AdjustmentInterval::::get(netuid); + weight = weight.saturating_add(T::DbWeight::get().reads(1)); + + let new_half_life: u16 = old_interval.max(1); + BurnHalfLife::::insert(netuid, new_half_life); + weight = weight.saturating_add(T::DbWeight::get().writes(1)); + + // 1.b) Set BurnIncreaseMult default. + BurnIncreaseMult::::insert(netuid, DEFAULT_BURN_INCREASE_MULT.max(1)); + weight = weight.saturating_add(T::DbWeight::get().writes(1)); + + // 1.c) Start halving schedule "now". + BurnLastHalvingBlock::::insert(netuid, block_to_set); + weight = weight.saturating_add(T::DbWeight::get().writes(1)); + + // 1.d) Ensure burn is non-zero on non-root nets so multiplier logic works. + if netuid != NetUid::ROOT { + let burn_u64: u64 = Pallet::::get_burn(netuid).into(); + weight = weight.saturating_add(T::DbWeight::get().reads(1)); + + if burn_u64 == 0 { + Pallet::::set_burn(netuid, TaoBalance::from(ONE_TAO_RAO)); + weight = weight.saturating_add(T::DbWeight::get().writes(1)); + } + } + } + + // Account for the NetworksAdded iteration itself. + weight = weight.saturating_add(T::DbWeight::get().reads(networks_seen)); + + // --- 2) Clear deprecated/unused maps + + macro_rules! clear_map_and_log { + ($map:ident, $label:expr) => {{ + let res = $map::::clear(u32::MAX, None); + weight = weight.saturating_add(T::DbWeight::get().writes(1)); + if res.maybe_cursor.is_some() { + log::warn!( + target: "runtime", + "Migration '{}' - '{}' not fully cleared (cursor present).", + String::from_utf8_lossy(&migration_name), + $label + ); + } else { + log::info!( + target: "runtime", + "Migration '{}' - cleared '{}'.", + String::from_utf8_lossy(&migration_name), + $label + ); + } + }}; + } + + // PoW path (deprecated) + clear_map_and_log!(UsedWork, "UsedWork"); + clear_map_and_log!(Difficulty, "Difficulty"); + clear_map_and_log!(MinDifficulty, "MinDifficulty"); + clear_map_and_log!(MaxDifficulty, "MaxDifficulty"); + clear_map_and_log!( + NetworkPowRegistrationAllowed, + "NetworkPowRegistrationAllowed" + ); + + // Old per-interval tracking (deprecated) + clear_map_and_log!(POWRegistrationsThisInterval, "POWRegistrationsThisInterval"); + clear_map_and_log!( + BurnRegistrationsThisInterval, + "BurnRegistrationsThisInterval" + ); + + // Old adjustment mechanism (deprecated) + clear_map_and_log!(AdjustmentAlpha, "AdjustmentAlpha"); + clear_map_and_log!(AdjustmentInterval, "AdjustmentInterval"); + clear_map_and_log!(LastAdjustmentBlock, "LastAdjustmentBlock"); + + // Burn bounds (deprecated, NOT part of new spec) + clear_map_and_log!(MinBurn, "MinBurn"); + clear_map_and_log!(MaxBurn, "MaxBurn"); + + // --- 3) Mark migration done + HasMigrationRun::::insert(&migration_name, true); + weight = weight.saturating_add(T::DbWeight::get().writes(1)); + + log::info!( + target: "runtime", + "Migration '{}' completed at block {}. Initialized BurnHalfLife/BurnIncreaseMult/BurnLastHalvingBlock for {} networks and cleared deprecated maps (root_register preserved).", + String::from_utf8_lossy(&migration_name), + block_to_set, + networks_seen + ); + + weight +} diff --git a/pallets/subtensor/src/migrations/mod.rs b/pallets/subtensor/src/migrations/mod.rs index 23a2899b94..9af1945c69 100644 --- a/pallets/subtensor/src/migrations/mod.rs +++ b/pallets/subtensor/src/migrations/mod.rs @@ -5,6 +5,7 @@ use sp_io::KillStorageResult; use sp_io::hashing::twox_128; use sp_io::storage::clear_prefix; pub mod migrate_auto_stake_destination; +pub mod migrate_clear_deprecated_registration_maps; pub mod migrate_clear_rank_trust_pruning_maps; pub mod migrate_coldkey_swap_scheduled; pub mod migrate_coldkey_swap_scheduled_to_announcements; diff --git a/pallets/subtensor/src/subnets/registration.rs b/pallets/subtensor/src/subnets/registration.rs index ccf32f6ac7..4e190001d6 100644 --- a/pallets/subtensor/src/subnets/registration.rs +++ b/pallets/subtensor/src/subnets/registration.rs @@ -34,98 +34,55 @@ impl Pallet { } } - /// ---- The implementation for the extrinsic do_burned_registration: registering by burning TAO. - /// - /// # Args: - /// * 'origin': (RuntimeOrigin): - /// - The signature of the calling coldkey. - /// Burned registers can only be created by the coldkey. - /// - /// * 'netuid' (u16): - /// - The u16 network identifier. - /// - /// * 'hotkey' ( T::AccountId ): - /// - Hotkey to be registered to the network. - /// - /// # Event: - /// * NeuronRegistered; - /// - On successfully registereing a uid to a neuron slot on a subnetwork. - /// - /// # Raises: - /// * 'MechanismDoesNotExist': - /// - Attempting to registed to a non existent network. - /// - /// * 'TooManyRegistrationsThisBlock': - /// - This registration exceeds the total allowed on this network this block. - /// - /// * 'HotKeyAlreadyRegisteredInSubNet': - /// - The hotkey is already registered on this network. - /// - pub fn do_burned_registration( + pub fn do_register( origin: T::RuntimeOrigin, netuid: NetUid, hotkey: T::AccountId, ) -> DispatchResult { - // --- 1. Check that the caller has signed the transaction. (the coldkey of the pairing) + // 1) coldkey pays let coldkey = ensure_signed(origin)?; - log::debug!("do_registration( coldkey:{coldkey:?} netuid:{netuid:?} hotkey:{hotkey:?} )"); + log::debug!("do_register( coldkey:{coldkey:?} netuid:{netuid:?} hotkey:{hotkey:?} )"); - // --- 2. Ensure the passed network is valid. + // 2) network validity ensure!( !netuid.is_root(), Error::::RegistrationNotPermittedOnRootSubnet ); ensure!(Self::if_subnet_exist(netuid), Error::::SubnetNotExists); - // --- 3. Ensure the passed network allows registrations. + // 3) registrations allowed ensure!( Self::get_network_registration_allowed(netuid), Error::::SubNetRegistrationDisabled ); - // --- 4. Ensure we are not exceeding the max allowed registrations per block. - ensure!( - Self::get_registrations_this_block(netuid) - < Self::get_max_registrations_per_block(netuid), - Error::::TooManyRegistrationsThisBlock - ); - - // --- 5. Ensure we are not exceeding the max allowed registrations per interval. - ensure!( - Self::get_registrations_this_interval(netuid) - < Self::get_target_registrations_per_interval(netuid).saturating_mul(3), - Error::::TooManyRegistrationsThisInterval - ); - - // --- 6. Ensure that the key is not already registered. + // 4) hotkey not already registered ensure!( !Uids::::contains_key(netuid, &hotkey), Error::::HotKeyAlreadyRegisteredInSubNet ); - // --- 7. Ensure the callers coldkey has enough stake to perform the transaction. - let registration_cost = Self::get_burn(netuid); + // 5) compute current burn price (already updated in on_initialize for this block) + let registration_cost: TaoBalance = Self::get_burn(netuid); + ensure!( Self::can_remove_balance_from_coldkey_account(&coldkey, registration_cost.into()), Error::::NotEnoughBalanceToStake ); - // If the network account does not exist we will create it here. + // 6) ensure pairing exists and is correct Self::create_account_if_non_existent(&coldkey, &hotkey); - - // --- 8. Ensure that the pairing is correct. ensure!( Self::coldkey_owns_hotkey(&coldkey, &hotkey), Error::::NonAssociatedColdKey ); - // --- 9. Possibly there are no neuron slots at all. + // 7) capacity check + prune candidate if full ensure!( Self::get_max_allowed_uids(netuid) != 0, Error::::NoNeuronIdAvailable ); - // --- 10. If replacement is needed, ensure a safe prune candidate exists. let current_n = Self::get_subnetwork_n(netuid); let max_n = Self::get_max_allowed_uids(netuid); if current_n >= max_n { @@ -135,11 +92,10 @@ impl Pallet { ); } - // --- 11. Ensure the remove operation from the coldkey is a success. + // 8) burn payment (same mechanics as old burned_register) let actual_burn_amount = Self::remove_balance_from_coldkey_account(&coldkey, registration_cost.into())?; - // Tokens are swapped and then burned. let burned_alpha = Self::swap_tao_for_alpha( netuid, actual_burn_amount, @@ -147,191 +103,54 @@ impl Pallet { false, )? .amount_paid_out; + SubnetAlphaOut::::mutate(netuid, |total| { *total = total.saturating_sub(burned_alpha.into()) }); - // Actually perform the registration. + // 9) register neuron let neuron_uid: u16 = Self::register_neuron(netuid, &hotkey)?; - // --- 12. Record the registration and increment block and interval counters. - BurnRegistrationsThisInterval::::mutate(netuid, |val| val.saturating_inc()); + // 10) counters RegistrationsThisInterval::::mutate(netuid, |val| val.saturating_inc()); RegistrationsThisBlock::::mutate(netuid, |val| val.saturating_inc()); - Self::increase_rao_recycled(netuid, Self::get_burn(netuid).into()); + Self::increase_rao_recycled(netuid, registration_cost.into()); - // --- 13. Deposit successful event. - log::debug!("NeuronRegistered( netuid:{netuid:?} uid:{neuron_uid:?} hotkey:{hotkey:?} ) "); + // 11) event + log::debug!("NeuronRegistered( netuid:{netuid:?} uid:{neuron_uid:?} hotkey:{hotkey:?} )"); Self::deposit_event(Event::NeuronRegistered(netuid, neuron_uid, hotkey)); - // --- 14. Ok and done. Ok(()) } - /// ---- The implementation for the extrinsic do_registration. - /// - /// # Args: - /// *'origin': (RuntimeOrigin): - /// - The signature of the calling hotkey. - /// - /// *'netuid' (u16): - /// - The u16 network identifier. - /// - /// *'block_number' ( u64 ): - /// - Block hash used to prove work done. - /// - /// *'nonce' ( u64 ): - /// - Positive integer nonce used in POW. - /// - /// *'work' ( Vec ): - /// - Vector encoded bytes representing work done. - /// - /// *'hotkey' ( T::AccountId ): - /// - Hotkey to be registered to the network. - /// - /// *'coldkey' ( T::AccountId ): - /// - Associated coldkey account. - /// - /// # Event: - /// *NeuronRegistered; - /// - On successfully registereing a uid to a neuron slot on a subnetwork. - /// - /// # Raises: - /// *'MechanismDoesNotExist': - /// - Attempting to registed to a non existent network. - /// - /// *'TooManyRegistrationsThisBlock': - /// - This registration exceeds the total allowed on this network this block. - /// - /// *'HotKeyAlreadyRegisteredInSubNet': - /// - The hotkey is already registered on this network. - /// - /// *'InvalidWorkBlock': - /// - The work has been performed on a stale, future, or non existent block. - /// - /// *'InvalidDifficulty': - /// - The work does not match the difficutly. - /// - /// *'InvalidSeal': - /// - The seal is incorrect. - /// - pub fn do_registration( + pub fn do_register_limit( origin: T::RuntimeOrigin, netuid: NetUid, - block_number: u64, - nonce: u64, - work: Vec, hotkey: T::AccountId, - coldkey: T::AccountId, + limit_price: u64, ) -> DispatchResult { - // --- 1. Check that the caller has signed the transaction. - let signing_origin = ensure_signed(origin)?; log::debug!( - "do_registration( origin:{signing_origin:?} netuid:{netuid:?} hotkey:{hotkey:?}, coldkey:{coldkey:?} )" - ); - - ensure!( - signing_origin == hotkey, - Error::::TransactorAccountShouldBeHotKey + "do_register_limit( netuid:{netuid:?} hotkey:{hotkey:?} limit_price:{limit_price:?} )" ); - // --- 2. Ensure the passed network is valid. + // Minimal validation before reading/comparing burn. ensure!( !netuid.is_root(), Error::::RegistrationNotPermittedOnRootSubnet ); ensure!(Self::if_subnet_exist(netuid), Error::::SubnetNotExists); - // --- 3. Ensure the passed network allows registrations. - ensure!( - Self::get_network_pow_registration_allowed(netuid), - Error::::SubNetRegistrationDisabled - ); - - // --- 4. Ensure we are not exceeding the max allowed registrations per block. - ensure!( - Self::get_registrations_this_block(netuid) - < Self::get_max_registrations_per_block(netuid), - Error::::TooManyRegistrationsThisBlock - ); - - // --- 5. Ensure we are not exceeding the max allowed registrations per interval. - ensure!( - Self::get_registrations_this_interval(netuid) - < Self::get_target_registrations_per_interval(netuid).saturating_mul(3), - Error::::TooManyRegistrationsThisInterval - ); - - // --- 6. Ensure that the key is not already registered. - ensure!( - !Uids::::contains_key(netuid, &hotkey), - Error::::HotKeyAlreadyRegisteredInSubNet - ); - - // --- 7. Ensure the passed block number is valid, not in the future or too old. - // Work must have been done within 3 blocks (stops long range attacks). - let current_block_number: u64 = Self::get_current_block_as_u64(); - ensure!( - block_number <= current_block_number, - Error::::InvalidWorkBlock - ); - ensure!( - current_block_number.saturating_sub(block_number) < 3, - Error::::InvalidWorkBlock - ); - - // --- 8. Ensure the supplied work passes the difficulty. - let difficulty: U256 = Self::get_difficulty(netuid); - let work_hash: H256 = Self::vec_to_hash(work.clone()); - ensure!( - Self::hash_meets_difficulty(&work_hash, difficulty), - Error::::InvalidDifficulty - ); // Check that the work meets difficulty. - - // --- 9. Check Work is the product of the nonce, the block number, and hotkey. Add this as used work. - let seal: H256 = Self::create_seal_hash(block_number, nonce, &hotkey); - ensure!(seal == work_hash, Error::::InvalidSeal); - UsedWork::::insert(work.clone(), current_block_number); - - // --- 10. If the network account does not exist we will create it here. - Self::create_account_if_non_existent(&coldkey, &hotkey); + // Enforce caller limit before entering the shared registration path. + let registration_cost: TaoBalance = Self::get_burn(netuid); + let limit_price_tao: TaoBalance = TaoBalance::from(limit_price); - // --- 11. Ensure that the pairing is correct. ensure!( - Self::coldkey_owns_hotkey(&coldkey, &hotkey), - Error::::NonAssociatedColdKey - ); - - // --- 12. Possibly there is no neuron slots at all. - ensure!( - Self::get_max_allowed_uids(netuid) != 0, - Error::::NoNeuronIdAvailable + registration_cost <= limit_price_tao, + Error::::RegistrationPriceLimitExceeded ); - // --- 13. If replacement is needed, ensure a safe prune candidate exists. - let current_n = Self::get_subnetwork_n(netuid); - let max_n = Self::get_max_allowed_uids(netuid); - if current_n >= max_n { - ensure!( - Self::get_neuron_to_prune(netuid).is_some(), - Error::::NoNeuronIdAvailable - ); - } - - // Actually perform the registration. - let neuron_uid: u16 = Self::register_neuron(netuid, &hotkey)?; - - // --- 14. Record the registration and increment block and interval counters. - POWRegistrationsThisInterval::::mutate(netuid, |val| val.saturating_inc()); - RegistrationsThisInterval::::mutate(netuid, |val| val.saturating_inc()); - RegistrationsThisBlock::::mutate(netuid, |val| val.saturating_inc()); - - // --- 15. Deposit successful event. - log::debug!("NeuronRegistered( netuid:{netuid:?} uid:{neuron_uid:?} hotkey:{hotkey:?} ) "); - Self::deposit_event(Event::NeuronRegistered(netuid, neuron_uid, hotkey)); - - // --- 16. Ok and done. - Ok(()) + // Delegate the full shared registration flow. + Self::do_register(origin, netuid, hotkey) } pub fn do_faucet( @@ -635,4 +454,97 @@ impl Pallet { let vec_work: Vec = Self::hash_to_vec(work); (nonce, vec_work) } + + /// Updates neuron burn price. + /// + /// Behavior: + /// - Each non-genesis block: burn decays continuously by a per-block factor `f`, + /// where `f ^ BurnHalfLife = 1/2`. + /// - Every BurnHalfLife completed blocks: `RegistrationsThisInterval` is reset and + /// `BurnLastHalvingBlock` is advanced by whole intervals. + /// - Each block: if there were registrations in the previous block, burn is + /// multiplied by `BurnIncreaseMult ^ regs_prev_block`. + /// - Each block: `RegistrationsThisBlock` is reset to 0 for the new block. + /// + /// Notes: + /// - This runs in `on_initialize(current_block)`. + /// - Therefore, interval timing is exclusive of the current block, i.e. based on + /// `last_completed_block = current_block - 1`. + /// + pub fn update_registration_prices_for_networks() { + let current_block: u64 = Self::get_current_block_as_u64(); + let last_completed_block: u64 = current_block.saturating_sub(1); + + for (netuid, _) in NetworksAdded::::iter() { + // --- 1) Apply continuous per-block decay + interval reset. + let half_life: u16 = BurnHalfLife::::get(netuid); + let half_life_u64: u64 = u64::from(half_life); + + if half_life_u64 > 0 { + // 1a) Continuous exponential decay, once per non-genesis block. + // + // Since this function runs every block in `on_initialize`, applying the + // per-block factor once here gives continuous exponential decay. + // + // We intentionally do NOT decay in block 1, because interval timing is + // exclusive of the current block and there is no completed prior block yet. + if current_block > 1 { + let burn_u64: u64 = Self::get_burn(netuid).into(); + let factor_q32: u64 = Self::decay_factor_q32(half_life); + + let mut new_burn_u64: u64 = Self::mul_by_q32(burn_u64, factor_q32); + + // Prevent stuck-at-zero behavior. + if new_burn_u64 == 0 { + new_burn_u64 = 1; + } + + Self::set_burn(netuid, TaoBalance::from(new_burn_u64)); + } + + // 1b) Keep the existing half-life interval anchor behavior for resetting + // RegistrationsThisInterval. This is still exclusive of the current block. + let last_halving: u64 = BurnLastHalvingBlock::::get(netuid); + let delta: u64 = last_completed_block.saturating_sub(last_halving); + + let intervals_passed: u64 = Self::checked_div_or_zero_u64(delta, half_life_u64); + + if intervals_passed > 0 { + let anchor_advance: u64 = intervals_passed.saturating_mul(half_life_u64); + + BurnLastHalvingBlock::::insert( + netuid, + last_halving.saturating_add(anchor_advance), + ); + + RegistrationsThisInterval::::insert(netuid, 0); + } + } + + // --- 2) Apply post-registration bump. + // + // At the start of block N, RegistrationsThisBlock contains the count from block N-1. + // We skip bumping on root because root_register does not use burn-based pricing. + if !netuid.is_root() { + let regs_prev_block: u16 = RegistrationsThisBlock::::get(netuid); + if regs_prev_block > 0 { + let mult: u64 = BurnIncreaseMult::::get(netuid).max(1); + let bump: u64 = Self::saturating_pow_u64(mult, regs_prev_block); + + let burn_u64: u64 = Self::get_burn(netuid).into(); + let mut new_burn_u64: u64 = burn_u64.saturating_mul(bump); + + // Prevent stuck-at-zero behavior. + if new_burn_u64 == 0 { + new_burn_u64 = 1; + } + + Self::set_burn(netuid, TaoBalance::from(new_burn_u64)); + } + } + + // --- 3) Reset per-block registrations counter for the new block. + Self::set_registrations_this_block(netuid, 0); + } + } } diff --git a/pallets/subtensor/src/subnets/subnet.rs b/pallets/subtensor/src/subnets/subnet.rs index 769db17ebe..99f11e3410 100644 --- a/pallets/subtensor/src/subnets/subnet.rs +++ b/pallets/subtensor/src/subnets/subnet.rs @@ -273,13 +273,15 @@ impl Pallet { Self::set_max_allowed_uids(netuid, 256); Self::set_max_allowed_validators(netuid, 64); Self::set_min_allowed_weights(netuid, 1); - Self::set_adjustment_interval(netuid, 360); Self::set_target_registrations_per_interval(netuid, 1); - Self::set_adjustment_alpha(netuid, 17_893_341_751_498_265_066); // 18_446_744_073_709_551_615 * 0.97 = 17_893_341_751_498_265_066 Self::set_immunity_period(netuid, 5000); Self::set_min_difficulty(netuid, u64::MAX); Self::set_max_difficulty(netuid, u64::MAX); + Self::set_burn(netuid, TaoBalance::from(1_000_000_000)); + let current_block = Self::get_current_block_as_u64(); + BurnLastHalvingBlock::::insert(netuid, current_block); + // Make network parameters explicit. if !Tempo::::contains_key(netuid) { Tempo::::insert(netuid, Tempo::::get(netuid)); diff --git a/pallets/subtensor/src/tests/coinbase.rs b/pallets/subtensor/src/tests/coinbase.rs index a11cf317ff..f26b46be92 100644 --- a/pallets/subtensor/src/tests/coinbase.rs +++ b/pallets/subtensor/src/tests/coinbase.rs @@ -13,6 +13,7 @@ use alloc::collections::BTreeMap; use approx::assert_abs_diff_eq; use frame_support::assert_ok; use pallet_subtensor_swap::position::PositionId; +use safe_math::FixedExt; use sp_core::U256; use substrate_fixed::{ transcendental::sqrt, @@ -2390,26 +2391,42 @@ fn test_distribute_emission_no_miners_all_drained() { let netuid = add_dynamic_network(&U256::from(1), &U256::from(2)); let hotkey = U256::from(3); let coldkey = U256::from(4); - let init_stake = 1; + let init_stake: u64 = 1; + register_ok_neuron(netuid, hotkey, coldkey, 0); - // Give non-zero stake + + // Give non-zero stake (alpha stake) SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( &hotkey, &coldkey, netuid, init_stake.into(), ); - assert_eq!( - SubtensorModule::get_total_stake_for_hotkey(&hotkey), - init_stake.into() - ); + + // assert on ALPHA stake + let alpha_before = + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid); + assert_eq!(alpha_before, init_stake.into()); // Set the weight of root TAO to be 0%, so only alpha is effective. SubtensorModule::set_tao_weight(0); - // Set the emission to be 1 million. + // Drain any pre-existing pending emissions so the test is deterministic. + SubtensorModule::distribute_emission( + netuid, + AlphaBalance::ZERO, + AlphaBalance::ZERO, + AlphaBalance::ZERO, + AlphaBalance::ZERO, + ); + + let alpha_baseline = + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid); + + // Set emission to 1 million (alpha units) let emission = AlphaBalance::from(1_000_000); - // Run drain pending without any miners. + + // Distribute half/half. SubtensorModule::distribute_emission( netuid, emission.saturating_div(2.into()).into(), @@ -2418,15 +2435,11 @@ fn test_distribute_emission_no_miners_all_drained() { AlphaBalance::ZERO, ); - // Get the new stake of the hotkey. - let new_stake = SubtensorModule::get_total_stake_for_hotkey(&hotkey); - // We expect this neuron to get *all* the emission. - // Slight epsilon due to rounding (hotkey_take). - assert_abs_diff_eq!( - new_stake, - u64::from(emission + init_stake.into()).into(), - epsilon = 1.into() - ); + let alpha_after = + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid); + + // With a single staker and no miners, all emission should end up on that stake. + assert_abs_diff_eq!(alpha_after, alpha_baseline + emission, epsilon = 2.into()); }); } @@ -2439,14 +2452,17 @@ fn test_distribute_emission_zero_emission() { let coldkey = U256::from(4); let miner_hk = U256::from(5); let miner_ck = U256::from(6); + let init_stake: u64 = 100_000_000_000_000; let tempo = 2; SubtensorModule::set_tempo(netuid, tempo); + // Set weight-set limit to 0. SubtensorModule::set_weights_set_rate_limit(netuid, 0); register_ok_neuron(netuid, hotkey, coldkey, 0); register_ok_neuron(netuid, miner_hk, miner_ck, 0); + // Give non-zero stake SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( &hotkey, @@ -2454,10 +2470,11 @@ fn test_distribute_emission_zero_emission() { netuid, init_stake.into(), ); - assert_eq!( - SubtensorModule::get_total_stake_for_hotkey(&hotkey), - init_stake.into() - ); + + // Assert on ALPHA stake, not TAO-valued total stake. + let alpha_initial = + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid); + assert_eq!(alpha_initial, init_stake.into()); // Set the weight of root TAO to be 0%, so only alpha is effective. SubtensorModule::set_tao_weight(0); @@ -2478,11 +2495,24 @@ fn test_distribute_emission_zero_emission() { run_to_block_no_epoch(netuid, 50); - // Clear incentive and dividends. + // First, do a drain pass so any accumulated pending emission is consumed. + // This makes the *second* call a true "zero emission + zero pending" check. + SubtensorModule::distribute_emission( + netuid, + AlphaBalance::ZERO, + AlphaBalance::ZERO, + AlphaBalance::ZERO, + AlphaBalance::ZERO, + ); + + // Clear incentive and dividends AFTER draining so we can verify they get rebuilt. Incentive::::remove(NetUidStorageIndex::from(netuid)); Dividends::::remove(netuid); - // Set the emission to be ZERO. + let alpha_before = + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid); + + // Now: no new emission AND no pending emission => stake must not change. SubtensorModule::distribute_emission( netuid, AlphaBalance::ZERO, @@ -2491,12 +2521,28 @@ fn test_distribute_emission_zero_emission() { AlphaBalance::ZERO, ); - // Get the new stake of the hotkey. - let new_stake = SubtensorModule::get_total_stake_for_hotkey(&hotkey); - // We expect the stake to remain unchanged. - assert_eq!(new_stake, init_stake.into()); + // Clear incentive and dividends AFTER draining so we can verify they get rebuilt. + Incentive::::remove(NetUidStorageIndex::from(netuid)); + Dividends::::remove(netuid); + + let alpha_before = + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid); + + // Now: no new emission AND no pending emission => stake must not change. + SubtensorModule::distribute_emission( + netuid, + AlphaBalance::ZERO, + AlphaBalance::ZERO, + AlphaBalance::ZERO, + AlphaBalance::ZERO, + ); + + let alpha_after = + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid); - // Check that the incentive and dividends are set by epoch. + assert_eq!(alpha_after, alpha_before); + + // Check that the incentive and dividends are set by the epoch logic that runs in distribute_emission. assert!( Incentive::::get(NetUidStorageIndex::from(netuid)) .iter() @@ -2980,6 +3026,7 @@ fn test_mining_emission_distribution_with_no_root_sell() { register_ok_neuron(netuid, validator_hotkey, validator_coldkey, 0); register_ok_neuron(netuid, validator_miner_hotkey, validator_miner_coldkey, 1); register_ok_neuron(netuid, miner_hotkey, miner_coldkey, 2); + SubtensorModule::add_balance_to_coldkey_account( &validator_coldkey, TaoBalance::from(stake) + ExistentialDeposit::get(), @@ -2992,16 +3039,17 @@ fn test_mining_emission_distribution_with_no_root_sell() { &miner_coldkey, TaoBalance::from(stake) + ExistentialDeposit::get(), ); + SubtensorModule::set_weights_set_rate_limit(netuid, 0); step_block(subnet_tempo); + SubnetOwnerCut::::set(u16::MAX / 10); + // There are two validators and three neurons MaxAllowedUids::::set(netuid, 3); SubtensorModule::set_max_allowed_validators(netuid, 2); // Setup stakes: - // Stake from validator - // Stake from valiminer assert_ok!(SubtensorModule::add_stake( RuntimeOrigin::signed(validator_coldkey), validator_hotkey, @@ -3031,7 +3079,6 @@ fn test_mining_emission_distribution_with_no_root_sell() { // Add stake to validator so it has root stake SubtensorModule::add_balance_to_coldkey_account(&validator_coldkey, root_stake.into()); - // init root assert_ok!(SubtensorModule::add_stake( RuntimeOrigin::signed(validator_coldkey), validator_hotkey, @@ -3041,59 +3088,69 @@ fn test_mining_emission_distribution_with_no_root_sell() { // Set tao weight non zero SubtensorModule::set_tao_weight(u64::MAX / 10); - // Make root sell NOT happen - // set price very low, e.g. a lot of alpha in - //SubnetAlphaIn::::insert(netuid, AlphaBalance::from(1_000_000_000_000_000)); + // Make root sell NOT happen by forcing EMA price sum <= 1 (very low EMA price) pallet_subtensor_swap::AlphaSqrtPrice::::insert( netuid, U64F64::saturating_from_num(0.01), ); - // Make sure we ARE NOT root selling, so we do not have root alpha divs. let root_sell_flag = SubtensorModule::get_network_root_sell_flag(&[netuid]); assert!(!root_sell_flag, "Root sell flag should be false"); // Run run_coinbase until emissions are drained step_block(subnet_tempo); + // Ensure we are not accumulating root alpha divs let old_root_alpha_divs = PendingRootAlphaDivs::::get(netuid); let per_block_emission = SubtensorModule::get_block_emission_for_issuance( SubtensorModule::get_alpha_issuance(netuid).into(), ) .unwrap_or(0); - // step by one block step_block(1); - // Verify that root alpha divs + let new_root_alpha_divs = PendingRootAlphaDivs::::get(netuid); - // Check that we are indeed NOT root selling, i.e. that root alpha divs are NOT increasing assert_eq!( new_root_alpha_divs, old_root_alpha_divs, "Root alpha divs should not increase" ); - // Check root divs are zero assert_eq!( new_root_alpha_divs, AlphaBalance::ZERO, "Root alpha divs should be zero" ); + + // --- Measure miner stake before the epoch-triggering block let miner_stake_before_epoch = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( &miner_hotkey, &miner_coldkey, netuid, ); - // Run again but with some root stake + + // Move near the next epoch boundary but don't trigger it yet step_block(subnet_tempo - 2); + + // Pending amounts accumulated so far (before the epoch-triggering block adds its share) + let pending_server_before: u64 = PendingServerEmission::::get(netuid).to_u64(); + let pending_validator_before: u64 = PendingValidatorEmission::::get(netuid).to_u64(); + + // Keep your original sanity check on pending server emission (approx, allows rounding / per-block variance) assert_abs_diff_eq!( PendingServerEmission::::get(netuid).to_u64(), U96F32::saturating_from_num(per_block_emission) .saturating_mul(U96F32::saturating_from_num(subnet_tempo as u64)) .saturating_mul(U96F32::saturating_from_num(0.5)) // miner cut - .saturating_mul(U96F32::saturating_from_num(0.90)) + .saturating_mul(U96F32::saturating_from_num(0.90)) // (1 - owner cut) .saturating_to_num::(), - epsilon = 100_000_u64.into() + epsilon = 100_000_u64 ); + + // Capture root proportion *before* the epoch-triggering block runs + let root_prop: U96F32 = SubtensorModule::root_proportion(netuid); + + // This block should trigger the epoch (drain + distribute) step_block(1); + assert!( BlocksSinceLastStep::::get(netuid) == 0, "Blocks since last step should be 0" @@ -3121,22 +3178,66 @@ fn test_mining_emission_distribution_with_no_root_sell() { .to_u64() - miner_stake_before_epoch.to_u64(); - assert_abs_diff_eq!( - Incentive::::get(NetUidStorageIndex::from(netuid)) - .iter() - .sum::(), - u16::MAX, - epsilon = 10 - ); + // Incentive vector + miner uid + let incentive_vec = Incentive::::get(NetUidStorageIndex::from(netuid)); + assert_abs_diff_eq!(incentive_vec.iter().sum::(), u16::MAX, epsilon = 10); + + let miner_uid = Uids::::get(netuid, miner_hotkey).unwrap_or(0); + let miner_incentive_u16: u16 = incentive_vec + .get(miner_uid as usize) + .copied() + .unwrap_or_default(); + + // --- Compute the drained (server, validator) amounts for the epoch-triggering block + // We reconstruct the last block's per-block contributions from SubnetAlphaOutEmission and the same math in emit_to_subnets(). + let alpha_out_last_block_u64: u64 = SubnetAlphaOutEmission::::get(netuid).to_u64(); + let alpha_out_i: U96F32 = U96F32::saturating_from_num(alpha_out_last_block_u64); + + // owner cut percent + let owner_cut_u16: u16 = SubnetOwnerCut::::get(); + let cut_percent: U96F32 = U96F32::saturating_from_num(owner_cut_u16) + .safe_div(U96F32::saturating_from_num(u16::MAX)); + + // alpha_out after owner cut (still fixed-point) + let owner_cut_i: U96F32 = alpha_out_i.saturating_mul(cut_percent); + let alpha_out_after_cut: U96F32 = alpha_out_i.saturating_sub(owner_cut_i); + + // server = 50% of alpha_out_after_cut + let server_add_last: u64 = alpha_out_after_cut + .saturating_mul(U96F32::saturating_from_num(0.5)) + .saturating_to_num::(); + + // validator = 50% of alpha_out_after_cut minus root_alpha + // root_alpha = root_prop * (alpha_out_after_cut * 0.5) + let total_validator_alpha_last: U96F32 = + alpha_out_after_cut.saturating_mul(U96F32::saturating_from_num(0.5)); + let root_alpha_last: U96F32 = root_prop.saturating_mul(total_validator_alpha_last); + let validator_add_last: u64 = total_validator_alpha_last + .saturating_sub(root_alpha_last) + .saturating_to_num::(); + + let drained_server: u64 = pending_server_before.saturating_add(server_add_last); + let drained_validator: u64 = pending_validator_before.saturating_add(validator_add_last); + + // epoch_with_mechanisms receives total_alpha_minus_owner_cut = drained_server + drained_validator (root is recycled here), + // and the mechanism splits that total into incentive/dividend budgets internally. + // Therefore incentive budget is half of that total. + let total_alpha_into_epoch: u64 = drained_server.saturating_add(drained_validator); + let incentive_pool: u64 = U96F32::saturating_from_num(total_alpha_into_epoch) + .saturating_mul(U96F32::saturating_from_num(0.5)) + .saturating_to_num::(); + + let incentive_sum_u64: u64 = incentive_vec.iter().fold(0u64, |acc, &x| acc + x as u64); + assert!(incentive_sum_u64 > 0); + + let expected_miner_emission: u64 = U96F32::saturating_from_num(miner_incentive_u16 as u64) + .safe_div(U96F32::saturating_from_num(incentive_sum_u64)) + .saturating_mul(U96F32::saturating_from_num(incentive_pool)) + .saturating_to_num::(); assert_abs_diff_eq!( miner_emission_1, - U96F32::saturating_from_num(miner_incentive) - .saturating_div(u16::MAX.into()) - .saturating_mul(U96F32::saturating_from_num(per_block_emission)) - .saturating_mul(U96F32::saturating_from_num(subnet_tempo + 1)) - .saturating_mul(U96F32::saturating_from_num(0.45)) // miner cut - .saturating_to_num::(), + expected_miner_emission, epsilon = 1_000_000_u64 ); }); diff --git a/pallets/subtensor/src/tests/difficulty.rs b/pallets/subtensor/src/tests/difficulty.rs deleted file mode 100644 index 78ac8620c9..0000000000 --- a/pallets/subtensor/src/tests/difficulty.rs +++ /dev/null @@ -1,173 +0,0 @@ -#![allow(clippy::unwrap_used)] - -use sp_core::U256; -use subtensor_runtime_common::NetUid; - -use super::mock::*; - -// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::difficulty::test_registration_difficulty_adjustment --exact --show-output --nocapture -#[test] -fn test_registration_difficulty_adjustment() { - new_test_ext(1).execute_with(|| { - // Create Net 1 - let netuid = NetUid::from(1); - let tempo: u16 = 1; - let modality: u16 = 1; - add_network(netuid, tempo, modality); - - // owners are not deregistered - crate::SubnetOwner::::insert(netuid, U256::from(99999)); - - SubtensorModule::set_min_difficulty(netuid, 10000); - assert_eq!(SubtensorModule::get_difficulty_as_u64(netuid), 10000); // Check initial difficulty. - assert_eq!(SubtensorModule::get_last_adjustment_block(netuid), 0); // Last adjustment block starts at 0. - assert_eq!(SubtensorModule::get_registrations_this_block(netuid), 0); // No registrations this block. - SubtensorModule::set_adjustment_alpha(netuid, 58000); - SubtensorModule::set_target_registrations_per_interval(netuid, 2); - SubtensorModule::set_adjustment_interval(netuid, 100); - assert!(SubtensorModule::get_network_registration_allowed(netuid)); // Default registration allowed. - - // Set values and check. - SubtensorModule::set_difficulty(netuid, 20000); - SubtensorModule::set_adjustment_interval(netuid, 1); - SubtensorModule::set_target_registrations_per_interval(netuid, 1); - SubtensorModule::set_max_registrations_per_block(netuid, 3); - SubtensorModule::set_max_allowed_uids(netuid, 3); - SubtensorModule::set_network_registration_allowed(netuid, true); - assert_eq!(SubtensorModule::get_difficulty_as_u64(netuid), 20000); // Check set difficutly. - assert_eq!(SubtensorModule::get_adjustment_interval(netuid), 1); // Check set adjustment interval. - assert_eq!( - SubtensorModule::get_target_registrations_per_interval(netuid), - 1 - ); // Check set adjustment interval. - assert_eq!(SubtensorModule::get_max_registrations_per_block(netuid), 3); // Check set registrations per block. - assert_eq!(SubtensorModule::get_max_allowed_uids(netuid), 3); // Check set registrations per block. - assert!(SubtensorModule::get_network_registration_allowed(netuid)); // Check set registration allowed - - // Lets register 3 neurons... - let hotkey0 = U256::from(0); - let hotkey1 = U256::from(100); - let hotkey2 = U256::from(2000); - let coldkey0 = U256::from(0); - let coldkey1 = U256::from(1000); - let coldkey2 = U256::from(20000); - register_ok_neuron(netuid, hotkey0, coldkey0, 39420842); - register_ok_neuron(netuid, hotkey1, coldkey1, 12412392); - register_ok_neuron(netuid, hotkey2, coldkey2, 21813123); - assert_eq!( - SubtensorModule::get_hotkey_for_net_and_uid(netuid, 0).unwrap(), - hotkey0 - ); - assert_eq!( - SubtensorModule::get_hotkey_for_net_and_uid(netuid, 1).unwrap(), - hotkey1 - ); - assert_eq!( - SubtensorModule::get_hotkey_for_net_and_uid(netuid, 2).unwrap(), - hotkey2 - ); - - assert_eq!(SubtensorModule::get_subnetwork_n(netuid), 3); // All 3 are registered. - assert_eq!(SubtensorModule::get_registrations_this_block(netuid), 3); // 3 Registrations. - assert_eq!(SubtensorModule::get_registrations_this_interval(netuid), 3); // 3 Registrations this interval. - - // Fast forward 1 block. - assert_eq!(SubtensorModule::get_difficulty_as_u64(netuid), 20000); // Difficulty is unchanged. - step_block(1); - assert_eq!(SubtensorModule::get_registrations_this_block(netuid), 0); // Registrations have been erased. - - // TODO: are we OK with this change? - assert_eq!(SubtensorModule::get_last_adjustment_block(netuid), 2); // We just adjusted on the first block. - - assert_eq!(SubtensorModule::get_difficulty_as_u64(netuid), 40000); // Difficulty is increased ( 20000 * ( 3 + 1 ) / ( 1 + 1 ) ) = 80_000 - assert_eq!(SubtensorModule::get_registrations_this_interval(netuid), 0); // Registrations this interval has been wiped. - - // Lets change the adjustment interval - SubtensorModule::set_adjustment_interval(netuid, 3); - assert_eq!(SubtensorModule::get_adjustment_interval(netuid), 3); // Check set adjustment interval. - - SubtensorModule::set_target_registrations_per_interval(netuid, 3); - assert_eq!( - SubtensorModule::get_target_registrations_per_interval(netuid), - 3 - ); // Target is default. - - // Register 3 more - register_ok_neuron(netuid, hotkey0 + 1, coldkey0 + 1, 3942084); - register_ok_neuron(netuid, hotkey1 + 1, coldkey1 + 1, 1241239); - register_ok_neuron(netuid, hotkey2 + 1, coldkey2 + 1, 2181312); - assert_eq!( - SubtensorModule::get_hotkey_for_net_and_uid(netuid, 0).unwrap(), - hotkey0 + 1 - ); // replace 0 - assert_eq!( - SubtensorModule::get_hotkey_for_net_and_uid(netuid, 1).unwrap(), - hotkey1 + 1 - ); // replace 1 - assert_eq!( - SubtensorModule::get_hotkey_for_net_and_uid(netuid, 2).unwrap(), - hotkey2 + 1 - ); // replace 2 - assert_eq!(SubtensorModule::get_registrations_this_block(netuid), 3); // Registrations have been erased. - assert_eq!(SubtensorModule::get_registrations_this_interval(netuid), 3); // Registrations this interval = 3 - - step_block(1); // Step - - // TODO: are we OK with this change? - assert_eq!(SubtensorModule::get_last_adjustment_block(netuid), 2); // Still previous adjustment block. - - assert_eq!(SubtensorModule::get_registrations_this_block(netuid), 0); // Registrations have been erased. - assert_eq!(SubtensorModule::get_registrations_this_interval(netuid), 3); // Registrations this interval = 3 - - // Register 3 more. - register_ok_neuron(netuid, hotkey0 + 2, coldkey0 + 2, 394208420); - register_ok_neuron(netuid, hotkey1 + 2, coldkey1 + 2, 124123920); - register_ok_neuron(netuid, hotkey2 + 2, coldkey2 + 2, 218131230); - assert_eq!(SubtensorModule::get_registrations_this_block(netuid), 3); // Registrations have been erased. - - // We have 6 registrations this adjustment interval. - step_block(1); // Step - assert_eq!(SubtensorModule::get_registrations_this_interval(netuid), 6); // Registrations this interval = 6 - assert_eq!(SubtensorModule::get_difficulty_as_u64(netuid), 40000); // Difficulty unchanged. - step_block(1); // Step - assert_eq!(SubtensorModule::get_difficulty_as_u64(netuid), 60_000); // Difficulty changed ( 40000 ) * ( 6 + 3 / 3 + 3 ) = 40000 * 1.5 = 60_000 - assert_eq!(SubtensorModule::get_registrations_this_interval(netuid), 0); // Registrations this interval drops to 0. - - // Test min value. - SubtensorModule::set_min_difficulty(netuid, 1); - SubtensorModule::set_difficulty(netuid, 4); - assert_eq!(SubtensorModule::get_min_difficulty(netuid), 1); - assert_eq!(SubtensorModule::get_difficulty_as_u64(netuid), 4); - SubtensorModule::set_adjustment_interval(netuid, 1); - step_block(1); // Step - assert_eq!(SubtensorModule::get_difficulty_as_u64(netuid), 2); // Difficulty dropped 4 * ( 0 + 1 ) / (1 + 1) = 1/2 = 2 - step_block(1); // Step - assert_eq!(SubtensorModule::get_difficulty_as_u64(netuid), 1); // Difficulty dropped 2 * ( 0 + 1 ) / (1 + 1) = 1/2 = 1 - step_block(1); // Step - assert_eq!(SubtensorModule::get_difficulty_as_u64(netuid), 1); // Difficulty dropped 2 * ( 0 + 1 ) / (1 + 1) = 1/2 = max(0.5, 1) - - // Test max value. - SubtensorModule::set_max_difficulty(netuid, 10000); - SubtensorModule::set_difficulty(netuid, 5000); - assert_eq!(SubtensorModule::get_max_difficulty(netuid), 10000); - assert_eq!(SubtensorModule::get_difficulty_as_u64(netuid), 5000); - SubtensorModule::set_max_registrations_per_block(netuid, 4); - register_ok_neuron(netuid, hotkey0 + 3, coldkey0 + 3, 294208420); - register_ok_neuron(netuid, hotkey1 + 3, coldkey1 + 3, 824123920); - register_ok_neuron(netuid, hotkey2 + 3, coldkey2 + 3, 324123920); - register_ok_neuron(netuid, hotkey2 + 4, coldkey2 + 4, 524123920); - assert_eq!(SubtensorModule::get_registrations_this_interval(netuid), 4); - assert_eq!( - SubtensorModule::get_target_registrations_per_interval(netuid), - 3 - ); - step_block(1); // Step - assert_eq!(SubtensorModule::get_difficulty_as_u64(netuid), 5833); // Difficulty increased 5000 * ( 4 + 3 ) / (3 + 3) = 1.16 * 5000 = 5833 - - register_ok_neuron(netuid, hotkey0 + 4, coldkey0 + 4, 124208420); - register_ok_neuron(netuid, hotkey1 + 4, coldkey1 + 4, 314123920); - register_ok_neuron(netuid, hotkey2 + 4, coldkey2 + 4, 834123920); - step_block(1); // Step - assert_eq!(SubtensorModule::get_difficulty_as_u64(netuid), 5833); // Difficulty unchanged - }); -} diff --git a/pallets/subtensor/src/tests/epoch.rs b/pallets/subtensor/src/tests/epoch.rs index 3775efbea0..5c516f9f30 100644 --- a/pallets/subtensor/src/tests/epoch.rs +++ b/pallets/subtensor/src/tests/epoch.rs @@ -560,12 +560,12 @@ fn test_1_graph() { let coldkey = U256::from(0); let hotkey = U256::from(0); let uid: u16 = 0; - let stake_amount: u64 = 1_000_000_000; + let stake_amount: TaoBalance = 1_000_000_000.into(); add_network_disable_commit_reveal(netuid, u16::MAX - 1, 0); // set higher tempo to avoid built-in epoch, then manual epoch instead SubtensorModule::set_max_allowed_uids(netuid, 1); SubtensorModule::add_balance_to_coldkey_account( &coldkey, - TaoBalance::from(stake_amount) + ExistentialDeposit::get(), + stake_amount + ExistentialDeposit::get() + SubtensorModule::get_network_min_lock(), ); register_ok_neuron(netuid, hotkey, coldkey, 1); SubtensorModule::set_weights_set_rate_limit(netuid, 0); @@ -603,7 +603,6 @@ fn test_1_graph() { assert_eq!(SubtensorModule::get_dividends_for_uid(netuid, uid), 0); }); } - // Test an epoch on a graph with two items. // SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::epoch::test_10_graph --exact --show-output --nocapture #[test] @@ -1009,7 +1008,7 @@ fn test_bonds() { let n: u16 = 8; let netuid = NetUid::from(1); let tempo: u16 = 1; - let max_stake: u64 = 4; + let max_stake: TaoBalance = 4.into(); let stakes: Vec = vec![1, 2, 3, 4, 0, 0, 0, 0]; let block_number = System::block_number(); add_network_disable_commit_reveal(netuid, tempo, 0); @@ -1024,7 +1023,10 @@ fn test_bonds() { // === Register [validator1, validator2, validator3, validator4, server1, server2, server3, server4] for key in 0..n as u64 { - SubtensorModule::add_balance_to_coldkey_account( &U256::from(key), max_stake.into()); + SubtensorModule::add_balance_to_coldkey_account( + &U256::from(key), + max_stake + ExistentialDeposit::get() + SubtensorModule::get_network_min_lock() + ); let (nonce, work): (u64, Vec) = SubtensorModule::create_work_for_block_number( netuid, block_number, key * 1_000_000, &U256::from(key)); assert_ok!(SubtensorModule::register(<::RuntimeOrigin>::signed(U256::from(key)), netuid, block_number, nonce, work, U256::from(key), U256::from(key))); SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( &U256::from(key), &U256::from(key), netuid, stakes[key as usize].into() ); @@ -1357,7 +1359,7 @@ fn test_active_stake() { let netuid = NetUid::from(1); let tempo: u16 = 1; let block_number: u64 = System::block_number(); - let stake: u64 = 1; + let stake: TaoBalance = 1.into(); add_network_disable_commit_reveal(netuid, tempo, 0); SubtensorModule::set_max_allowed_uids(netuid, n); assert_eq!(SubtensorModule::get_max_allowed_uids(netuid), n); @@ -1367,7 +1369,10 @@ fn test_active_stake() { // === Register [validator1, validator2, server1, server2] for key in 0..n as u64 { - SubtensorModule::add_balance_to_coldkey_account(&U256::from(key), stake.into()); + SubtensorModule::add_balance_to_coldkey_account( + &U256::from(key), + stake + ExistentialDeposit::get() + SubtensorModule::get_network_min_lock(), + ); let (nonce, work): (u64, Vec) = SubtensorModule::create_work_for_block_number( netuid, block_number, @@ -1387,7 +1392,7 @@ fn test_active_stake() { &U256::from(key), &U256::from(key), netuid, - stake.into(), + AlphaBalance::from(stake.to_u64()), ); } assert_eq!(SubtensorModule::get_max_allowed_uids(netuid), n); @@ -1573,7 +1578,7 @@ fn test_outdated_weights() { let netuid = NetUid::from(1); let tempo: u16 = 0; let mut block_number: u64 = System::block_number(); - let stake: u64 = 1; + let stake: TaoBalance = 1.into(); add_network_disable_commit_reveal(netuid, tempo, 0); SubtensorModule::set_max_allowed_uids(netuid, n); SubtensorModule::set_weights_set_rate_limit(netuid, 0); @@ -1585,7 +1590,12 @@ fn test_outdated_weights() { // === Register [validator1, validator2, server1, server2] for key in 0..n as u64 { - SubtensorModule::add_balance_to_coldkey_account(&U256::from(key), stake.into()); + SubtensorModule::add_balance_to_coldkey_account( + &U256::from(key), + stake + + ExistentialDeposit::get() + + (SubtensorModule::get_network_min_lock() * 2.into()), + ); let (nonce, work): (u64, Vec) = SubtensorModule::create_work_for_block_number( netuid, block_number, @@ -1605,7 +1615,7 @@ fn test_outdated_weights() { &U256::from(key), &U256::from(key), netuid, - stake.into(), + AlphaBalance::from(stake.to_u64()), ); } assert_eq!(SubtensorModule::get_subnetwork_n(netuid), n); @@ -1673,6 +1683,12 @@ fn test_outdated_weights() { // === Dereg server2 at uid3 (least emission) + register new key over uid3 let new_key: u64 = n as u64; // register a new key while at max capacity, which means the least incentive uid will be deregistered + SubtensorModule::add_balance_to_coldkey_account( + &U256::from(new_key), + stake + + ExistentialDeposit::get() + + (SubtensorModule::get_network_min_lock() * 2.into()), + ); let (nonce, work): (u64, Vec) = SubtensorModule::create_work_for_block_number( netuid, block_number, @@ -1753,7 +1769,7 @@ fn test_outdated_weights() { }); } -// Test the zero emission handling and fallback under zero effective weight conditions, to ensure non-zero effective emission. +/// Test the zero emission handling and fallback under zero effective weight conditions, to ensure non-zero effective emission. #[test] fn test_zero_weights() { new_test_ext(1).execute_with(|| { @@ -1772,6 +1788,10 @@ fn test_zero_weights() { // === Register [validator, server] for key in 0..n as u64 { + SubtensorModule::add_balance_to_coldkey_account( + &U256::from(key), + ExistentialDeposit::get() + (SubtensorModule::get_network_min_lock() * 2.into()), + ); let (nonce, work): (u64, Vec) = SubtensorModule::create_work_for_block_number( netuid, block_number, @@ -1881,6 +1901,10 @@ fn test_zero_weights() { // === Outdate weights by reregistering servers for new_key in n..n + (n / 2) { // register a new key while at max capacity, which means the least emission uid will be deregistered + SubtensorModule::add_balance_to_coldkey_account( + &U256::from(new_key), + ExistentialDeposit::get() + (SubtensorModule::get_network_min_lock() * 2.into()), + ); let (nonce, work): (u64, Vec) = SubtensorModule::create_work_for_block_number( netuid, block_number, @@ -1964,7 +1988,7 @@ fn test_deregistered_miner_bonds() { let netuid = NetUid::from(1); let high_tempo: u16 = u16::MAX - 1; // high tempo to skip automatic epochs in on_initialize, use manual epochs instead - let stake: u64 = 1; + let stake: TaoBalance = 1.into(); add_network_disable_commit_reveal(netuid, high_tempo, 0); SubtensorModule::set_max_allowed_uids(netuid, n); SubtensorModule::set_weights_set_rate_limit(netuid, 0); @@ -1977,7 +2001,12 @@ fn test_deregistered_miner_bonds() { // === Register [validator1, validator2, server1, server2] let block_number = System::block_number(); for key in 0..n as u64 { - SubtensorModule::add_balance_to_coldkey_account(&U256::from(key), stake.into()); + SubtensorModule::add_balance_to_coldkey_account( + &U256::from(key), + stake + + ExistentialDeposit::get() + + (SubtensorModule::get_network_min_lock() * 2.into()), + ); let (nonce, work): (u64, Vec) = SubtensorModule::create_work_for_block_number( netuid, block_number, @@ -1997,7 +2026,7 @@ fn test_deregistered_miner_bonds() { &U256::from(key), &U256::from(key), netuid, - stake.into(), + AlphaBalance::from(stake.to_u64()), ); } assert_eq!(SubtensorModule::get_subnetwork_n(netuid), n); @@ -2056,6 +2085,12 @@ fn test_deregistered_miner_bonds() { // === Dereg server2 at uid3 (least emission) + register new key over uid3 let new_key: u64 = n as u64; // register a new key while at max capacity, which means the least incentive uid will be deregistered let block_number = System::block_number(); + SubtensorModule::add_balance_to_coldkey_account( + &U256::from(new_key), + stake + + ExistentialDeposit::get() + + (SubtensorModule::get_network_min_lock() * 2.into()), + ); let (nonce, work): (u64, Vec) = SubtensorModule::create_work_for_block_number( netuid, block_number, @@ -2140,17 +2175,17 @@ fn test_validator_permits() { let (validators, servers) = distribute_nodes(validators_n as usize, network_n, interleave as usize); let correct: bool = true; - let mut stake: Vec = vec![0; network_n]; + let mut stake: Vec = vec![0.into(); network_n]; for validator in &validators { stake[*validator as usize] = match assignment { - 1 => *validator as u64 + network_n as u64, - _ => 1, + 1 => TaoBalance::from(*validator) + network_n.into(), + _ => 1.into(), }; } for server in &servers { stake[*server as usize] = match assignment { - 1 => *server as u64, - _ => 0, + 1 => TaoBalance::from(*server), + _ => 0.into(), }; } new_test_ext(1).execute_with(|| { @@ -2172,7 +2207,9 @@ fn test_validator_permits() { for key in 0..network_n as u64 { SubtensorModule::add_balance_to_coldkey_account( &U256::from(key), - stake[key as usize].into(), + stake[key as usize] + + ExistentialDeposit::get() + + SubtensorModule::get_network_min_lock(), ); let (nonce, work): (u64, Vec) = SubtensorModule::create_work_for_block_number( @@ -2194,7 +2231,7 @@ fn test_validator_permits() { &U256::from(key), &U256::from(key), netuid, - stake[key as usize].into(), + stake[key as usize].to_u64().into(), ); } assert_eq!(SubtensorModule::get_subnetwork_n(netuid), network_n as u16); @@ -2208,7 +2245,7 @@ fn test_validator_permits() { SubtensorModule::epoch(netuid, 1_000_000_000.into()); // run first epoch to set allowed validators for validator in &validators { assert_eq!( - stake[*validator as usize] >= min_stake, + stake[*validator as usize] >= TaoBalance::from(min_stake), SubtensorModule::get_validator_permit_for_uid(netuid, *validator) ); } @@ -2246,7 +2283,9 @@ fn test_validator_permits() { } for server in &servers { assert_eq!( - (stake[*server as usize] + (2 * network_n as u64)) >= min_stake, + (stake[*server as usize] + + (TaoBalance::from(2) * TaoBalance::from(network_n))) + >= TaoBalance::from(min_stake), SubtensorModule::get_validator_permit_for_uid(netuid, *server) ); } @@ -2696,7 +2735,12 @@ fn setup_yuma_3_scenario(netuid: NetUid, n: u16, sparse: bool, max_stake: u64, s // === Register for key in 0..n as u64 { - SubtensorModule::add_balance_to_coldkey_account(&U256::from(key), max_stake.into()); + SubtensorModule::add_balance_to_coldkey_account( + &U256::from(key), + TaoBalance::from(max_stake) + + ExistentialDeposit::get() + + SubtensorModule::get_network_min_lock(), + ); let (nonce, work): (u64, Vec) = SubtensorModule::create_work_for_block_number( netuid, block_number, @@ -3833,7 +3877,9 @@ fn test_last_update_size_mismatch() { SubtensorModule::set_max_allowed_uids(netuid, 1); SubtensorModule::add_balance_to_coldkey_account( &coldkey, - TaoBalance::from(stake_amount) + ExistentialDeposit::get(), + TaoBalance::from(stake_amount) + + ExistentialDeposit::get() + + (SubtensorModule::get_network_min_lock() * 2.into()), ); register_ok_neuron(netuid, hotkey, coldkey, 1); SubtensorModule::set_weights_set_rate_limit(netuid, 0); diff --git a/pallets/subtensor/src/tests/migration.rs b/pallets/subtensor/src/tests/migration.rs index f4d0347686..94c443be05 100644 --- a/pallets/subtensor/src/tests/migration.rs +++ b/pallets/subtensor/src/tests/migration.rs @@ -2693,23 +2693,40 @@ fn test_migrate_reset_unactive_sn_get_unactive_netuids() { #[test] fn test_migrate_reset_unactive_sn() { new_test_ext(1).execute_with(|| { + use sp_std::collections::btree_map::BTreeMap; + let (active_netuids, inactive_netuids) = do_setup_unactive_sn(); let initial_tao = Pallet::::get_network_min_lock(); let initial_alpha: AlphaBalance = initial_tao.to_u64().into(); + let mut locked_before: BTreeMap = BTreeMap::new(); + let mut rao_recycled_before: BTreeMap = BTreeMap::new(); + + for netuid in active_netuids.iter().chain(inactive_netuids.iter()) { + locked_before.insert(*netuid, SubnetLocked::::get(*netuid)); + rao_recycled_before.insert(*netuid, RAORecycledForRegistration::::get(netuid)); + } + // Run the migration let w = crate::migrations::migrate_reset_unactive_sn::migrate_reset_unactive_sn::(); assert!(!w.is_zero(), "weight must be non-zero"); // Verify the results for netuid in &inactive_netuids { - let actual_tao_lock_amount = SubnetLocked::::get(*netuid); - let actual_tao_lock_amount_less_pool_tao = if (actual_tao_lock_amount < initial_tao) { - TaoBalance::ZERO - } else { - actual_tao_lock_amount - initial_tao - }; + let netuid = *netuid; + + assert_eq!( + SubnetLocked::::get(netuid), + *locked_before.get(&netuid).unwrap(), + "SubnetLocked unexpectedly changed for inactive subnet {netuid:?}" + ); + assert_eq!( + RAORecycledForRegistration::::get(netuid), + *rao_recycled_before.get(&netuid).unwrap(), + "RAORecycledForRegistration unexpectedly changed for inactive subnet {netuid:?}" + ); + assert_eq!( PendingServerEmission::::get(netuid), AlphaBalance::ZERO @@ -2722,13 +2739,8 @@ fn test_migrate_reset_unactive_sn() { PendingRootAlphaDivs::::get(netuid), AlphaBalance::ZERO ); - assert_eq!( - // not modified - RAORecycledForRegistration::::get(netuid), - actual_tao_lock_amount_less_pool_tao - ); assert!(pallet_subtensor_swap::AlphaSqrtPrice::::contains_key( - *netuid + netuid )); assert_eq!(PendingOwnerCut::::get(netuid), AlphaBalance::ZERO); assert_ne!(SubnetTAO::::get(netuid), initial_tao); @@ -2758,7 +2770,7 @@ fn test_migrate_reset_unactive_sn() { TotalHotkeyAlphaLastEpoch::::get(hk, netuid), AlphaBalance::ZERO ); - assert_ne!(RootClaimable::::get(hk).get(netuid), None); + assert_ne!(RootClaimable::::get(hk).get(&netuid), None); for coldkey in 0..10 { let ck = U256::from(coldkey); assert_ne!(Alpha::::get((hk, ck, netuid)), U64F64::from_num(0.0)); @@ -2772,8 +2784,19 @@ fn test_migrate_reset_unactive_sn() { // !!! Make sure the active subnets were not reset for netuid in &active_netuids { - let actual_tao_lock_amount = SubnetLocked::::get(*netuid); - let actual_tao_lock_amount_less_pool_tao = actual_tao_lock_amount - initial_tao; + let netuid = *netuid; + + assert_eq!( + SubnetLocked::::get(netuid), + *locked_before.get(&netuid).unwrap(), + "SubnetLocked unexpectedly changed for active subnet {netuid:?}" + ); + assert_eq!( + RAORecycledForRegistration::::get(netuid), + *rao_recycled_before.get(&netuid).unwrap(), + "RAORecycledForRegistration unexpectedly changed for active subnet {netuid:?}" + ); + assert_ne!( PendingServerEmission::::get(netuid), AlphaBalance::ZERO @@ -2787,9 +2810,9 @@ fn test_migrate_reset_unactive_sn() { AlphaBalance::ZERO ); assert_eq!( - // not modified + // unchanged (already asserted above via snapshot) RAORecycledForRegistration::::get(netuid), - actual_tao_lock_amount_less_pool_tao + *rao_recycled_before.get(&netuid).unwrap() ); assert_ne!(SubnetTaoInEmission::::get(netuid), TaoBalance::ZERO); assert_ne!( @@ -2801,7 +2824,7 @@ fn test_migrate_reset_unactive_sn() { AlphaBalance::ZERO ); assert!(pallet_subtensor_swap::AlphaSqrtPrice::::contains_key( - *netuid + netuid )); assert_ne!(PendingOwnerCut::::get(netuid), AlphaBalance::ZERO); assert_ne!(SubnetTAO::::get(netuid), initial_tao); @@ -2822,7 +2845,7 @@ fn test_migrate_reset_unactive_sn() { TotalHotkeyAlphaLastEpoch::::get(hk, netuid), AlphaBalance::ZERO ); - assert!(RootClaimable::::get(hk).contains_key(netuid)); + assert!(RootClaimable::::get(hk).contains_key(&netuid)); for coldkey in 0..10 { let ck = U256::from(coldkey); assert_ne!(Alpha::::get((hk, ck, netuid)), U64F64::from_num(0.0)); diff --git a/pallets/subtensor/src/tests/mock.rs b/pallets/subtensor/src/tests/mock.rs index deb4cd7fc5..a422423eae 100644 --- a/pallets/subtensor/src/tests/mock.rs +++ b/pallets/subtensor/src/tests/mock.rs @@ -569,6 +569,9 @@ where } } +pub const RAO_PER_TAO: u64 = 1_000_000_000; +pub const DEFAULT_RESERVE: u64 = 1_000_000_000_000; + static TEST_LOGS_INIT: OnceLock<()> = OnceLock::new(); pub fn init_logs_for_tests() { @@ -724,32 +727,74 @@ pub(crate) fn next_block() -> u64 { block } -#[allow(dead_code)] pub fn register_ok_neuron( netuid: NetUid, hotkey_account_id: U256, coldkey_account_id: U256, - start_nonce: u64, + _start_nonce: u64, ) { - let block_number: u64 = SubtensorModule::get_current_block_as_u64(); - let (nonce, work): (u64, Vec) = SubtensorModule::create_work_for_block_number( - netuid, - block_number, - start_nonce, - &hotkey_account_id, - ); - let result = SubtensorModule::register( - <::RuntimeOrigin>::signed(hotkey_account_id), - netuid, - block_number, - nonce, - work, - hotkey_account_id, - coldkey_account_id, - ); - assert_ok!(result); + let reserve: u64 = 1_000_000_000_000; + let tao_reserve = SubnetTAO::::get(netuid); + let alpha_reserve = SubnetAlphaIn::::get(netuid) + .saturating_add(SubnetAlphaInProvided::::get(netuid)); + + if tao_reserve.is_zero() && alpha_reserve.is_zero() { + setup_reserves(netuid, reserve.into(), reserve.into()); + } + + // Ensure coldkey has enough to pay the current burn AND is not fully drained to zero. + // This avoids ZeroBalanceAfterWithdrawn in burned_register. + let top_up_for_burn = |netuid: NetUid, cold: U256| { + let burn: TaoBalance = SubtensorModule::get_burn(netuid); + let burn_u64: TaoBalance = burn; + + // Make sure something remains after withdrawal even if ED is 0 in tests. + let ed: TaoBalance = ExistentialDeposit::get(); + let min_remaining: TaoBalance = ed.max(1.into()); + + // Small buffer for safety (fees / rounding / future changes). + let buffer: TaoBalance = 10.into(); + + let min_balance_needed: TaoBalance = burn_u64 + .saturating_add(min_remaining) + .saturating_add(buffer); + + let bal: TaoBalance = SubtensorModule::get_coldkey_balance(&cold); + if bal < min_balance_needed { + SubtensorModule::add_balance_to_coldkey_account(&cold, min_balance_needed - bal); + } + }; + + top_up_for_burn(netuid, coldkey_account_id); + + let origin = <::RuntimeOrigin>::signed(coldkey_account_id); + let result = SubtensorModule::burned_register(origin.clone(), netuid, hotkey_account_id); + + match result { + Ok(()) => { + // success + } + Err(e) + if e == Error::::TooManyRegistrationsThisInterval.into() + || e == Error::::NotEnoughBalanceToStake.into() + || e == Error::::ZeroBalanceAfterWithdrawn.into() => + { + // Re-top-up and retry once (burn can be state-dependent). + top_up_for_burn(netuid, coldkey_account_id); + + assert_ok!(SubtensorModule::burned_register( + origin, + netuid, + hotkey_account_id + )); + } + Err(e) => { + panic!("Expected Ok(_). Got Err({e:?})"); + } + } + log::info!( - "Register ok neuron: netuid: {netuid:?}, coldkey: {hotkey_account_id:?}, hotkey: {coldkey_account_id:?}" + "Register ok neuron: netuid: {netuid:?}, coldkey: {coldkey_account_id:?}, hotkey: {hotkey_account_id:?}" ); } @@ -757,24 +802,35 @@ pub fn register_ok_neuron( pub fn add_network(netuid: NetUid, tempo: u16, _modality: u16) { SubtensorModule::init_new_network(netuid, tempo); SubtensorModule::set_network_registration_allowed(netuid, true); - SubtensorModule::set_network_pow_registration_allowed(netuid, true); FirstEmissionBlockNumber::::insert(netuid, 1); SubtokenEnabled::::insert(netuid, true); + + // make interval 1 block so tests can register by stepping 1 block. + BurnHalfLife::::insert(netuid, 1); + BurnIncreaseMult::::insert(netuid, 1); + BurnLastHalvingBlock::::insert(netuid, SubtensorModule::get_current_block_as_u64()); } #[allow(dead_code)] pub fn add_network_without_emission_block(netuid: NetUid, tempo: u16, _modality: u16) { SubtensorModule::init_new_network(netuid, tempo); SubtensorModule::set_network_registration_allowed(netuid, true); - SubtensorModule::set_network_pow_registration_allowed(netuid, true); + + BurnHalfLife::::insert(netuid, 1); + BurnIncreaseMult::::insert(netuid, 1); + BurnLastHalvingBlock::::insert(netuid, SubtensorModule::get_current_block_as_u64()); } #[allow(dead_code)] pub fn add_network_disable_subtoken(netuid: NetUid, tempo: u16, _modality: u16) { SubtensorModule::init_new_network(netuid, tempo); SubtensorModule::set_network_registration_allowed(netuid, true); - SubtensorModule::set_network_pow_registration_allowed(netuid, true); + SubtokenEnabled::::insert(netuid, false); + + BurnHalfLife::::insert(netuid, 1); + BurnIncreaseMult::::insert(netuid, 1); + BurnLastHalvingBlock::::insert(netuid, SubtensorModule::get_current_block_as_u64()); } #[allow(dead_code)] @@ -791,9 +847,14 @@ pub fn add_dynamic_network(hotkey: &U256, coldkey: &U256) -> NetUid { *hotkey )); NetworkRegistrationAllowed::::insert(netuid, true); - NetworkPowRegistrationAllowed::::insert(netuid, true); FirstEmissionBlockNumber::::insert(netuid, 0); SubtokenEnabled::::insert(netuid, true); + + // make interval 1 block so tests can register by stepping 1 block. + BurnHalfLife::::insert(netuid, 1); + BurnIncreaseMult::::insert(netuid, 1); + BurnLastHalvingBlock::::insert(netuid, SubtensorModule::get_current_block_as_u64()); + netuid } @@ -810,8 +871,12 @@ pub fn add_dynamic_network_without_emission_block(hotkey: &U256, coldkey: &U256) RawOrigin::Signed(*coldkey).into(), *hotkey )); + NetworkRegistrationAllowed::::insert(netuid, true); - NetworkPowRegistrationAllowed::::insert(netuid, true); + BurnHalfLife::::insert(netuid, 1); + BurnIncreaseMult::::insert(netuid, 1); + BurnLastHalvingBlock::::insert(netuid, SubtensorModule::get_current_block_as_u64()); + netuid } diff --git a/pallets/subtensor/src/tests/mod.rs b/pallets/subtensor/src/tests/mod.rs index 8f07572e25..7e0c477c56 100644 --- a/pallets/subtensor/src/tests/mod.rs +++ b/pallets/subtensor/src/tests/mod.rs @@ -5,7 +5,6 @@ mod claim_root; mod coinbase; mod consensus; mod delegate_info; -mod difficulty; mod emission; mod ensure; mod epoch; diff --git a/pallets/subtensor/src/tests/networks.rs b/pallets/subtensor/src/tests/networks.rs index f6a50cf4ff..35b55443cd 100644 --- a/pallets/subtensor/src/tests/networks.rs +++ b/pallets/subtensor/src/tests/networks.rs @@ -18,7 +18,28 @@ fn test_registration_ok() { let netuid = NetUid::from(2); let tempo: u16 = 13; let hotkey_account_id: U256 = U256::from(1); - let coldkey_account_id = U256::from(0); // Neighbour of the beast, har har + let coldkey_account_id: U256 = U256::from(0); // Neighbour of the beast, har har + + add_network(netuid, tempo, 0); + + // Ensure reserves exist for any registration path that might touch swap/burn logic. + let reserve: u64 = 1_000_000_000_000; + setup_reserves( + netuid, + TaoBalance::from(reserve), + AlphaBalance::from(reserve), + ); + + // registration economics changed. Ensure the coldkey has enough spendable balance + SubtensorModule::add_balance_to_coldkey_account( + &coldkey_account_id, + TaoBalance::from(reserve), + ); + SubtensorModule::add_balance_to_coldkey_account( + &hotkey_account_id, + TaoBalance::from(reserve), + ); + let (nonce, work): (u64, Vec) = SubtensorModule::create_work_for_block_number( netuid, block_number, @@ -26,9 +47,7 @@ fn test_registration_ok() { &hotkey_account_id, ); - //add network - add_network(netuid, tempo, 0); - + // PoW register should succeed. assert_ok!(SubtensorModule::register( <::RuntimeOrigin>::signed(hotkey_account_id), netuid, @@ -40,8 +59,7 @@ fn test_registration_ok() { )); assert_ok!(SubtensorModule::do_dissolve_network(netuid)); - - assert!(!SubtensorModule::if_subnet_exist(netuid)) + assert!(!SubtensorModule::if_subnet_exist(netuid)); }) } @@ -555,7 +573,6 @@ fn dissolve_clears_all_per_subnet_storages() { // Weights/versioning/targets/limits assert!(!WeightsVersionKey::::contains_key(net)); assert!(!MaxAllowedValidators::::contains_key(net)); - assert!(!AdjustmentInterval::::contains_key(net)); assert!(!BondsMovingAverage::::contains_key(net)); assert!(!BondsPenalty::::contains_key(net)); assert!(!BondsResetOn::::contains_key(net)); @@ -563,7 +580,6 @@ fn dissolve_clears_all_per_subnet_storages() { assert!(!ValidatorPruneLen::::contains_key(net)); assert!(!ScalingLawPower::::contains_key(net)); assert!(!TargetRegistrationsPerInterval::::contains_key(net)); - assert!(!AdjustmentAlpha::::contains_key(net)); assert!(!CommitRevealWeightsEnabled::::contains_key(net)); // Burn/difficulty/adjustment diff --git a/pallets/subtensor/src/tests/registration.rs b/pallets/subtensor/src/tests/registration.rs index 5209204115..f523eacaca 100644 --- a/pallets/subtensor/src/tests/registration.rs +++ b/pallets/subtensor/src/tests/registration.rs @@ -23,1291 +23,395 @@ use crate::{AxonInfoOf, Error}; *********************************************/ #[test] -fn test_registration_difficulty() { +fn test_init_new_network_registration_defaults() { new_test_ext(1).execute_with(|| { - assert_eq!(SubtensorModule::get_difficulty(1.into()).as_u64(), 10000); - }); -} - -#[test] -fn test_registration_invalid_seal_hotkey() { - new_test_ext(1).execute_with(|| { - let block_number: u64 = 0; let netuid = NetUid::from(1); let tempo: u16 = 13; - let hotkey_account_id_1: U256 = U256::from(1); - let hotkey_account_id_2: U256 = U256::from(2); - let coldkey_account_id: U256 = U256::from(667); // Neighbour of the beast, har har - let (nonce, work): (u64, Vec) = SubtensorModule::create_work_for_block_number( - netuid, - block_number, - 0, - &hotkey_account_id_1, - ); - let (nonce2, work2): (u64, Vec) = SubtensorModule::create_work_for_block_number( - netuid, - block_number, - 0, - &hotkey_account_id_1, - ); - //add network - add_network(netuid, tempo, 0); + SubtensorModule::init_new_network(netuid, tempo); - assert_ok!(SubtensorModule::register( - <::RuntimeOrigin>::signed(hotkey_account_id_1), - netuid, - block_number, - nonce, - work.clone(), - hotkey_account_id_1, - coldkey_account_id - )); - let result = SubtensorModule::register( - <::RuntimeOrigin>::signed(hotkey_account_id_2), - netuid, - block_number, - nonce2, - work2.clone(), - hotkey_account_id_2, - coldkey_account_id, + assert_eq!(BurnHalfLife::::get(netuid), 360); + assert_eq!(BurnIncreaseMult::::get(netuid), 2); + + assert_eq!( + SubtensorModule::get_burn(netuid), + TaoBalance::from(RAO_PER_TAO) + ); + + assert_eq!( + BurnLastHalvingBlock::::get(netuid), + SubtensorModule::get_current_block_as_u64() ); - assert_eq!(result, Err(Error::::InvalidSeal.into())); }); } #[test] fn test_registration_ok() { new_test_ext(1).execute_with(|| { - let block_number: u64 = 0; let netuid = NetUid::from(1); let tempo: u16 = 13; - let hotkey_account_id: U256 = U256::from(1); - let coldkey_account_id = U256::from(667); // Neighbour of the beast, har har - let (nonce, work): (u64, Vec) = SubtensorModule::create_work_for_block_number( - netuid, - block_number, - 129123813, - &hotkey_account_id, - ); - //add network add_network(netuid, tempo, 0); + mock::setup_reserves(netuid, DEFAULT_RESERVE.into(), DEFAULT_RESERVE.into()); - // Subscribe and check extrinsic output - assert_ok!(SubtensorModule::register( - <::RuntimeOrigin>::signed(hotkey_account_id), + // Make burn small and stable for this test. + SubtensorModule::set_burn(netuid, 1_000u64.into()); + + let hotkey = U256::from(1); + let coldkey = U256::from(667); + + SubtensorModule::add_balance_to_coldkey_account(&coldkey, 50_000.into()); + + assert_ok!(SubtensorModule::burned_register( + <::RuntimeOrigin>::signed(coldkey), netuid, - block_number, - nonce, - work, - hotkey_account_id, - coldkey_account_id + hotkey )); - // Check if neuron has added to the specified network(netuid) + // neuron inserted assert_eq!(SubtensorModule::get_subnetwork_n(netuid), 1); - //check if hotkey is added to the Hotkeys + // ownership set assert_eq!( - SubtensorModule::get_owning_coldkey_for_hotkey(&hotkey_account_id), - coldkey_account_id + SubtensorModule::get_owning_coldkey_for_hotkey(&hotkey), + coldkey ); - // Check if the neuron has added to the Keys - let neuron_uid = - SubtensorModule::get_uid_for_net_and_hotkey(netuid, &hotkey_account_id).unwrap(); - - assert!(SubtensorModule::get_uid_for_net_and_hotkey(netuid, &hotkey_account_id).is_ok()); - // Check if neuron has added to Uids - let neuro_uid = - SubtensorModule::get_uid_for_net_and_hotkey(netuid, &hotkey_account_id).unwrap(); - assert_eq!(neuro_uid, neuron_uid); + // uid exists + let uid = SubtensorModule::get_uid_for_net_and_hotkey(netuid, &hotkey).unwrap(); + assert_eq!(uid, 0); - // Check if the balance of this hotkey account for this subnetwork == 0 + // no stake by default assert_eq!( - SubtensorModule::get_stake_for_uid_and_subnetwork(netuid, neuron_uid), + SubtensorModule::get_stake_for_uid_and_subnetwork(netuid, uid), AlphaBalance::ZERO ); }); } #[test] -fn test_registration_without_neuron_slot() { +fn test_registration_failed_no_signature() { new_test_ext(1).execute_with(|| { - let block_number: u64 = 0; let netuid = NetUid::from(1); - let tempo: u16 = 13; - let hotkey_account_id: U256 = U256::from(1); - let coldkey_account_id = U256::from(667); // Neighbour of the beast, har har - let (nonce, work): (u64, Vec) = SubtensorModule::create_work_for_block_number( - netuid, - block_number, - 129123813, - &hotkey_account_id, - ); + add_network(netuid, 13, 0); - //add network - add_network(netuid, tempo, 0); - SubtensorModule::set_max_allowed_uids(netuid, 0); + let hotkey = U256::from(1); - assert_noop!( - SubtensorModule::register( - <::RuntimeOrigin>::signed(hotkey_account_id), - netuid, - block_number, - nonce, - work, - hotkey_account_id, - coldkey_account_id - ), - Error::::NoNeuronIdAvailable + let result = SubtensorModule::burned_register( + <::RuntimeOrigin>::none(), + netuid, + hotkey, ); + + assert_eq!(result, Err(sp_runtime::DispatchError::BadOrigin)); }); } #[test] -fn test_registration_under_limit() { +fn test_registration_disabled() { new_test_ext(1).execute_with(|| { let netuid = NetUid::from(1); - let block_number: u64 = 0; - let hotkey_account_id: U256 = U256::from(1); - let coldkey_account_id = U256::from(667); - let who: ::AccountId = hotkey_account_id; + add_network(netuid, 13, 0); - let max_registrants = 2; - SubtensorModule::set_target_registrations_per_interval(netuid, max_registrants); + SubtensorModule::set_network_registration_allowed(netuid, false); - let (nonce, work) = SubtensorModule::create_work_for_block_number( - netuid, - block_number, - 129123813, - &hotkey_account_id, - ); - let work_clone = work.clone(); - let call = crate::Call::register { - netuid, - block_number, - nonce, - work: work_clone, - hotkey: hotkey_account_id, - coldkey: coldkey_account_id, - }; - let info: DispatchInfo = - DispatchInfoOf::<::RuntimeCall>::default(); - let extension = SubtensorTransactionExtension::::new(); - //does not actually call register - let result = extension.validate( - RawOrigin::Signed(who).into(), - &call.into(), - &info, - 10, - (), - &TxBaseImplication(()), - TransactionSource::External, - ); - assert_ok!(result); + let hotkey = U256::from(1); + let coldkey = U256::from(667); - //actually call register - add_network(netuid, 13, 0); - assert_ok!(SubtensorModule::register( - <::RuntimeOrigin>::signed(hotkey_account_id), + let result = SubtensorModule::burned_register( + <::RuntimeOrigin>::signed(coldkey), netuid, - block_number, - nonce, - work, - hotkey_account_id, - coldkey_account_id - )); + hotkey, + ); - let current_registrants = SubtensorModule::get_registrations_this_interval(netuid); - let target_registrants = SubtensorModule::get_target_registrations_per_interval(netuid); - assert!(current_registrants <= target_registrants); + assert_eq!( + result, + Err(Error::::SubNetRegistrationDisabled.into()) + ); }); } #[test] -fn test_registration_rate_limit_exceeded() { +fn test_registration_root_not_permitted() { new_test_ext(1).execute_with(|| { - let netuid = NetUid::from(1); - let block_number: u64 = 0; - let hotkey_account_id: U256 = U256::from(1); - let coldkey_account_id = U256::from(667); - let who: ::AccountId = hotkey_account_id; + let tempo: u16 = 13; + // Ensure root exists in this test env. + SubtensorModule::init_new_network(NetUid::ROOT, tempo); - let target_registrants = 1; - let max_registrants = target_registrants * 3; - SubtensorModule::set_target_registrations_per_interval(netuid, target_registrants); - SubtensorModule::set_registrations_this_interval(netuid, max_registrants); + let hotkey = U256::from(1); + let coldkey = U256::from(2); - let (nonce, work) = SubtensorModule::create_work_for_block_number( - netuid, - block_number, - 129123813, - &hotkey_account_id, - ); - let call = crate::Call::register { - netuid, - block_number, - nonce, - work, - hotkey: hotkey_account_id, - coldkey: coldkey_account_id, - }; - let info: DispatchInfo = - DispatchInfoOf::<::RuntimeCall>::default(); - let extension = SubtensorTransactionExtension::::new(); - let result = extension.validate( - RawOrigin::Signed(who).into(), - &call.into(), - &info, - 10, - (), - &TxBaseImplication(()), - TransactionSource::External, + let result = SubtensorModule::burned_register( + <::RuntimeOrigin>::signed(coldkey), + NetUid::ROOT, + hotkey, ); - // Expectation: The transaction should be rejected assert_eq!( - result.unwrap_err(), - CustomTransactionError::RateLimitExceeded.into() + result, + Err(Error::::RegistrationNotPermittedOnRootSubnet.into()) ); - - let current_registrants = SubtensorModule::get_registrations_this_interval(netuid); - assert!(current_registrants <= max_registrants); }); } -/******************************************** - registration::do_burned_registration tests -*********************************************/ - #[test] -fn test_burned_registration_under_limit() { +fn test_registration_not_enough_balance() { new_test_ext(1).execute_with(|| { let netuid = NetUid::from(1); - let hotkey_account_id: U256 = U256::from(1); - let coldkey_account_id = U256::from(667); - let who: ::AccountId = coldkey_account_id; - let burn_cost = 1000; - // Set the burn cost - SubtensorModule::set_burn(netuid, burn_cost.into()); + add_network(netuid, 13, 0); - let reserve = 1_000_000_000_000_u64; - mock::setup_reserves(netuid, reserve.into(), reserve.into()); + mock::setup_reserves(netuid, DEFAULT_RESERVE.into(), DEFAULT_RESERVE.into()); - add_network(netuid, 13, 0); // Add the network - // Give it some TAO to the coldkey balance; more than the burn cost - SubtensorModule::add_balance_to_coldkey_account( - &coldkey_account_id, - (burn_cost + 10_000).into(), - ); + // burn cost is 10_000, but coldkey only has 9_999. + SubtensorModule::set_burn(netuid, 10_000u64.into()); - let target_registrants = 2; - let max_registrants = target_registrants * 3; // Maximum is 3 times the target - SubtensorModule::set_target_registrations_per_interval(netuid, target_registrants); + let hotkey = U256::from(1); + let coldkey = U256::from(667); - let call_burned_register: crate::Call = crate::Call::burned_register { - netuid, - hotkey: hotkey_account_id, - }; - - let info: DispatchInfo = - DispatchInfoOf::<::RuntimeCall>::default(); - let extension = SubtensorTransactionExtension::::new(); - //does not actually call register - let burned_register_result = extension.validate( - RawOrigin::Signed(who).into(), - &call_burned_register.into(), - &info, - 10, - (), - &TxBaseImplication(()), - TransactionSource::External, - ); - assert_ok!(burned_register_result); + SubtensorModule::add_balance_to_coldkey_account(&coldkey, 9_999.into()); - //actually call register - assert_ok!(SubtensorModule::burned_register( - <::RuntimeOrigin>::signed(coldkey_account_id), + let result = SubtensorModule::burned_register( + <::RuntimeOrigin>::signed(coldkey), netuid, - hotkey_account_id, - )); + hotkey, + ); - let current_registrants = SubtensorModule::get_registrations_this_interval(netuid); - assert!(current_registrants <= max_registrants); + assert_eq!(result, Err(Error::::NotEnoughBalanceToStake.into())); + assert_eq!(SubtensorModule::get_subnetwork_n(netuid), 0); }); } #[test] -fn test_burned_registration_rate_limit_exceeded() { +fn test_registration_non_associated_coldkey() { new_test_ext(1).execute_with(|| { let netuid = NetUid::from(1); - let hotkey_account_id: U256 = U256::from(1); - let coldkey_account_id = U256::from(667); - let who: ::AccountId = coldkey_account_id; + add_network(netuid, 13, 0); - let target_registrants = 1; - let max_registrants = target_registrants * 3; // Maximum is 3 times the target + mock::setup_reserves(netuid, DEFAULT_RESERVE.into(), DEFAULT_RESERVE.into()); + SubtensorModule::set_burn(netuid, 1_000u64.into()); - SubtensorModule::set_target_registrations_per_interval(netuid, target_registrants); - // Set the current registrations to the maximum; should not be able to register more - SubtensorModule::set_registrations_this_interval(netuid, max_registrants); + let hotkey = U256::from(1); + let true_owner = U256::from(111); + let attacker = U256::from(222); - let call_burned_register: crate::Call = crate::Call::burned_register { - netuid, - hotkey: hotkey_account_id, - }; - - let info: DispatchInfo = - DispatchInfoOf::<::RuntimeCall>::default(); - let extension = SubtensorTransactionExtension::::new(); - let burned_register_result = extension.validate( - RawOrigin::Signed(who).into(), - &call_burned_register.into(), - &info, - 10, - (), - &TxBaseImplication(()), - TransactionSource::External, - ); + // Pre-own the hotkey by a different coldkey. + Owner::::insert(hotkey, true_owner); - // Expectation: The transaction should be rejected - assert_eq!( - burned_register_result.unwrap_err(), - CustomTransactionError::RateLimitExceeded.into() + // Attacker has enough funds, but doesn't own the hotkey. + SubtensorModule::add_balance_to_coldkey_account(&attacker, 50_000.into()); + + let result = SubtensorModule::burned_register( + <::RuntimeOrigin>::signed(attacker), + netuid, + hotkey, ); - let current_registrants = SubtensorModule::get_registrations_this_interval(netuid); - assert!(current_registrants <= max_registrants); + assert_eq!(result, Err(Error::::NonAssociatedColdKey.into())); + assert_eq!(SubtensorModule::get_subnetwork_n(netuid), 0); }); } #[test] -fn test_burned_registration_rate_allows_burn_adjustment() { - // We need to be able to register more than the *target* registrations per interval +fn test_registration_without_neuron_slot_doesnt_burn() { new_test_ext(1).execute_with(|| { let netuid = NetUid::from(1); - let hotkey_account_id: U256 = U256::from(1); - let coldkey_account_id = U256::from(667); - let who: ::AccountId = coldkey_account_id; + add_network(netuid, 13, 0); - let burn_cost = 1000; - // Set the burn cost - SubtensorModule::set_burn(netuid, burn_cost.into()); + mock::setup_reserves(netuid, DEFAULT_RESERVE.into(), DEFAULT_RESERVE.into()); + SubtensorModule::set_burn(netuid, 1_000u64.into()); - let reserve = 1_000_000_000_000_u64; - mock::setup_reserves(netuid, reserve.into(), reserve.into()); + let hotkey = U256::from(1); + let coldkey = U256::from(667); - add_network(netuid, 13, 0); // Add the network - // Give it some TAO to the coldkey balance; more than the burn cost - SubtensorModule::add_balance_to_coldkey_account( - &coldkey_account_id, - (burn_cost + 10_000).into(), - ); + SubtensorModule::add_balance_to_coldkey_account(&coldkey, 10_000.into()); + let before = SubtensorModule::get_coldkey_balance(&coldkey); - let target_registrants = 1; // Target is 1, but we can register more than that, up to some maximum. - SubtensorModule::set_target_registrations_per_interval(netuid, target_registrants); - // Set the current registrations to above the target; we should be able to register at least 1 more - SubtensorModule::set_registrations_this_interval(netuid, target_registrants); + // No slots => should fail before burning. + SubtensorModule::set_max_allowed_uids(netuid, 0); - // Register one more, so the current registrations are above the target - let call_burned_register: crate::Call = crate::Call::burned_register { - netuid, - hotkey: hotkey_account_id, - }; - - let info: DispatchInfo = - DispatchInfoOf::<::RuntimeCall>::default(); - let extension = SubtensorTransactionExtension::::new(); - //does not actually call register - let burned_register_result = extension.validate( - RawOrigin::Signed(who).into(), - &call_burned_register.into(), - &info, - 10, - (), - &TxBaseImplication(()), - TransactionSource::External, + assert_noop!( + SubtensorModule::burned_register( + <::RuntimeOrigin>::signed(coldkey), + netuid, + hotkey + ), + Error::::NoNeuronIdAvailable ); - assert_ok!(burned_register_result); - //actually call register - assert_ok!(SubtensorModule::burned_register( - <::RuntimeOrigin>::signed(coldkey_account_id), - netuid, - hotkey_account_id - )); - - let current_registrants = SubtensorModule::get_registrations_this_interval(netuid); - assert!(current_registrants > target_registrants); // Should be able to register more than the target + assert_eq!(SubtensorModule::get_coldkey_balance(&coldkey), before); + assert_eq!(SubtensorModule::get_subnetwork_n(netuid), 0); }); } #[test] -fn test_burned_registration_ok() { +fn test_registration_already_active_hotkey_error() { new_test_ext(1).execute_with(|| { let netuid = NetUid::from(1); - let tempo: u16 = 13; - let hotkey_account_id = U256::from(1); - let burn_cost = 1000; - let coldkey_account_id = U256::from(667); // Neighbour of the beast, har har - //add network - SubtensorModule::set_burn(netuid, burn_cost.into()); - add_network(netuid, tempo, 0); + add_network(netuid, 13, 0); - let reserve = 1_000_000_000_000_u64; - mock::setup_reserves(netuid, reserve.into(), reserve.into()); + mock::setup_reserves(netuid, DEFAULT_RESERVE.into(), DEFAULT_RESERVE.into()); + SubtensorModule::set_burn(netuid, 1_000u64.into()); + + let coldkey = U256::from(667); + let hotkey = U256::from(1); + SubtensorModule::add_balance_to_coldkey_account(&coldkey, 1_000_000.into()); - // Give it some $$$ in his coldkey balance - SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id, 10000.into()); - // Subscribe and check extrinsic output assert_ok!(SubtensorModule::burned_register( - <::RuntimeOrigin>::signed(coldkey_account_id), + <::RuntimeOrigin>::signed(coldkey), netuid, - hotkey_account_id + hotkey )); - // Check if balance has decreased to pay for the burn. - assert_eq!( - SubtensorModule::get_coldkey_balance(&coldkey_account_id), - (10000 - burn_cost).into() - ); // funds drained on reg. - // Check if neuron has added to the specified network(netuid) - assert_eq!(SubtensorModule::get_subnetwork_n(netuid), 1); - //check if hotkey is added to the Hotkeys - assert_eq!( - SubtensorModule::get_owning_coldkey_for_hotkey(&hotkey_account_id), - coldkey_account_id + + // Step to a new interval so we don't hit TooManyRegistrationsThisInterval first. + step_block(1); + + let result = SubtensorModule::burned_register( + <::RuntimeOrigin>::signed(coldkey), + netuid, + hotkey, ); - // Check if the neuron has added to the Keys - let neuron_uid = - SubtensorModule::get_uid_for_net_and_hotkey(netuid, &hotkey_account_id).unwrap(); - assert!(SubtensorModule::get_uid_for_net_and_hotkey(netuid, &hotkey_account_id).is_ok()); - // Check if neuron has added to Uids - let neuro_uid = - SubtensorModule::get_uid_for_net_and_hotkey(netuid, &hotkey_account_id).unwrap(); - assert_eq!(neuro_uid, neuron_uid); - // Check if the balance of this hotkey account for this subnetwork == 0 assert_eq!( - SubtensorModule::get_stake_for_uid_and_subnetwork(netuid, neuron_uid), - AlphaBalance::ZERO + result, + Err(Error::::HotKeyAlreadyRegisteredInSubNet.into()) ); }); } +// ----------------------------- +// Burn price dynamics tests +// ----------------------------- + #[test] -fn test_burn_registration_without_neuron_slot() { +fn test_burn_increases_next_block_after_registration() { new_test_ext(1).execute_with(|| { let netuid = NetUid::from(1); - let tempo: u16 = 13; - let hotkey_account_id = U256::from(1); - let burn_cost = 1000; - let coldkey_account_id = U256::from(667); // Neighbour of the beast, har har - //add network - SubtensorModule::set_burn(netuid, burn_cost.into()); - add_network(netuid, tempo, 0); - // Give it some $$$ in his coldkey balance - SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id, 10000.into()); - SubtensorModule::set_max_allowed_uids(netuid, 0); + add_network(netuid, 13, 0); + mock::setup_reserves(netuid, DEFAULT_RESERVE.into(), DEFAULT_RESERVE.into()); - assert_noop!( - SubtensorModule::burned_register( - <::RuntimeOrigin>::signed(coldkey_account_id), - netuid, - hotkey_account_id - ), - Error::::NoNeuronIdAvailable - ); + // Make behavior deterministic for this test: + // - no interval rollover during the test + // - x2 bump on the block after registration + BurnHalfLife::::insert(netuid, 1_000); + BurnIncreaseMult::::insert(netuid, 2); + BurnLastHalvingBlock::::insert(netuid, SubtensorModule::get_current_block_as_u64()); + + SubtensorModule::set_burn(netuid, 1_000u64.into()); + + let coldkey = U256::from(100); + let hotkey = U256::from(200); + SubtensorModule::add_balance_to_coldkey_account(&coldkey, 1_000_000.into()); + + // Register in this block. Burn itself does not change until next block. + assert_ok!(SubtensorModule::burned_register( + <::RuntimeOrigin>::signed(coldkey), + netuid, + hotkey + )); + assert_eq!(SubtensorModule::get_burn(netuid), 1_000u64.into()); + + // Next block: + // 1) continuous per-block decay floors 1000 -> 999 + // 2) then previous-block registration bump applies: 999 * 2 = 1998 + step_block(1); + + assert_eq!(SubtensorModule::get_burn(netuid), 1_998u64.into()); }); } #[test] -fn test_burn_registration_doesnt_write_on_failure() { +fn test_burn_halves_every_half_life() { new_test_ext(1).execute_with(|| { let netuid = NetUid::from(1); - let tempo: u16 = 13; - let hotkey_account_id = U256::from(1); - let burn_cost = 1000; - let initial_balance = burn_cost * 10; - let coldkey_account_id = U256::from(987); + add_network(netuid, 13, 0); + mock::setup_reserves(netuid, DEFAULT_RESERVE.into(), DEFAULT_RESERVE.into()); - // Add network and set burn cost - add_network(netuid, tempo, 0); - SubtensorModule::set_burn(netuid, burn_cost.into()); - // Give coldkey balance to pay for registration - SubtensorModule::add_balance_to_coldkey_account( - &coldkey_account_id, - initial_balance.into(), - ); - // Set max allowed uids to 0 so registration will fail, but only on last check. - SubtensorModule::set_max_allowed_uids(netuid, 0); + BurnHalfLife::::insert(netuid, 2); + BurnIncreaseMult::::insert(netuid, 1); + BurnLastHalvingBlock::::insert(netuid, SubtensorModule::get_current_block_as_u64()); - // We expect this to fail at the last ensure check. - assert_err!( - SubtensorModule::burned_register( - <::RuntimeOrigin>::signed(coldkey_account_id), - netuid, - hotkey_account_id - ), - Error::::NoNeuronIdAvailable - ); + SubtensorModule::set_burn(netuid, 1_024u64.into()); - // Make sure the coldkey balance is unchanged. - assert_eq!( - SubtensorModule::get_coldkey_balance(&coldkey_account_id), - initial_balance.into() - ); - // Make sure the neuron is not registered. - assert_eq!(SubtensorModule::get_subnetwork_n(netuid), 0); - // Make sure the hotkey is not registered. - assert!(SubtensorModule::get_uid_for_net_and_hotkey(netuid, &hotkey_account_id).is_err()); + step_block(2); + assert_eq!(SubtensorModule::get_burn(netuid), 511u64.into()); + + step_block(2); + assert_eq!(SubtensorModule::get_burn(netuid), 255u64.into()); }); } #[test] -fn test_burn_adjustment() { +fn test_burn_floor_prevents_zero_stuck_and_allows_bump() { new_test_ext(1).execute_with(|| { let netuid = NetUid::from(1); - let tempo: u16 = 13; - let init_burn_cost: u64 = InitialMinBurn::get() + 10_000; - let adjustment_interval = 1; - let target_registrations_per_interval = 1; - add_network(netuid, tempo, 0); - SubtensorModule::set_burn(netuid, init_burn_cost.into()); - SubtensorModule::set_adjustment_interval(netuid, adjustment_interval); - SubtensorModule::set_adjustment_alpha(netuid, 58000); // Set to old value. - SubtensorModule::set_target_registrations_per_interval( - netuid, - target_registrations_per_interval, - ); + add_network(netuid, 13, 0); + mock::setup_reserves(netuid, DEFAULT_RESERVE.into(), DEFAULT_RESERVE.into()); - let reserve = 1_000_000_000_000_u64; - mock::setup_reserves(netuid, reserve.into(), reserve.into()); + // Half-life every block; multiplier 2. + BurnHalfLife::::insert(netuid, 1); + BurnIncreaseMult::::insert(netuid, 2); + BurnLastHalvingBlock::::insert(netuid, SubtensorModule::get_current_block_as_u64()); - // Register key 1. - let hotkey_account_id_1 = U256::from(1); - let coldkey_account_id_1 = U256::from(1); - SubtensorModule::add_balance_to_coldkey_account( - &coldkey_account_id_1, - init_burn_cost.into(), - ); - assert_ok!(SubtensorModule::burned_register( - <::RuntimeOrigin>::signed(hotkey_account_id_1), - netuid, - hotkey_account_id_1 - )); + // Start at 1 => halving would go to 0, but floor keeps it at 1. + SubtensorModule::set_burn(netuid, 1u64.into()); + + // Step one block => halving applies, but floor => burn stays 1. + step_block(1); + assert_eq!(SubtensorModule::get_burn(netuid), 1u64.into()); + + // Register now; bump should apply next block and not be stuck at 0. + let coldkey = U256::from(1); + let hotkey = U256::from(2); + SubtensorModule::add_balance_to_coldkey_account(&coldkey, 10_000.into()); - // Register key 2. - let hotkey_account_id_2 = U256::from(2); - let coldkey_account_id_2 = U256::from(2); - SubtensorModule::add_balance_to_coldkey_account( - &coldkey_account_id_2, - init_burn_cost.into(), - ); assert_ok!(SubtensorModule::burned_register( - <::RuntimeOrigin>::signed(hotkey_account_id_2), + <::RuntimeOrigin>::signed(coldkey), netuid, - hotkey_account_id_2 + hotkey )); - // We are over the number of regs allowed this interval. - // Step the block and trigger the adjustment. step_block(1); - // Check the adjusted burn is above the initial min burn. - assert!(SubtensorModule::get_burn(netuid) > init_burn_cost.into()); - assert_abs_diff_eq!( - SubtensorModule::get_burn(netuid), - (init_burn_cost.saturating_mul(3).saturating_div(2)).into(), // 1.5x - epsilon = 1000.into() - ); + // burn should become 2 (after halving floor then bump) + assert_eq!(SubtensorModule::get_burn(netuid), 2u64.into()); }); } -#[allow(clippy::indexing_slicing)] #[test] -fn test_burn_registration_pruning_scenarios() { +fn test_registration_increases_recycled_rao_per_subnet() { new_test_ext(1).execute_with(|| { let netuid = NetUid::from(1); - let tempo: u16 = 13; - let burn_cost = 1000; - let coldkey_account_id = U256::from(667); - let max_allowed_uids = 6; - let immunity_period = 5000; - - const IS_IMMUNE: bool = true; - const NOT_IMMUNE: bool = false; - - // --- Neutralize the safety floor for this test. - SubtensorModule::set_min_non_immune_uids(netuid, 0); - - // Initial setup - SubtensorModule::set_burn(netuid, burn_cost.into()); - SubtensorModule::set_max_allowed_uids(netuid, max_allowed_uids); - SubtensorModule::set_target_registrations_per_interval(netuid, max_allowed_uids); - SubtensorModule::set_immunity_period(netuid, immunity_period); - - let reserve = 1_000_000_000_000_u64; - mock::setup_reserves(netuid, reserve.into(), reserve.into()); - - add_network(netuid, tempo, 0); - - let mint_balance = burn_cost * max_allowed_uids as u64 + 1_000_000_000; - SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id, mint_balance.into()); - - // Register first half of neurons (uids: 0,1,2); all will be immune initially. - for i in 0..3 { - assert_ok!(SubtensorModule::burned_register( - <::RuntimeOrigin>::signed(coldkey_account_id), - netuid, - U256::from(i) - )); - step_block(1); - } - - // 1) All immune neurons - assert_eq!(SubtensorModule::get_neuron_is_immune(netuid, 0), IS_IMMUNE); - assert_eq!(SubtensorModule::get_neuron_is_immune(netuid, 1), IS_IMMUNE); - assert_eq!(SubtensorModule::get_neuron_is_immune(netuid, 2), IS_IMMUNE); - - // Drive selection with emissions: lowest emission is pruned (among immune if all immune). - // Set: uid0=100, uid1=75, uid2=50 -> expect uid2 - Emission::::mutate(netuid, |v| { - v[0] = 100u64.into(); - v[1] = 75u64.into(); - v[2] = 50u64.into(); - }); - assert_eq!(SubtensorModule::get_neuron_to_prune(netuid), Some(2)); + add_network(netuid, 13, 0); + mock::setup_reserves(netuid, DEFAULT_RESERVE.into(), DEFAULT_RESERVE.into()); - // 2) Tie-breaking for immune neurons: uid1=50, uid2=50 -> earliest registration among {1,2} is uid1 - Emission::::mutate(netuid, |v| { - v[1] = 50u64.into(); - v[2] = 50u64.into(); - }); - assert_eq!(SubtensorModule::get_neuron_to_prune(netuid), Some(1)); + BurnHalfLife::::insert(netuid, 1); // allow 1 reg / block + BurnIncreaseMult::::insert(netuid, 1); // keep burn stable aside from halving + BurnLastHalvingBlock::::insert(netuid, SubtensorModule::get_current_block_as_u64()); + SubtensorModule::set_burn(netuid, 1_000u64.into()); - // 3) Make all three non-immune - step_block(immunity_period); - assert_eq!(SubtensorModule::get_neuron_is_immune(netuid, 0), NOT_IMMUNE); - assert_eq!(SubtensorModule::get_neuron_is_immune(netuid, 1), NOT_IMMUNE); - assert_eq!(SubtensorModule::get_neuron_is_immune(netuid, 2), NOT_IMMUNE); + let coldkey = U256::from(667); + SubtensorModule::add_balance_to_coldkey_account(&coldkey, 1_000_000.into()); - // Among non-immune, choose lowest emission: set uid0=100, uid1=50, uid2=75 -> expect uid1 - Emission::::mutate(netuid, |v| { - v[0] = 100u64.into(); - v[1] = 50u64.into(); - v[2] = 75u64.into(); - }); - assert_eq!(SubtensorModule::get_neuron_to_prune(netuid), Some(1)); - - // 4) Non-immune tie-breaking: uid1=50, uid2=50 -> earliest registration among {1,2} is uid1 - Emission::::mutate(netuid, |v| { - v[1] = 50u64.into(); - v[2] = 50u64.into(); - }); - assert_eq!(SubtensorModule::get_neuron_to_prune(netuid), Some(1)); - - // 5) Mixed immunity: register another 3 immune neurons (uids: 3,4,5) - for i in 3..6 { - assert_ok!(SubtensorModule::burned_register( - <::RuntimeOrigin>::signed(coldkey_account_id), - netuid, - U256::from(i) - )); - step_block(1); - } - - // Ensure new neurons are immune - assert_eq!(SubtensorModule::get_neuron_is_immune(netuid, 3), IS_IMMUNE); - assert_eq!(SubtensorModule::get_neuron_is_immune(netuid, 4), IS_IMMUNE); - assert_eq!(SubtensorModule::get_neuron_is_immune(netuid, 5), IS_IMMUNE); - - // Set emissions: - // non-immune (0..2): [75, 50, 60] -> lowest among non-immune is uid1 - // immune (3..5): [40, 55, 45] -> ignored while a non-immune exists - Emission::::mutate(netuid, |v| { - v[0] = 75u64.into(); - v[1] = 50u64.into(); - v[2] = 60u64.into(); - v[3] = 40u64.into(); - v[4] = 55u64.into(); - v[5] = 45u64.into(); - }); - assert_eq!(SubtensorModule::get_neuron_to_prune(netuid), Some(1)); - - // Remove lowest non-immune by making uid1 emission very high -> next lowest non-immune is uid2 - Emission::::mutate(netuid, |v| { - v[1] = 10_000u64.into(); - }); - assert_eq!(SubtensorModule::get_neuron_to_prune(netuid), Some(2)); - - // If all non-immune are equally high, choose the oldest non-immune -> uid0 - Emission::::mutate(netuid, |v| { - v[0] = 10_000u64.into(); - v[1] = 10_000u64.into(); - v[2] = 10_000u64.into(); - }); - assert_eq!(SubtensorModule::get_neuron_to_prune(netuid), Some(0)); - }); -} - -#[test] -fn test_registration_too_many_registrations_per_block() { - new_test_ext(1).execute_with(|| { - let netuid = NetUid::from(1); - let tempo: u16 = 13; - add_network(netuid, tempo, 0); - SubtensorModule::set_max_registrations_per_block(netuid, 10); - SubtensorModule::set_target_registrations_per_interval(netuid, 10); - assert_eq!(SubtensorModule::get_max_registrations_per_block(netuid), 10); - - let block_number: u64 = 0; - let (nonce0, work0): (u64, Vec) = SubtensorModule::create_work_for_block_number( - netuid, - block_number, - 3942084, - &U256::from(0), - ); - let (nonce1, work1): (u64, Vec) = SubtensorModule::create_work_for_block_number( - netuid, - block_number, - 11231312312, - &U256::from(1), - ); - let (nonce2, work2): (u64, Vec) = SubtensorModule::create_work_for_block_number( - netuid, - block_number, - 212312414, - &U256::from(2), - ); - let (nonce3, work3): (u64, Vec) = SubtensorModule::create_work_for_block_number( - netuid, - block_number, - 21813123, - &U256::from(3), - ); - let (nonce4, work4): (u64, Vec) = SubtensorModule::create_work_for_block_number( - netuid, - block_number, - 148141209, - &U256::from(4), - ); - let (nonce5, work5): (u64, Vec) = SubtensorModule::create_work_for_block_number( - netuid, - block_number, - 1245235534, - &U256::from(5), - ); - let (nonce6, work6): (u64, Vec) = SubtensorModule::create_work_for_block_number( - netuid, - block_number, - 256234, - &U256::from(6), - ); - let (nonce7, work7): (u64, Vec) = SubtensorModule::create_work_for_block_number( - netuid, - block_number, - 6923424, - &U256::from(7), - ); - let (nonce8, work8): (u64, Vec) = SubtensorModule::create_work_for_block_number( - netuid, - block_number, - 124242, - &U256::from(8), - ); - let (nonce9, work9): (u64, Vec) = SubtensorModule::create_work_for_block_number( - netuid, - block_number, - 153453, - &U256::from(9), - ); - let (nonce10, work10): (u64, Vec) = SubtensorModule::create_work_for_block_number( - netuid, - block_number, - 345923888, - &U256::from(10), - ); - assert_eq!(SubtensorModule::get_difficulty_as_u64(netuid), 10000); - - // Subscribe and check extrinsic output - assert_ok!(SubtensorModule::register( - <::RuntimeOrigin>::signed(U256::from(0)), - netuid, - block_number, - nonce0, - work0, - U256::from(0), - U256::from(0) - )); - assert_eq!(SubtensorModule::get_registrations_this_block(netuid), 1); - assert_ok!(SubtensorModule::register( - <::RuntimeOrigin>::signed(U256::from(1)), - netuid, - block_number, - nonce1, - work1, - U256::from(1), - U256::from(1) - )); - assert_eq!(SubtensorModule::get_registrations_this_block(netuid), 2); - assert_ok!(SubtensorModule::register( - <::RuntimeOrigin>::signed(U256::from(2)), - netuid, - block_number, - nonce2, - work2, - U256::from(2), - U256::from(2) - )); - assert_eq!(SubtensorModule::get_registrations_this_block(netuid), 3); - assert_ok!(SubtensorModule::register( - <::RuntimeOrigin>::signed(U256::from(3)), - netuid, - block_number, - nonce3, - work3, - U256::from(3), - U256::from(3) - )); - assert_eq!(SubtensorModule::get_registrations_this_block(netuid), 4); - assert_ok!(SubtensorModule::register( - <::RuntimeOrigin>::signed(U256::from(4)), - netuid, - block_number, - nonce4, - work4, - U256::from(4), - U256::from(4) - )); - assert_eq!(SubtensorModule::get_registrations_this_block(netuid), 5); - assert_ok!(SubtensorModule::register( - <::RuntimeOrigin>::signed(U256::from(5)), - netuid, - block_number, - nonce5, - work5, - U256::from(5), - U256::from(5) - )); - assert_eq!(SubtensorModule::get_registrations_this_block(netuid), 6); - assert_ok!(SubtensorModule::register( - <::RuntimeOrigin>::signed(U256::from(6)), - netuid, - block_number, - nonce6, - work6, - U256::from(6), - U256::from(6) - )); - assert_eq!(SubtensorModule::get_registrations_this_block(netuid), 7); - assert_ok!(SubtensorModule::register( - <::RuntimeOrigin>::signed(U256::from(7)), - netuid, - block_number, - nonce7, - work7, - U256::from(7), - U256::from(7) - )); - assert_eq!(SubtensorModule::get_registrations_this_block(netuid), 8); - assert_ok!(SubtensorModule::register( - <::RuntimeOrigin>::signed(U256::from(8)), - netuid, - block_number, - nonce8, - work8, - U256::from(8), - U256::from(8) - )); - assert_eq!(SubtensorModule::get_registrations_this_block(netuid), 9); - assert_ok!(SubtensorModule::register( - <::RuntimeOrigin>::signed(U256::from(9)), - netuid, - block_number, - nonce9, - work9, - U256::from(9), - U256::from(9) - )); - assert_eq!(SubtensorModule::get_registrations_this_block(netuid), 10); - let result = SubtensorModule::register( - <::RuntimeOrigin>::signed(U256::from(10)), - netuid, - block_number, - nonce10, - work10, - U256::from(10), - U256::from(10), - ); - assert_eq!( - result, - Err(Error::::TooManyRegistrationsThisBlock.into()) - ); - }); -} - -#[test] -fn test_registration_too_many_registrations_per_interval() { - new_test_ext(1).execute_with(|| { - let netuid = NetUid::from(1); - let tempo: u16 = 13; - add_network(netuid, tempo, 0); - SubtensorModule::set_max_registrations_per_block(netuid, 11); - assert_eq!(SubtensorModule::get_max_registrations_per_block(netuid), 11); - SubtensorModule::set_target_registrations_per_interval(netuid, 3); - assert_eq!( - SubtensorModule::get_target_registrations_per_interval(netuid), - 3 - ); - // Then the max is 3 * 3 = 9 - - let block_number: u64 = 0; - let (nonce0, work0): (u64, Vec) = SubtensorModule::create_work_for_block_number( - netuid, - block_number, - 3942084, - &U256::from(0), - ); - let (nonce1, work1): (u64, Vec) = SubtensorModule::create_work_for_block_number( - netuid, - block_number, - 11231312312, - &U256::from(1), - ); - let (nonce2, work2): (u64, Vec) = SubtensorModule::create_work_for_block_number( - netuid, - block_number, - 212312414, - &U256::from(2), - ); - let (nonce3, work3): (u64, Vec) = SubtensorModule::create_work_for_block_number( - netuid, - block_number, - 21813123, - &U256::from(3), - ); - let (nonce4, work4): (u64, Vec) = SubtensorModule::create_work_for_block_number( - netuid, - block_number, - 148141209, - &U256::from(4), - ); - let (nonce5, work5): (u64, Vec) = SubtensorModule::create_work_for_block_number( - netuid, - block_number, - 1245235534, - &U256::from(5), - ); - let (nonce6, work6): (u64, Vec) = SubtensorModule::create_work_for_block_number( - netuid, - block_number, - 256234, - &U256::from(6), - ); - let (nonce7, work7): (u64, Vec) = SubtensorModule::create_work_for_block_number( - netuid, - block_number, - 6923424, - &U256::from(7), - ); - let (nonce8, work8): (u64, Vec) = SubtensorModule::create_work_for_block_number( - netuid, - block_number, - 124242, - &U256::from(8), - ); - let (nonce9, work9): (u64, Vec) = SubtensorModule::create_work_for_block_number( - netuid, - block_number, - 153453, - &U256::from(9), - ); - assert_eq!(SubtensorModule::get_difficulty_as_u64(netuid), 10000); - - // Subscribe and check extrinsic output - // Try 10 registrations, this is less than the max per block, but more than the max per interval - assert_ok!(SubtensorModule::register( - <::RuntimeOrigin>::signed(U256::from(0)), - netuid, - block_number, - nonce0, - work0, - U256::from(0), - U256::from(0) - )); - assert_eq!(SubtensorModule::get_registrations_this_interval(netuid), 1); - assert_ok!(SubtensorModule::register( - <::RuntimeOrigin>::signed(U256::from(1)), + // First registration + let burn1 = SubtensorModule::get_burn(netuid); + assert_ok!(SubtensorModule::burned_register( + <::RuntimeOrigin>::signed(coldkey), netuid, - block_number, - nonce1, - work1, - U256::from(1), U256::from(1) - )); - assert_eq!(SubtensorModule::get_registrations_this_interval(netuid), 2); - assert_ok!(SubtensorModule::register( - <::RuntimeOrigin>::signed(U256::from(2)), - netuid, - block_number, - nonce2, - work2, - U256::from(2), - U256::from(2) - )); - assert_eq!(SubtensorModule::get_registrations_this_interval(netuid), 3); - assert_ok!(SubtensorModule::register( - <::RuntimeOrigin>::signed(U256::from(3)), - netuid, - block_number, - nonce3, - work3, - U256::from(3), - U256::from(3) - )); - assert_eq!(SubtensorModule::get_registrations_this_interval(netuid), 4); - assert_ok!(SubtensorModule::register( - <::RuntimeOrigin>::signed(U256::from(4)), - netuid, - block_number, - nonce4, - work4, - U256::from(4), - U256::from(4) - )); - assert_eq!(SubtensorModule::get_registrations_this_interval(netuid), 5); - assert_ok!(SubtensorModule::register( - <::RuntimeOrigin>::signed(U256::from(5)), - netuid, - block_number, - nonce5, - work5, - U256::from(5), - U256::from(5) - )); - assert_eq!(SubtensorModule::get_registrations_this_interval(netuid), 6); - assert_ok!(SubtensorModule::register( - <::RuntimeOrigin>::signed(U256::from(6)), - netuid, - block_number, - nonce6, - work6, - U256::from(6), - U256::from(6) - )); - assert_eq!(SubtensorModule::get_registrations_this_interval(netuid), 7); - assert_ok!(SubtensorModule::register( - <::RuntimeOrigin>::signed(U256::from(7)), - netuid, - block_number, - nonce7, - work7, - U256::from(7), - U256::from(7) - )); - assert_eq!(SubtensorModule::get_registrations_this_interval(netuid), 8); - assert_ok!(SubtensorModule::register( - <::RuntimeOrigin>::signed(U256::from(8)), - netuid, - block_number, - nonce8, - work8, - U256::from(8), - U256::from(8) - )); - assert_eq!(SubtensorModule::get_registrations_this_interval(netuid), 9); - let result = SubtensorModule::register( - <::RuntimeOrigin>::signed(U256::from(9)), - netuid, - block_number, - nonce9, - work9, - U256::from(9), - U256::from(9), - ); - assert_eq!( - result, - Err(Error::::TooManyRegistrationsThisInterval.into()) - ); - }); -} - -#[test] -fn test_registration_immunity_period() { //impl this test when epoch impl and calculating pruning score is done - // TODO: Implement this test -} - -#[test] -fn test_registration_already_active_hotkey() { - new_test_ext(1).execute_with(|| { - let block_number: u64 = 0; - let netuid = NetUid::from(1); - let tempo: u16 = 13; - let hotkey_account_id = U256::from(1); - let coldkey_account_id = U256::from(667); - let (nonce, work): (u64, Vec) = SubtensorModule::create_work_for_block_number( - netuid, - block_number, - 0, - &hotkey_account_id, - ); - - //add network - add_network(netuid, tempo, 0); - - assert_ok!(SubtensorModule::register( - <::RuntimeOrigin>::signed(hotkey_account_id), - netuid, - block_number, - nonce, - work, - hotkey_account_id, - coldkey_account_id - )); - - let block_number: u64 = 0; - let hotkey_account_id = U256::from(1); - let coldkey_account_id = U256::from(667); - let (nonce, work): (u64, Vec) = SubtensorModule::create_work_for_block_number( - netuid, - block_number, - 0, - &hotkey_account_id, - ); - let result = SubtensorModule::register( - <::RuntimeOrigin>::signed(hotkey_account_id), - netuid, - block_number, - nonce, - work, - hotkey_account_id, - coldkey_account_id, - ); - assert_eq!( - result, - Err(Error::::HotKeyAlreadyRegisteredInSubNet.into()) - ); - }); -} - -#[test] -fn test_registration_invalid_seal() { - new_test_ext(1).execute_with(|| { - let block_number: u64 = 0; - let netuid = NetUid::from(1); - let tempo: u16 = 13; - let hotkey_account_id = U256::from(1); - let coldkey_account_id = U256::from(667); - let (nonce, work): (u64, Vec) = - SubtensorModule::create_work_for_block_number(netuid, 1, 0, &hotkey_account_id); - - //add network - add_network(netuid, tempo, 0); - - let result = SubtensorModule::register( - <::RuntimeOrigin>::signed(hotkey_account_id), - netuid, - block_number, - nonce, - work, - hotkey_account_id, - coldkey_account_id, - ); - assert_eq!(result, Err(Error::::InvalidSeal.into())); - }); -} - -#[test] -fn test_registration_invalid_block_number() { - new_test_ext(1).execute_with(|| { - System::set_block_number(0); - let block_number: u64 = 1; - let netuid = NetUid::from(1); - let tempo: u16 = 13; - let hotkey_account_id = U256::from(1); - let coldkey_account_id = U256::from(667); - let (nonce, work): (u64, Vec) = SubtensorModule::create_work_for_block_number( - netuid, - block_number, - 0, - &hotkey_account_id, - ); - - //add network - add_network(netuid, tempo, 0); - - let result = SubtensorModule::register( - <::RuntimeOrigin>::signed(hotkey_account_id), - netuid, - block_number, - nonce, - work, - hotkey_account_id, - coldkey_account_id, - ); - assert_eq!(result, Err(Error::::InvalidWorkBlock.into())); - }); -} - -#[test] -fn test_registration_invalid_difficulty() { - new_test_ext(1).execute_with(|| { - let block_number: u64 = 0; - let netuid = NetUid::from(1); - let tempo: u16 = 13; - let hotkey_account_id = U256::from(1); - let coldkey_account_id = U256::from(667); - let (nonce, work): (u64, Vec) = SubtensorModule::create_work_for_block_number( - netuid, - block_number, - 0, - &hotkey_account_id, - ); - - //add network - add_network(netuid, tempo, 0); - - SubtensorModule::set_difficulty(netuid, 18_446_744_073_709_551_615u64); - - let result = SubtensorModule::register( - <::RuntimeOrigin>::signed(hotkey_account_id), - netuid, - block_number, - nonce, - work, - hotkey_account_id, - coldkey_account_id, - ); - assert_eq!(result, Err(Error::::InvalidDifficulty.into())); - }); -} - -#[test] -fn test_registration_failed_no_signature() { - new_test_ext(1).execute_with(|| { - let block_number: u64 = 1; - let netuid = NetUid::from(1); - let hotkey_account_id = U256::from(1); - let coldkey_account_id = U256::from(667); // Neighbour of the beast, har har - let (nonce, work): (u64, Vec) = SubtensorModule::create_work_for_block_number( - netuid, - block_number, - 0, - &hotkey_account_id, - ); + )); + assert_eq!(SubtensorModule::get_rao_recycled(netuid), burn1); - // Subscribe and check extrinsic output - let result = SubtensorModule::register( - <::RuntimeOrigin>::none(), + // Next interval + step_block(1); + + // Second registration (burn may have changed; read it) + let burn2 = SubtensorModule::get_burn(netuid); + assert_ok!(SubtensorModule::burned_register( + <::RuntimeOrigin>::signed(coldkey), netuid, - block_number, - nonce, - work, - hotkey_account_id, - coldkey_account_id, - ); - assert_eq!(result, Err(DispatchError::BadOrigin)); + U256::from(2) + )); + + assert_eq!(SubtensorModule::get_rao_recycled(netuid), burn1 + burn2); }); } @@ -1318,23 +422,23 @@ fn test_registration_get_uid_to_prune_all_in_immunity_period() { System::set_block_number(0); let netuid = NetUid::from(1); add_network(netuid, 1, 0); + mock::setup_reserves(netuid, DEFAULT_RESERVE.into(), DEFAULT_RESERVE.into()); // Neutralize safety floor and owner-immortality for deterministic selection SubtensorModule::set_min_non_immune_uids(netuid, 0); ImmuneOwnerUidsLimit::::insert(netuid, 0); - // Make sure the subnet owner is not one of the test coldkeys SubnetOwner::::insert(netuid, U256::from(999_999u64)); - register_ok_neuron(netuid, U256::from(0), U256::from(0), 39420842); - register_ok_neuron(netuid, U256::from(1), U256::from(1), 12412392); + // Register uid0 @ block 0 + register_ok_neuron(netuid, U256::from(0), U256::from(0), 0); + + // Move to next block so interval resets; register uid1 @ block 1 + step_block(1); + register_ok_neuron(netuid, U256::from(1), U256::from(1), 0); + // Both immune with immunity_period=2 at current block=1. SubtensorModule::set_immunity_period(netuid, 2); - assert_eq!(SubtensorModule::get_immunity_period(netuid), 2); - assert_eq!(SubtensorModule::get_current_block_as_u64(), 0); - assert_eq!( - SubtensorModule::get_neuron_block_at_registration(netuid, 0), - 0 - ); + assert_eq!(SubtensorModule::get_current_block_as_u64(), 1); // Both immune; prune lowest emission among immune (uid0=100, uid1=110 => uid0) Emission::::mutate(netuid, |v| { @@ -1353,28 +457,26 @@ fn test_registration_get_uid_to_prune_none_in_immunity_period() { System::set_block_number(0); let netuid = NetUid::from(1); add_network(netuid, 1, 0); + mock::setup_reserves(netuid, DEFAULT_RESERVE.into(), DEFAULT_RESERVE.into()); // Neutralize safety floor and owner-immortality for deterministic selection SubtensorModule::set_min_non_immune_uids(netuid, 0); ImmuneOwnerUidsLimit::::insert(netuid, 0); SubnetOwner::::insert(netuid, U256::from(999_999u64)); - register_ok_neuron(netuid, U256::from(0), U256::from(0), 39420842); - register_ok_neuron(netuid, U256::from(1), U256::from(1), 12412392); + // Register uid0 @ block 0 + register_ok_neuron(netuid, U256::from(0), U256::from(0), 0); + + // Register uid1 @ block 1 + step_block(1); + register_ok_neuron(netuid, U256::from(1), U256::from(1), 0); SubtensorModule::set_immunity_period(netuid, 2); - assert_eq!(SubtensorModule::get_immunity_period(netuid), 2); - assert_eq!(SubtensorModule::get_current_block_as_u64(), 0); - assert_eq!( - SubtensorModule::get_neuron_block_at_registration(netuid, 0), - 0 - ); - // Advance beyond immunity -> both non-immune + // Advance beyond immunity -> both non-immune (current=1, step 3 => 4) step_block(3); - assert_eq!(SubtensorModule::get_current_block_as_u64(), 3); + assert_eq!(SubtensorModule::get_current_block_as_u64(), 4); - // Among non-immune, lowest emission pruned: uid0=100, uid1=110 -> expect uid0 Emission::::mutate(netuid, |v| { v[0] = 100u64.into(); v[1] = 110u64.into(); @@ -1384,31 +486,28 @@ fn test_registration_get_uid_to_prune_none_in_immunity_period() { }); } -// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::registration::test_registration_get_uid_to_prune_owner_immortality --exact --show-output --nocapture +// These owner-immortality tests do not depend on registration paths; keep as-is. + #[test] fn test_registration_get_uid_to_prune_owner_immortality() { new_test_ext(1).execute_with(|| { [ - // Limit = 1: only the earliest owner hotkey is immortal -> prune the other owner hotkey (uid 1) - (1, 1), - // Limit = 2: both owner hotkeys are immortal -> prune the non-owner (uid 2) - (2, 2), + (1, 1), // limit=1 => prune other owner hk (uid1) + (2, 2), // limit=2 => both owner hks immortal => prune non-owner (uid2) ] .iter() .for_each(|(limit, uid_to_prune)| { let subnet_owner_ck = U256::from(0); let subnet_owner_hk = U256::from(1); - // Other hk owned by owner let other_owner_hk = U256::from(2); Owner::::insert(other_owner_hk, subnet_owner_ck); OwnedHotkeys::::insert(subnet_owner_ck, vec![subnet_owner_hk, other_owner_hk]); - // Another hk not owned by owner let non_owner_hk = U256::from(3); let netuid = add_dynamic_network(&subnet_owner_hk, &subnet_owner_ck); - // Make sure registration blocks are set + BlockAtRegistration::::insert(netuid, 1, 1); BlockAtRegistration::::insert(netuid, 2, 2); Uids::::insert(netuid, other_owner_hk, 1); @@ -1418,16 +517,12 @@ fn test_registration_get_uid_to_prune_owner_immortality() { ImmunityPeriod::::insert(netuid, 1); SubnetworkN::::insert(netuid, 3); - // Neutralize safety floor for this test SubtensorModule::set_min_non_immune_uids(netuid, 0); step_block(10); // all non-immune - // Configure the number of immortal owner UIDs ImmuneOwnerUidsLimit::::insert(netuid, *limit); - // Drive selection by emissions (lowest first) - // uid0=0, uid1=0, uid2=1 Emission::::insert( netuid, vec![AlphaBalance::from(0), 0u64.into(), 1u64.into()], @@ -1441,7 +536,6 @@ fn test_registration_get_uid_to_prune_owner_immortality() { }); } -// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::registration::test_registration_get_uid_to_prune_owner_immortality_all_immune --exact --show-output --nocapture #[test] fn test_registration_get_uid_to_prune_owner_immortality_all_immune() { new_test_ext(1).execute_with(|| { @@ -1450,15 +544,13 @@ fn test_registration_get_uid_to_prune_owner_immortality_all_immune() { let subnet_owner_ck = U256::from(0); let subnet_owner_hk = U256::from(1); - // Other hk owned by owner let other_owner_hk = U256::from(2); Owner::::insert(other_owner_hk, subnet_owner_ck); OwnedHotkeys::::insert(subnet_owner_ck, vec![subnet_owner_hk, other_owner_hk]); - // Another hk not owned by owner let non_owner_hk = U256::from(3); - let netuid = add_dynamic_network(&subnet_owner_hk, &subnet_owner_ck); + BlockAtRegistration::::insert(netuid, 0, 12); BlockAtRegistration::::insert(netuid, 1, 11); BlockAtRegistration::::insert(netuid, 2, 10); @@ -1469,14 +561,12 @@ fn test_registration_get_uid_to_prune_owner_immortality_all_immune() { ImmunityPeriod::::insert(netuid, 100); SubnetworkN::::insert(netuid, 3); - // Neutralize safety floor for this test SubtensorModule::set_min_non_immune_uids(netuid, 0); step_block(20); // all still immune ImmuneOwnerUidsLimit::::insert(netuid, limit); - // Lowest emission among non-immortal candidates -> uid2 Emission::::insert( netuid, vec![AlphaBalance::from(0), 0u64.into(), 1u64.into()], @@ -1493,32 +583,21 @@ fn test_registration_get_uid_to_prune_owner_immortality_all_immune() { fn test_registration_get_neuron_metadata() { new_test_ext(1).execute_with(|| { let netuid = NetUid::from(1); - let block_number: u64 = 0; - let tempo: u16 = 13; - let hotkey_account_id = U256::from(1); - let coldkey_account_id = U256::from(667); - let (nonce0, work0): (u64, Vec) = SubtensorModule::create_work_for_block_number( - netuid, - block_number, - 3942084, - &hotkey_account_id, - ); + add_network(netuid, 13, 0); + mock::setup_reserves(netuid, DEFAULT_RESERVE.into(), DEFAULT_RESERVE.into()); + SubtensorModule::set_burn(netuid, 1_000u64.into()); - add_network(netuid, tempo, 0); + let hotkey = U256::from(1); + let coldkey = U256::from(667); + SubtensorModule::add_balance_to_coldkey_account(&coldkey, 100_000.into()); - assert_ok!(SubtensorModule::register( - <::RuntimeOrigin>::signed(hotkey_account_id), + assert_ok!(SubtensorModule::burned_register( + <::RuntimeOrigin>::signed(coldkey), netuid, - block_number, - nonce0, - work0, - hotkey_account_id, - coldkey_account_id + hotkey )); - // - //let neuron_id = SubtensorModule::get_uid_for_net_and_hotkey(netuid, &hotkey_account_id); - // let neuron_uid = SubtensorModule::get_uid_for_net_and_hotkey( netuid, &hotkey_account_id ).unwrap(); - let neuron: AxonInfoOf = SubtensorModule::get_axon_info(netuid, &hotkey_account_id); + + let neuron: AxonInfoOf = SubtensorModule::get_axon_info(netuid, &hotkey); assert_eq!(neuron.ip, 0); assert_eq!(neuron.version, 0); assert_eq!(neuron.port, 0); @@ -1526,361 +605,123 @@ fn test_registration_get_neuron_metadata() { } #[test] -fn test_registration_add_network_size() { +fn test_last_update_correctness() { new_test_ext(1).execute_with(|| { let netuid = NetUid::from(1); - let netuid2 = NetUid::from(2); - let block_number: u64 = 0; + let tempo: u16 = 13; let hotkey_account_id = U256::from(1); - let hotkey_account_id1 = U256::from(2); - let hotkey_account_id2 = U256::from(3); - let (nonce0, work0): (u64, Vec) = SubtensorModule::create_work_for_block_number( - netuid, - block_number, - 3942084, - &hotkey_account_id, - ); - let (nonce1, work1): (u64, Vec) = SubtensorModule::create_work_for_block_number( - netuid2, - block_number, - 11231312312, - &hotkey_account_id1, - ); - let (nonce2, work2): (u64, Vec) = SubtensorModule::create_work_for_block_number( - netuid2, - block_number, - 21813123, - &hotkey_account_id2, - ); - let coldkey_account_id = U256::from(667); + let burn_cost: u64 = 1_000; + let coldkey_account_id = U256::from(667); // Neighbour of the beast, har har - add_network(netuid, 13, 0); - assert_eq!(SubtensorModule::get_subnetwork_n(netuid), 0); + // Add network first, then override burn so init_new_network doesn't clobber it. + add_network(netuid, tempo, 0); + SubtensorModule::set_burn(netuid, burn_cost.into()); - add_network(netuid2, 13, 0); - assert_eq!(SubtensorModule::get_subnetwork_n(netuid2), 0); + let reserve: u64 = 1_000_000_000_000; + mock::setup_reserves(netuid, reserve.into(), reserve.into()); - assert_ok!(SubtensorModule::register( - <::RuntimeOrigin>::signed(hotkey_account_id), + // Simulate existing neurons + let existing_neurons = 3; + SubnetworkN::::insert(netuid, existing_neurons); + + // Simulate no LastUpdate so far (can happen on mechanisms) + LastUpdate::::remove(NetUidStorageIndex::from(netuid)); + + // Give enough balance for the burn path. + SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id, 10_000.into()); + + // Register and ensure LastUpdate is expanded correctly. + assert_ok!(SubtensorModule::burned_register( + <::RuntimeOrigin>::signed(coldkey_account_id), netuid, - block_number, - nonce0, - work0, - hotkey_account_id, - coldkey_account_id + hotkey_account_id )); - assert_eq!(SubtensorModule::get_subnetwork_n(netuid), 1); - assert_eq!(SubtensorModule::get_registrations_this_interval(netuid), 1); - assert_ok!(SubtensorModule::register( - <::RuntimeOrigin>::signed(hotkey_account_id1), - netuid2, - block_number, - nonce1, - work1, - hotkey_account_id1, - coldkey_account_id - )); - assert_ok!(SubtensorModule::register( - <::RuntimeOrigin>::signed(hotkey_account_id2), - netuid2, - block_number, - nonce2, - work2, - hotkey_account_id2, - coldkey_account_id - )); - assert_eq!(SubtensorModule::get_subnetwork_n(netuid2), 2); - assert_eq!(SubtensorModule::get_registrations_this_interval(netuid2), 2); + assert_eq!( + LastUpdate::::get(NetUidStorageIndex::from(netuid)).len(), + (existing_neurons + 1) as usize + ); }); } +#[allow(clippy::indexing_slicing)] #[test] -fn test_burn_registration_increase_recycled_rao() { +fn test_registration_pruning_1() { new_test_ext(1).execute_with(|| { - let netuid = NetUid::from(1); - let netuid2 = NetUid::from(2); + let netuid = NetUid::from(5); + add_network(netuid, 10_000, 0); + mock::setup_reserves(netuid, DEFAULT_RESERVE.into(), DEFAULT_RESERVE.into()); - let hotkey_account_id = U256::from(1); - let coldkey_account_id = U256::from(667); + ImmuneOwnerUidsLimit::::insert(netuid, 0); - // Give funds for burn. 1000 TAO - let _ = - Balances::deposit_creating(&coldkey_account_id, Balance::from(1_000_000_000_000_u64)); + MaxRegistrationsPerBlock::::insert(netuid, 1024); + SubtensorModule::set_max_allowed_uids(netuid, 3); - let reserve = 1_000_000_000_000_u64; - mock::setup_reserves(netuid, reserve.into(), reserve.into()); - mock::setup_reserves(netuid2, reserve.into(), reserve.into()); + let coldkeys = [U256::from(20_001), U256::from(20_002), U256::from(20_003)]; + let hotkeys = [U256::from(30_001), U256::from(30_002), U256::from(30_003)]; - add_network(netuid, 13, 0); - assert_eq!(SubtensorModule::get_subnetwork_n(netuid), 0); + for i in 0..3 { + register_ok_neuron(netuid, hotkeys[i], coldkeys[i], 0); + step_block(1); + } - add_network(netuid2, 13, 0); - assert_eq!(SubtensorModule::get_subnetwork_n(netuid2), 0); + let uid0 = SubtensorModule::get_uid_for_net_and_hotkey(netuid, &hotkeys[0]).unwrap(); + let uid1 = SubtensorModule::get_uid_for_net_and_hotkey(netuid, &hotkeys[1]).unwrap(); + let uid2 = SubtensorModule::get_uid_for_net_and_hotkey(netuid, &hotkeys[2]).unwrap(); - run_to_block(1); + assert_eq!(uid0, 0); + assert_eq!(uid1, 1); + assert_eq!(uid2, 2); + assert_eq!(SubtensorModule::get_subnetwork_n(netuid), 3); - let burn_amount = SubtensorModule::get_burn(netuid); - assert_ok!(SubtensorModule::burned_register( - <::RuntimeOrigin>::signed(hotkey_account_id), - netuid, - hotkey_account_id - )); - assert_eq!(SubtensorModule::get_rao_recycled(netuid), burn_amount); + let now: u64 = 1_000; + frame_system::Pallet::::set_block_number(now); - run_to_block(2); + SubtensorModule::set_immunity_period(netuid, 100); - let burn_amount2 = SubtensorModule::get_burn(netuid2); - assert_ok!(SubtensorModule::burned_register( - <::RuntimeOrigin>::signed(hotkey_account_id), - netuid2, - hotkey_account_id - )); - assert_ok!(SubtensorModule::burned_register( - <::RuntimeOrigin>::signed(U256::from(2)), - netuid2, - U256::from(2) - )); - assert_eq!( - SubtensorModule::get_rao_recycled(netuid2), - burn_amount2 * 2.into() - ); - // Validate netuid is not affected. - assert_eq!(SubtensorModule::get_rao_recycled(netuid), burn_amount); - }); -} + BlockAtRegistration::::insert(netuid, uid0, now - 150); + BlockAtRegistration::::insert(netuid, uid1, now - 200); + BlockAtRegistration::::insert(netuid, uid2, now - 10); -#[test] -fn test_full_pass_through() { - new_test_ext(1).execute_with(|| { - // Create 3 networks. - let netuid0 = NetUid::from(1); - let netuid1 = NetUid::from(2); - let netuid2 = NetUid::from(3); - - // With 3 tempos - let tempo0: u16 = 2; - let tempo1: u16 = 2; - let tempo2: u16 = 2; - - // Create 3 keys. - let hotkey0 = U256::from(0); - let hotkey1 = U256::from(1); - let hotkey2 = U256::from(2); - - // With 3 different coldkeys. - let coldkey0 = U256::from(0); - let coldkey1 = U256::from(1); - let coldkey2 = U256::from(2); - - // Add the 3 networks. - add_network(netuid0, tempo0, 0); - add_network(netuid1, tempo1, 0); - add_network(netuid2, tempo2, 0); - - // owners are not deregisterd - let dummy_owner = U256::from(99999); - crate::SubnetOwner::::insert(netuid0, dummy_owner); - crate::SubnetOwner::::insert(netuid1, dummy_owner); - crate::SubnetOwner::::insert(netuid2, dummy_owner); - - // Check their tempo. - assert_eq!(SubtensorModule::get_tempo(netuid0), tempo0); - assert_eq!(SubtensorModule::get_tempo(netuid1), tempo1); - assert_eq!(SubtensorModule::get_tempo(netuid2), tempo2); - - // Set their max allowed uids. - SubtensorModule::set_max_allowed_uids(netuid0, 2); - SubtensorModule::set_max_allowed_uids(netuid1, 2); - SubtensorModule::set_max_allowed_uids(netuid2, 2); - - // Check their max allowed. - assert_eq!(SubtensorModule::get_max_allowed_uids(netuid0), 2); - assert_eq!(SubtensorModule::get_max_allowed_uids(netuid0), 2); - assert_eq!(SubtensorModule::get_max_allowed_uids(netuid0), 2); - - // Set the max registration per block. - SubtensorModule::set_max_registrations_per_block(netuid0, 3); - SubtensorModule::set_max_registrations_per_block(netuid1, 3); - SubtensorModule::set_max_registrations_per_block(netuid2, 3); - assert_eq!(SubtensorModule::get_max_registrations_per_block(netuid0), 3); - assert_eq!(SubtensorModule::get_max_registrations_per_block(netuid1), 3); - assert_eq!(SubtensorModule::get_max_registrations_per_block(netuid2), 3); - - // Check that no one has registered yet. - assert_eq!(SubtensorModule::get_subnetwork_n(netuid0), 0); - assert_eq!(SubtensorModule::get_subnetwork_n(netuid1), 0); - assert_eq!(SubtensorModule::get_subnetwork_n(netuid2), 0); - - // Registered the keys to all networks. - register_ok_neuron(netuid0, hotkey0, coldkey0, 39420842); - register_ok_neuron(netuid0, hotkey1, coldkey1, 12412392); - register_ok_neuron(netuid1, hotkey0, coldkey0, 21813123); - register_ok_neuron(netuid1, hotkey1, coldkey1, 25755207); - register_ok_neuron(netuid2, hotkey0, coldkey0, 251232207); - register_ok_neuron(netuid2, hotkey1, coldkey1, 159184122); - - // Check uids. - // n0 [ h0, h1 ] - // n1 [ h0, h1 ] - // n2 [ h0, h1 ] - assert_eq!( - SubtensorModule::get_hotkey_for_net_and_uid(netuid0, 0).unwrap(), - hotkey0 - ); - assert_eq!( - SubtensorModule::get_hotkey_for_net_and_uid(netuid1, 0).unwrap(), - hotkey0 - ); - assert_eq!( - SubtensorModule::get_hotkey_for_net_and_uid(netuid2, 0).unwrap(), - hotkey0 - ); - assert_eq!( - SubtensorModule::get_hotkey_for_net_and_uid(netuid0, 1).unwrap(), - hotkey1 - ); - assert_eq!( - SubtensorModule::get_hotkey_for_net_and_uid(netuid1, 1).unwrap(), - hotkey1 - ); - assert_eq!( - SubtensorModule::get_hotkey_for_net_and_uid(netuid2, 1).unwrap(), - hotkey1 - ); + assert!(!SubtensorModule::get_neuron_is_immune(netuid, uid0)); + assert!(!SubtensorModule::get_neuron_is_immune(netuid, uid1)); + assert!(SubtensorModule::get_neuron_is_immune(netuid, uid2)); - // Check registered networks. - // assert!( SubtensorModule::get_registered_networks_for_hotkey( &hotkey0 ).contains( &netuid0 ) ); - // assert!( SubtensorModule::get_registered_networks_for_hotkey( &hotkey0 ).contains( &netuid1 ) ); - // assert!( SubtensorModule::get_registered_networks_for_hotkey( &hotkey0 ).contains( &netuid2 ) ); - // assert!( SubtensorModule::get_registered_networks_for_hotkey( &hotkey1 ).contains( &netuid0 ) ); - // assert!( SubtensorModule::get_registered_networks_for_hotkey( &hotkey1 ).contains( &netuid1 ) ); - // assert!( SubtensorModule::get_registered_networks_for_hotkey( &hotkey1 ).contains( &netuid2 ) ); - // assert!( !SubtensorModule::get_registered_networks_for_hotkey( &hotkey2 ).contains( &netuid0 ) ); - // assert!( !SubtensorModule::get_registered_networks_for_hotkey( &hotkey2 ).contains( &netuid1 ) ); - // assert!( !SubtensorModule::get_registered_networks_for_hotkey( &hotkey2 ).contains( &netuid2 ) ); - - // Check the number of registrations. - assert_eq!(SubtensorModule::get_registrations_this_interval(netuid0), 2); - assert_eq!(SubtensorModule::get_registrations_this_interval(netuid1), 2); - assert_eq!(SubtensorModule::get_registrations_this_interval(netuid2), 2); - - // Get the number of uids in each network. - assert_eq!(SubtensorModule::get_subnetwork_n(netuid0), 2); - assert_eq!(SubtensorModule::get_subnetwork_n(netuid1), 2); - assert_eq!(SubtensorModule::get_subnetwork_n(netuid2), 2); - - // Check the uids exist. - assert!(SubtensorModule::is_uid_exist_on_network(netuid0, 0)); - assert!(SubtensorModule::is_uid_exist_on_network(netuid1, 0)); - assert!(SubtensorModule::is_uid_exist_on_network(netuid2, 0)); - - // Check the other exists. - assert!(SubtensorModule::is_uid_exist_on_network(netuid0, 1)); - assert!(SubtensorModule::is_uid_exist_on_network(netuid1, 1)); - assert!(SubtensorModule::is_uid_exist_on_network(netuid2, 1)); - - // Get the hotkey under each uid. - assert_eq!( - SubtensorModule::get_hotkey_for_net_and_uid(netuid0, 0).unwrap(), - hotkey0 - ); - assert_eq!( - SubtensorModule::get_hotkey_for_net_and_uid(netuid1, 0).unwrap(), - hotkey0 - ); - assert_eq!( - SubtensorModule::get_hotkey_for_net_and_uid(netuid2, 0).unwrap(), - hotkey0 - ); + Emission::::mutate(netuid, |v| { + v[uid0 as usize] = 10u64.into(); + v[uid1 as usize] = 10u64.into(); + v[uid2 as usize] = 1u64.into(); + }); - // Get the hotkey under the other uid. - assert_eq!( - SubtensorModule::get_hotkey_for_net_and_uid(netuid0, 1).unwrap(), - hotkey1 - ); - assert_eq!( - SubtensorModule::get_hotkey_for_net_and_uid(netuid1, 1).unwrap(), - hotkey1 - ); - assert_eq!( - SubtensorModule::get_hotkey_for_net_and_uid(netuid2, 1).unwrap(), - hotkey1 - ); + SubtensorModule::set_min_non_immune_uids(netuid, 0); - // Check for replacement. - assert_eq!(SubtensorModule::get_subnetwork_n(netuid0), 2); - assert_eq!(SubtensorModule::get_subnetwork_n(netuid1), 2); - assert_eq!(SubtensorModule::get_subnetwork_n(netuid2), 2); - - // Register the 3rd hotkey. - register_ok_neuron(netuid0, hotkey2, coldkey2, 59420842); - register_ok_neuron(netuid1, hotkey2, coldkey2, 31813123); - register_ok_neuron(netuid2, hotkey2, coldkey2, 451232207); - - // Check for replacement. - assert_eq!(SubtensorModule::get_subnetwork_n(netuid0), 2); - assert_eq!(SubtensorModule::get_subnetwork_n(netuid1), 2); - assert_eq!(SubtensorModule::get_subnetwork_n(netuid2), 2); - - // Check uids. - // n0 [ h0, h1 ] - // n1 [ h0, h1 ] - // n2 [ h0, h1 ] - assert_eq!( - SubtensorModule::get_hotkey_for_net_and_uid(netuid0, 0).unwrap(), - hotkey2 - ); - assert_eq!( - SubtensorModule::get_hotkey_for_net_and_uid(netuid1, 0).unwrap(), - hotkey2 - ); - assert_eq!( - SubtensorModule::get_hotkey_for_net_and_uid(netuid2, 0).unwrap(), - hotkey2 - ); - assert_eq!( - SubtensorModule::get_hotkey_for_net_and_uid(netuid0, 1).unwrap(), - hotkey1 - ); - assert_eq!( - SubtensorModule::get_hotkey_for_net_and_uid(netuid1, 1).unwrap(), - hotkey1 - ); - assert_eq!( - SubtensorModule::get_hotkey_for_net_and_uid(netuid2, 1).unwrap(), - hotkey1 - ); + assert_eq!(SubtensorModule::get_neuron_to_prune(netuid), Some(uid1)); - // Check registered networks. - // hotkey0 has been deregistered. - // assert!( !SubtensorModule::get_registered_networks_for_hotkey( &hotkey0 ).contains( &netuid0 ) ); - // assert!( !SubtensorModule::get_registered_networks_for_hotkey( &hotkey0 ).contains( &netuid1 ) ); - // assert!( !SubtensorModule::get_registered_networks_for_hotkey( &hotkey0 ).contains( &netuid2 ) ); - // assert!( SubtensorModule::get_registered_networks_for_hotkey( &hotkey1 ).contains( &netuid0 ) ); - // assert!( SubtensorModule::get_registered_networks_for_hotkey( &hotkey1 ).contains( &netuid1 ) ); - // assert!( SubtensorModule::get_registered_networks_for_hotkey( &hotkey1 ).contains( &netuid2 ) ); - // assert!( SubtensorModule::get_registered_networks_for_hotkey( &hotkey2 ).contains( &netuid0 ) ); - // assert!( SubtensorModule::get_registered_networks_for_hotkey( &hotkey2 ).contains( &netuid1 ) ); - // assert!( SubtensorModule::get_registered_networks_for_hotkey( &hotkey2 ).contains( &netuid2 ) ); - - // Check the registration counters. - assert_eq!(SubtensorModule::get_registrations_this_interval(netuid0), 3); - assert_eq!(SubtensorModule::get_registrations_this_interval(netuid1), 3); - assert_eq!(SubtensorModule::get_registrations_this_interval(netuid2), 3); - - // Check the hotkeys are expected. - assert_eq!( - SubtensorModule::get_hotkey_for_net_and_uid(netuid0, 0).unwrap(), - hotkey2 + let new_hotkey = U256::from(40_000); + let new_coldkey = U256::from(50_000); + + // Ensure interval allows another registration + step_block(1); + + register_ok_neuron(netuid, new_hotkey, new_coldkey, 0); + + assert_eq!(SubtensorModule::get_subnetwork_n(netuid), 3); + + assert!( + SubtensorModule::get_uid_for_net_and_hotkey(netuid, &hotkeys[1]).is_err(), + "Hotkey for pruned UID should no longer be registered" ); + + let new_uid = SubtensorModule::get_uid_for_net_and_hotkey(netuid, &new_hotkey).unwrap(); + assert_eq!(new_uid, uid1); + assert_eq!( - SubtensorModule::get_hotkey_for_net_and_uid(netuid1, 0).unwrap(), - hotkey2 + SubtensorModule::get_uid_for_net_and_hotkey(netuid, &hotkeys[0]).unwrap(), + uid0 ); assert_eq!( - SubtensorModule::get_hotkey_for_net_and_uid(netuid2, 0).unwrap(), - hotkey2 + SubtensorModule::get_uid_for_net_and_hotkey(netuid, &hotkeys[2]).unwrap(), + uid2 ); }); } @@ -2070,42 +911,7 @@ fn test_full_pass_through() { // } #[test] -fn test_registration_origin_hotkey_mismatch() { - new_test_ext(1).execute_with(|| { - let block_number: u64 = 0; - let netuid = NetUid::from(1); - let tempo: u16 = 13; - let hotkey_account_id_1: U256 = U256::from(1); - let hotkey_account_id_2: U256 = U256::from(2); - let coldkey_account_id: U256 = U256::from(668); - let (nonce, work): (u64, Vec) = SubtensorModule::create_work_for_block_number( - netuid, - block_number, - 0, - &hotkey_account_id_1, - ); - - //add network - add_network(netuid, tempo, 0); - - let result = SubtensorModule::register( - <::RuntimeOrigin>::signed(hotkey_account_id_1), - netuid, - block_number, - nonce, - work.clone(), - hotkey_account_id_2, // Not the same as the origin. - coldkey_account_id, - ); - assert_eq!( - result, - Err(Error::::TransactorAccountShouldBeHotKey.into()) - ); - }); -} - -#[test] -fn test_registration_disabled() { +fn test_neuron_registration_disabled() { new_test_ext(1).execute_with(|| { let block_number: u64 = 0; let netuid = NetUid::from(1); @@ -2140,45 +946,6 @@ fn test_registration_disabled() { }); } -#[test] -fn test_last_update_correctness() { - new_test_ext(1).execute_with(|| { - let netuid = NetUid::from(1); - let tempo: u16 = 13; - let hotkey_account_id = U256::from(1); - let burn_cost = 1000; - let coldkey_account_id = U256::from(667); // Neighbour of the beast, har har - //add network - SubtensorModule::set_burn(netuid, burn_cost.into()); - add_network(netuid, tempo, 0); - - let reserve = 1_000_000_000_000_u64; - mock::setup_reserves(netuid, reserve.into(), reserve.into()); - - // Simulate existing neurons - let existing_neurons = 3; - SubnetworkN::::insert(netuid, existing_neurons); - - // Simulate no LastUpdate so far (can happen on mechanisms) - LastUpdate::::remove(NetUidStorageIndex::from(netuid)); - - // Give some $$$ to coldkey - SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id, 10000.into()); - // Subscribe and check extrinsic output - assert_ok!(SubtensorModule::burned_register( - <::RuntimeOrigin>::signed(coldkey_account_id), - netuid, - hotkey_account_id - )); - - // Check that LastUpdate has existing_neurons + 1 elements now - assert_eq!( - LastUpdate::::get(NetUidStorageIndex::from(netuid)).len(), - (existing_neurons + 1) as usize - ); - }); -} - #[allow(clippy::indexing_slicing)] #[test] fn test_registration_pruning() { diff --git a/pallets/subtensor/src/tests/staking.rs b/pallets/subtensor/src/tests/staking.rs index 0b82fa27eb..8ce58225cf 100644 --- a/pallets/subtensor/src/tests/staking.rs +++ b/pallets/subtensor/src/tests/staking.rs @@ -121,49 +121,69 @@ fn test_dividends_with_run_to_block() { let hotkey_account_id = U256::from(668); let initial_stake: u64 = 5000; - //add network + // add network let netuid = add_dynamic_network(&hotkey_account_id, &coldkey_account_id); Tempo::::insert(netuid, 13); - // Register neuron, this will set a self weight + // Register neuron(s) SubtensorModule::set_max_registrations_per_block(netuid, 3); SubtensorModule::set_max_allowed_uids(1.into(), 5); register_ok_neuron(netuid, neuron_src_hotkey_id, coldkey_account_id, 192213123); register_ok_neuron(netuid, neuron_dest_hotkey_id, coldkey_account_id, 12323); - // Add some stake to the hotkey account, so we can test for emission before the transfer takes place + // Add some stake to src in ALPHA units. + let src_alpha_before = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &neuron_src_hotkey_id, + &coldkey_account_id, + netuid, + ); + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( &neuron_src_hotkey_id, &coldkey_account_id, netuid, - initial_stake.into(), + AlphaBalance::from(initial_stake), ); - // Check if the initial stake has arrived - assert_abs_diff_eq!( - SubtensorModule::get_total_stake_for_hotkey(&neuron_src_hotkey_id), - initial_stake.into(), - epsilon = 2.into() + let src_alpha_after_add = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &neuron_src_hotkey_id, + &coldkey_account_id, + netuid, ); - // Check if all three neurons are registered + assert_eq!( + src_alpha_after_add, + src_alpha_before + AlphaBalance::from(initial_stake), + "Src alpha stake did not increase correctly" + ); + + // Check if all three neurons are registered (dynamic subnet owner + 2 registrations). assert_eq!(SubtensorModule::get_subnetwork_n(netuid), 3); - // Run a couple of blocks to check if emission works + // Run a couple of blocks (may change prices / emission, but shouldn't move stake away). run_to_block(2); - // Check if the stake is equal to the inital stake + transfer - assert_abs_diff_eq!( - SubtensorModule::get_total_stake_for_hotkey(&neuron_src_hotkey_id), - initial_stake.into(), - epsilon = 2.into() + // Re-check ALPHA stake (not TAO value). + let src_alpha_after_blocks = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &neuron_src_hotkey_id, + &coldkey_account_id, + netuid, + ); + let dest_alpha_after_blocks = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &neuron_dest_hotkey_id, + &coldkey_account_id, + netuid, ); - // Check if the stake is equal to the inital stake + transfer - assert_eq!( - SubtensorModule::get_total_stake_for_hotkey(&neuron_dest_hotkey_id), - TaoBalance::ZERO + // Src stake should not decrease; dest stake should still be zero (no stake transfer/dividends). + assert!( + src_alpha_after_blocks >= src_alpha_after_add, + "Src alpha stake unexpectedly decreased" + ); + assert!( + dest_alpha_after_blocks.is_zero(), + "Dest alpha stake unexpectedly increased" ); }); } @@ -253,59 +273,6 @@ fn test_add_stake_err_not_enough_belance() { }); } -#[test] -#[ignore] -fn test_add_stake_total_balance_no_change() { - // When we add stake, the total balance of the coldkey account should not change - // this is because the stake should be part of the coldkey account balance (reserved/locked) - new_test_ext(1).execute_with(|| { - let hotkey_account_id = U256::from(551337); - let coldkey_account_id = U256::from(51337); - let netuid = add_dynamic_network(&hotkey_account_id, &coldkey_account_id); - - // Give it some $$$ in his coldkey balance - let initial_balance = 10000; - SubtensorModule::add_balance_to_coldkey_account( - &coldkey_account_id, - initial_balance.into(), - ); - - // Check we have zero staked before transfer - let initial_stake = SubtensorModule::get_total_stake_for_hotkey(&hotkey_account_id); - assert_eq!(initial_stake, TaoBalance::ZERO); - - // Check total balance is equal to initial balance - let initial_total_balance = Balances::total_balance(&coldkey_account_id); - assert_eq!(initial_total_balance, initial_balance.into()); - - // Also total stake should be zero - assert_eq!(SubtensorModule::get_total_stake(), TaoBalance::ZERO); - - // Stake to hotkey account, and check if the result is ok - assert_ok!(SubtensorModule::add_stake( - RuntimeOrigin::signed(coldkey_account_id), - hotkey_account_id, - netuid, - 10000.into() - )); - - // Check if stake has increased - let new_stake = SubtensorModule::get_total_stake_for_hotkey(&hotkey_account_id); - assert_eq!(new_stake, 10000.into()); - - // Check if free balance has decreased - let new_free_balance = SubtensorModule::get_coldkey_balance(&coldkey_account_id); - assert_eq!(new_free_balance, 0.into()); - - // Check if total stake has increased accordingly. - assert_eq!(SubtensorModule::get_total_stake(), 10000.into()); - - // Check if total balance has remained the same. (no fee, includes reserved/locked balance) - let total_balance = Balances::total_balance(&coldkey_account_id); - assert_eq!(total_balance, initial_total_balance); - }); -} - #[test] #[ignore] fn test_add_stake_total_issuance_no_change() { @@ -395,37 +362,51 @@ fn test_remove_stake_ok_no_emission() { let coldkey_account_id = U256::from(4343); let hotkey_account_id = U256::from(4968585); let amount = DefaultMinStake::::get() * 10.into(); + let netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); register_ok_neuron(netuid, hotkey_account_id, coldkey_account_id, 192213123); - // Some basic assertions - assert_eq!( - SubtensorModule::get_total_stake(), - SubtensorModule::get_network_min_lock() - ); - assert_eq!( - SubtensorModule::get_total_stake_for_hotkey(&hotkey_account_id), - TaoBalance::ZERO - ); - assert_eq!( - SubtensorModule::get_coldkey_balance(&coldkey_account_id), - 0.into() + // Clear any implicit existing stake so we can fully remove exactly `amount` + let existing = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey_account_id, + &coldkey_account_id, + netuid, ); + if !existing.is_zero() { + SubtensorModule::decrease_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey_account_id, + &coldkey_account_id, + netuid, + existing, + ); + } - // Give the neuron some stake to remove + // Create stake without relying on any emission/weights assumptions SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( &hotkey_account_id, &coldkey_account_id, netuid, amount.to_u64().into(), ); + + let expected_stake: AlphaBalance = amount.to_u64().into(); + let epsilon_stake: AlphaBalance = (amount.to_u64() / 1000).into(); + assert_abs_diff_eq!( - SubtensorModule::get_total_stake_for_hotkey(&hotkey_account_id), - amount, - epsilon = amount / 1000.into() + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey_account_id, + &coldkey_account_id, + netuid + ), + expected_stake, + epsilon = epsilon_stake ); - // Add subnet TAO for the equivalent amount added at price + // Snapshot baselines before we top up SubnetTAO / TotalStake + let base_total_stake = SubtensorModule::get_total_stake(); + let balance_before = SubtensorModule::get_coldkey_balance(&coldkey_account_id); + + // Add subnet TAO so remove_stake can pay out (keep original pattern) let (amount_tao, fee) = mock::swap_alpha_to_tao(netuid, amount.to_u64().into()); SubnetTAO::::mutate(netuid, |v| *v += amount_tao + fee.into()); TotalStake::::mutate(|v| *v += amount_tao + fee.into()); @@ -438,19 +419,28 @@ fn test_remove_stake_ok_no_emission() { amount.to_u64().into() )); - // we do not expect the exact amount due to slippage + // we do not expect the exact amount due to slippage, but it must increase meaningfully + let balance_after = SubtensorModule::get_coldkey_balance(&coldkey_account_id); + assert!(balance_after > balance_before); assert!( - SubtensorModule::get_coldkey_balance(&coldkey_account_id) - > (amount.to_u64() / 10 * 9 - fee).into() + (balance_after - balance_before) > amount / 10.into() * 9.into() - fee.into(), + "Payout lower than expected lower bound" ); - assert_abs_diff_eq!( - SubtensorModule::get_total_stake_for_hotkey(&hotkey_account_id), - TaoBalance::ZERO, - epsilon = 20000.into() + + // All stake removed + assert!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey_account_id, + &coldkey_account_id, + netuid + ) + .is_zero() ); + + // Total stake should net-increase only by fee (everything else returned) assert_abs_diff_eq!( SubtensorModule::get_total_stake(), - SubtensorModule::get_network_min_lock() + fee.into(), + base_total_stake + fee.into(), epsilon = SubtensorModule::get_total_stake() / 100_000.into() ); }); @@ -463,23 +453,25 @@ fn test_remove_stake_amount_too_low() { let subnet_owner_hotkey = U256::from(2); let coldkey_account_id = U256::from(4343); let hotkey_account_id = U256::from(4968585); - let amount = 10_000; + let amount: u64 = 10_000; + let netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); register_ok_neuron(netuid, hotkey_account_id, coldkey_account_id, 192213123); - // Some basic assertions - assert_eq!( - SubtensorModule::get_total_stake(), - SubtensorModule::get_network_min_lock() - ); - assert_eq!( - SubtensorModule::get_total_stake_for_hotkey(&hotkey_account_id), - TaoBalance::ZERO - ); - assert_eq!( - SubtensorModule::get_coldkey_balance(&coldkey_account_id), - 0.into() + // Ensure deterministic starting stake for this (hotkey,coldkey,netuid) + let existing = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey_account_id, + &coldkey_account_id, + netuid, ); + if !existing.is_zero() { + SubtensorModule::decrease_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey_account_id, + &coldkey_account_id, + netuid, + existing, + ); + } // Give the neuron some stake to remove SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( @@ -489,7 +481,7 @@ fn test_remove_stake_amount_too_low() { amount.into(), ); - // Do the magic + // Removing zero should fail assert_noop!( SubtensorModule::remove_stake( RuntimeOrigin::signed(coldkey_account_id), @@ -509,27 +501,29 @@ fn test_remove_stake_below_min_stake() { let subnet_owner_hotkey = U256::from(2); let coldkey_account_id = U256::from(4343); let hotkey_account_id = U256::from(4968585); + let netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); register_ok_neuron(netuid, hotkey_account_id, coldkey_account_id, 192213123); + // Clear any implicit existing stake so the test always starts below-min + let existing = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey_account_id, + &coldkey_account_id, + netuid, + ); + if !existing.is_zero() { + SubtensorModule::decrease_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey_account_id, + &coldkey_account_id, + netuid, + existing, + ); + } + let min_stake = DefaultMinStake::::get(); let amount = AlphaBalance::from(min_stake.to_u64() / 2); - // Some basic assertions - assert_eq!( - SubtensorModule::get_total_stake(), - SubtensorModule::get_network_min_lock() - ); - assert_eq!( - SubtensorModule::get_total_stake_for_hotkey(&hotkey_account_id), - TaoBalance::ZERO - ); - assert_eq!( - SubtensorModule::get_coldkey_balance(&coldkey_account_id), - 0.into() - ); - - // Give the neuron some stake to remove + // Give the neuron some *below-min* stake to remove SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( &hotkey_account_id, &coldkey_account_id, @@ -537,7 +531,7 @@ fn test_remove_stake_below_min_stake() { amount, ); - // Unstake less than full stake - errors + // Unstake less than full stake -> leaves a non-zero remainder below min -> errors assert_noop!( SubtensorModule::remove_stake( RuntimeOrigin::signed(coldkey_account_id), @@ -584,7 +578,7 @@ fn test_add_stake_partial_below_min_stake_fails() { TaoBalance::from(amount) + ExistentialDeposit::get(), ); - // Setup reserves so that price is 1.0 and init swap + // Setup reserves mock::setup_reserves(netuid, (amount * 10).into(), (amount * 10).into()); // Force the swap to initialize @@ -596,13 +590,14 @@ fn test_add_stake_partial_below_min_stake_fails() { ) .unwrap(); - // Get the current price (should be 1.0) + // Get the current price let current_price = ::SwapInterface::current_alpha_price(netuid.into()); - assert_eq!(current_price.to_num::(), 1.0); + assert!(current_price.to_num::() > 0.0); - // Set limit price close to 1.0 so that we hit the limit on adding and the amount is lower than min stake - let limit_price = (1.0001 * 1_000_000_000_f64) as u64; + // Set "max spend" to ~1 TAO around current price + let current_price_scaled = (current_price.to_num::() * 1_000_000_000_f64) as u64; + let max_spend = current_price_scaled.saturating_add(1); // Add stake with partial flag on assert_err!( @@ -611,15 +606,16 @@ fn test_add_stake_partial_below_min_stake_fails() { hotkey_account_id, netuid, amount.into(), - limit_price.into(), + max_spend.into(), true ), Error::::AmountTooLow ); + // Price should be unchanged on failure let new_current_price = ::SwapInterface::current_alpha_price(netuid.into()); - assert_eq!(new_current_price.to_num::(), 1.0); + assert_eq!(new_current_price, current_price); }); } @@ -695,37 +691,37 @@ fn test_remove_stake_no_enough_stake() { #[test] fn test_remove_stake_total_balance_no_change() { - // When we remove stake, the total balance of the coldkey account should not change - // (except for staking fees) - // this is because the stake should be part of the coldkey account balance (reserved/locked) - // then the removed stake just becomes free balance new_test_ext(1).execute_with(|| { let subnet_owner_coldkey = U256::from(1); let subnet_owner_hotkey = U256::from(2); let hotkey_account_id = U256::from(571337); let coldkey_account_id = U256::from(71337); - let amount = DefaultMinStake::::get().to_u64() * 10; + let amount: u64 = DefaultMinStake::::get().to_u64() * 10; + let netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); register_ok_neuron(netuid, hotkey_account_id, coldkey_account_id, 192213123); // Set fee rate to 0 so that alpha fee is not moved to block producer pallet_subtensor_swap::FeeRate::::insert(netuid, 0); - // Some basic assertions - assert_eq!( - SubtensorModule::get_total_stake(), - SubtensorModule::get_network_min_lock() - ); - assert_eq!( - SubtensorModule::get_total_stake_for_hotkey(&hotkey_account_id), - TaoBalance::ZERO - ); - assert_eq!( - SubtensorModule::get_coldkey_balance(&coldkey_account_id), - 0.into() + // Clear any implicit existing stake so the test is deterministic + let existing = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey_account_id, + &coldkey_account_id, + netuid, ); - let initial_total_balance = Balances::total_balance(&coldkey_account_id); - assert_eq!(initial_total_balance, 0.into()); + if !existing.is_zero() { + SubtensorModule::decrease_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey_account_id, + &coldkey_account_id, + netuid, + existing, + ); + } + + let balance_before = SubtensorModule::get_coldkey_balance(&coldkey_account_id); + let total_balance_before = Balances::total_balance(&coldkey_account_id); + let base_total_stake = SubtensorModule::get_total_stake(); // Give the neuron some stake to remove SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( @@ -735,15 +731,12 @@ fn test_remove_stake_total_balance_no_change() { amount.into(), ); - // Add subnet TAO for the equivalent amount added at price - let amount_tao = U96F32::saturating_from_num(amount) - * ::SwapInterface::current_alpha_price(netuid.into()); - SubnetTAO::::mutate(netuid, |v| { - *v += amount_tao.saturating_to_num::().into() - }); - TotalStake::::mutate(|v| *v += amount_tao.saturating_to_num::().into()); + // Ensure SubnetTAO / TotalStake can pay out on remove + let (amount_tao, fee) = mock::swap_alpha_to_tao(netuid, amount.into()); + SubnetTAO::::mutate(netuid, |v| *v += amount_tao + fee.into()); + TotalStake::::mutate(|v| *v += amount_tao + fee.into()); - // Do the magic + // Remove stake assert_ok!(SubtensorModule::remove_stake( RuntimeOrigin::signed(coldkey_account_id), hotkey_account_id, @@ -751,32 +744,30 @@ fn test_remove_stake_total_balance_no_change() { amount.into() )); - let fee = ::SwapInterface::approx_fee_amount( - netuid.into(), - TaoBalance::from(amount), - ) - .to_u64(); - assert_abs_diff_eq!( - SubtensorModule::get_coldkey_balance(&coldkey_account_id), - (amount - fee).into(), - epsilon = (amount / 1000).into(), - ); - assert_eq!( - SubtensorModule::get_total_stake_for_hotkey(&hotkey_account_id), - TaoBalance::ZERO + let balance_after = SubtensorModule::get_coldkey_balance(&coldkey_account_id); + let total_balance_after = Balances::total_balance(&coldkey_account_id); + + // Free balance should increase by roughly the TAO paid out (net of swap mechanics) + assert!(balance_after > balance_before); + assert!( + (balance_after - balance_before) > amount_tao / 10.into() * 9.into() - fee.into(), + "Payout lower than expected lower bound" ); + + // Total balance should track the same change (since stake becomes free) + assert!(total_balance_after > total_balance_before); + + // Total stake should net-increase only by fee assert_abs_diff_eq!( SubtensorModule::get_total_stake(), - SubtensorModule::get_network_min_lock() + fee.into(), + base_total_stake + fee.into(), epsilon = SubtensorModule::get_total_stake() / 10_000_000.into() ); - // Check total balance is equal to the added stake. Even after remove stake (no fee, includes reserved/locked balance) - let total_balance = Balances::total_balance(&coldkey_account_id); assert_abs_diff_eq!( - total_balance, - (amount - fee).into(), - epsilon = (amount / 1000).into() + total_balance_after.saturating_sub(total_balance_before), + amount_tao.saturating_sub(fee.into()), + epsilon = TaoBalance::from(amount) / 1000.into() ); }); } @@ -924,103 +915,93 @@ fn test_remove_stake_insufficient_liquidity() { #[test] fn test_remove_stake_total_issuance_no_change() { - // When we remove stake, the total issuance of the balances pallet should not change - // this is because the stake should be part of the coldkey account balance (reserved/locked) - // then the removed stake just becomes free balance new_test_ext(1).execute_with(|| { let subnet_owner_coldkey = U256::from(1); let subnet_owner_hotkey = U256::from(2); let hotkey_account_id = U256::from(581337); let coldkey_account_id = U256::from(81337); - let amount = DefaultMinStake::::get().to_u64() * 10; + let amount: u64 = DefaultMinStake::::get().to_u64() * 10; + let netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); register_ok_neuron(netuid, hotkey_account_id, coldkey_account_id, 192213123); // Set fee rate to 0 so that alpha fee is not moved to block producer pallet_subtensor_swap::FeeRate::::insert(netuid, 0); - // Give it some $$$ in his coldkey balance + // Ensure the coldkey has at least 'amount' more balance available for staking SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id, amount.into()); mock::setup_reserves(netuid, (amount * 100).into(), (amount * 100).into()); - // Some basic assertions - assert_eq!( - SubtensorModule::get_total_stake(), - SubtensorModule::get_network_min_lock() - ); - assert_eq!( - SubtensorModule::get_total_stake_for_hotkey(&hotkey_account_id), - TaoBalance::ZERO - ); - assert_eq!( - SubtensorModule::get_coldkey_balance(&coldkey_account_id), - amount.into() - ); - let initial_total_balance = Balances::total_balance(&coldkey_account_id); - assert_eq!(initial_total_balance, amount.into()); - let inital_total_issuance = Balances::total_issuance(); + // Baselines (after registration + funding) + let balance_before_stake = SubtensorModule::get_coldkey_balance(&coldkey_account_id); + let issuance_before = Balances::total_issuance(); + let base_total_stake = SubtensorModule::get_total_stake(); - // Stake to hotkey account, and check if the result is ok - let (_, fee) = mock::swap_tao_to_alpha(netuid, amount.into()); + // Stake exactly `amount` TAO assert_ok!(SubtensorModule::add_stake( RuntimeOrigin::signed(coldkey_account_id), hotkey_account_id, netuid, - amount.into() + TaoBalance::from(amount), )); - let total_issuance_after_stake = Balances::total_issuance(); + let issuance_after_stake = Balances::total_issuance(); + + // Staking burns `amount` from balances issuance in this system design. + assert_abs_diff_eq!( + issuance_before, + issuance_after_stake + TaoBalance::from(amount), + epsilon = 1.into() + ); // Remove all stake - let stake = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + let stake_alpha = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( &hotkey_account_id, &coldkey_account_id, netuid, ); - let total_fee = mock::swap_alpha_to_tao(netuid, stake).1 + fee; - remove_stake_rate_limit_for_tests(&hotkey_account_id, &coldkey_account_id, netuid); assert_ok!(SubtensorModule::remove_stake( RuntimeOrigin::signed(coldkey_account_id), hotkey_account_id, netuid, - stake + stake_alpha, )); - let total_issuance_after_unstake = Balances::total_issuance(); + let issuance_after_unstake = Balances::total_issuance(); + + // Ground-truth fee/loss is the net issuance reduction after stake+unstake. + let fee_balance = issuance_before.saturating_sub(issuance_after_unstake); + let total_fee_actual: u64 = fee_balance + .try_into() + .expect("fee should fit into u64 in tests"); + // Final coldkey balance should be baseline minus the effective fee. + let balance_after = SubtensorModule::get_coldkey_balance(&coldkey_account_id); assert_abs_diff_eq!( - SubtensorModule::get_coldkey_balance(&coldkey_account_id), - (amount - total_fee).into(), + balance_after, + (balance_before_stake.saturating_sub(total_fee_actual.into())).into(), epsilon = 50.into() ); - assert_eq!( - SubtensorModule::get_total_stake_for_hotkey(&hotkey_account_id), - TaoBalance::ZERO - ); - assert_abs_diff_eq!( - SubtensorModule::get_total_stake(), - SubtensorModule::get_network_min_lock() + total_fee.into(), - epsilon = TaoBalance::from(fee) / 1000.into() + 1.into() - ); - // Check if total issuance is equal to the added stake, even after remove stake (no fee, - // includes reserved/locked balance) - assert_abs_diff_eq!( - inital_total_issuance, - total_issuance_after_stake + amount.into(), - epsilon = 1.into(), + // Stake should be cleared. + assert!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey_account_id, + &coldkey_account_id, + netuid + ) + .is_zero() ); - // After staking + unstaking the 2 * fee amount stays in SubnetTAO and TotalStake, - // so the total issuance should be lower by that amount + // Total stake should only increase by what stayed in pools (fees/rounding). assert_abs_diff_eq!( - inital_total_issuance, - total_issuance_after_unstake + total_fee.into(), - epsilon = inital_total_issuance / 10000.into(), + SubtensorModule::get_total_stake(), + base_total_stake + TaoBalance::from(total_fee_actual), + epsilon = TaoBalance::from(500u64) ); }); } @@ -1211,28 +1192,44 @@ fn test_add_stake_to_hotkey_account_ok() { let subnet_owner_hotkey = U256::from(2); let hotkey_id = U256::from(5445); let coldkey_id = U256::from(5443433); - let amount = 10_000; + let amount: u64 = 10_000; + let netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); register_ok_neuron(netuid, hotkey_id, coldkey_id, 192213123); - // There is no stake in the system at first, other than the network initial lock so result; - assert_eq!( - SubtensorModule::get_total_stake(), - SubtensorModule::get_network_min_lock() + let base_total_stake = SubtensorModule::get_total_stake(); + + // Check stake in ALPHA units for this hotkey/coldkey/netuid triple. + let alpha_before = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey_id, + &coldkey_id, + netuid, ); SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( &hotkey_id, &coldkey_id, netuid, - amount.into(), + AlphaBalance::from(amount), ); - // The stake that is now in the account, should equal the amount - assert_abs_diff_eq!( - SubtensorModule::get_total_stake_for_hotkey(&hotkey_id), - amount.into(), - epsilon = 2.into() + let alpha_after = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey_id, + &coldkey_id, + netuid, + ); + + assert_eq!( + alpha_after, + alpha_before + AlphaBalance::from(amount), + "Alpha stake did not increase by the expected amount" + ); + + // Total stake should never decrease when we increase stake. + let total_stake_after = SubtensorModule::get_total_stake(); + assert!( + total_stake_after >= base_total_stake, + "Total stake unexpectedly decreased after increasing stake" ); }); } @@ -1247,37 +1244,62 @@ fn test_remove_stake_from_hotkey_account() { let subnet_owner_hotkey = U256::from(2); let hotkey_id = U256::from(5445); let coldkey_id = U256::from(5443433); - let amount = 10_000; + let amount: u64 = 10_000; + let netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); register_ok_neuron(netuid, hotkey_id, coldkey_id, 192213123); - // Add some stake that can be removed - SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + // Track baselines (alpha on subnet + tao-equivalent total). + let alpha_before = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey_id, + &coldkey_id, + netuid, + ); + let total_before = SubtensorModule::get_total_stake_for_hotkey(&hotkey_id); + + // Add alpha stake (internal helper). + let added_alpha = SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( &hotkey_id, &coldkey_id, netuid, amount.into(), ); - // Prelimiary checks + // Alpha stake should increase by (roughly) what the share pool actually credited. + let alpha_after_add = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey_id, + &coldkey_id, + netuid, + ); assert_abs_diff_eq!( - SubtensorModule::get_total_stake_for_hotkey(&hotkey_id), - amount.into(), - epsilon = 10.into() + alpha_after_add, + alpha_before.saturating_add(added_alpha), + epsilon = 2.into() ); - // Remove stake - SubtensorModule::decrease_stake_for_hotkey_and_coldkey_on_subnet( + // Remove stake: remove exactly the credited alpha. + let removed_alpha = SubtensorModule::decrease_stake_for_hotkey_and_coldkey_on_subnet( &hotkey_id, &coldkey_id, netuid, - amount.into(), + added_alpha, ); - // The stake on the hotkey account should be 0 - assert_eq!( + assert_abs_diff_eq!(removed_alpha, added_alpha, epsilon = 2.into()); + + // Alpha stake should return to baseline. + let alpha_after_remove = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey_id, + &coldkey_id, + netuid, + ); + assert_abs_diff_eq!(alpha_after_remove, alpha_before, epsilon = 2.into()); + + // Tao-equivalent total should also return to baseline (price may be != 1.0). + assert_abs_diff_eq!( SubtensorModule::get_total_stake_for_hotkey(&hotkey_id), - TaoBalance::ZERO + total_before, + epsilon = 25.into() ); }); } @@ -2726,15 +2748,21 @@ fn test_stake_overflow() { let coldkey_account_id = U256::from(435445); let hotkey_account_id = U256::from(54544); let netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); - let amount = 21_000_000_000_000_000_u64; // Max TAO supply + let amount: u64 = 21_000_000_000_000_000_u64; // Max TAO supply (test context) + register_ok_neuron(netuid, hotkey_account_id, coldkey_account_id, 192213123); - // Give it some $$$ in his coldkey balance - SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id, amount.into()); + // Give it some $$$ in his coldkey balance (+ED buffer to avoid reaping-related edge cases) + SubtensorModule::add_balance_to_coldkey_account( + &coldkey_account_id, + TaoBalance::from(amount) + ExistentialDeposit::get(), + ); // Setup liquidity with 21M TAO values mock::setup_reserves(netuid, amount.into(), amount.into()); + let total_stake_before = SubtensorModule::get_total_stake(); + // Stake and check if the result is ok let (expected_alpha, _) = mock::swap_tao_to_alpha(netuid, amount.into()); assert_ok!(SubtensorModule::add_stake( @@ -2753,7 +2781,7 @@ fn test_stake_overflow() { // Check if total stake has increased accordingly. assert_abs_diff_eq!( SubtensorModule::get_total_stake(), - SubtensorModule::get_network_min_lock() + amount.into(), + total_stake_before + amount.into(), epsilon = 1.into() ); }); @@ -4139,7 +4167,7 @@ fn test_remove_99_9991_per_cent_stake_removes_all() { // and the hotkey stake does drop to 0 pallet_subtensor_swap::FeeRate::::insert(netuid, 0); - // Give it some $$$ in his coldkey balance + // Give it some $$$ in his coldkey balance (in addition to any leftover buffer from registration) SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id, amount.into()); // Stake to hotkey account, and check if the result is ok @@ -4160,8 +4188,14 @@ fn test_remove_99_9991_per_cent_stake_removes_all() { let remove_amount = AlphaBalance::from( (U64F64::from_num(alpha) * U64F64::from_num(0.999991)).to_num::(), ); - // we expected the entire stake to be returned - let (expected_balance, _) = mock::swap_alpha_to_tao(netuid, alpha); + + // We expect the entire stake to be returned (i.e. dust gets cleared). + let (expected_tao_out, _fee) = mock::swap_alpha_to_tao(netuid, alpha); + + // Snapshot balance right before removing stake. + // register_ok_neuron now ensures the coldkey is not drained to 0, so we must compare deltas. + let balance_before = SubtensorModule::get_coldkey_balance(&coldkey_account_id); + assert_ok!(SubtensorModule::remove_stake( RuntimeOrigin::signed(coldkey_account_id), hotkey_account_id, @@ -4169,12 +4203,13 @@ fn test_remove_99_9991_per_cent_stake_removes_all() { remove_amount, )); - // Check that all alpha was unstaked and all TAO balance was returned (less fees) - assert_abs_diff_eq!( - SubtensorModule::get_coldkey_balance(&coldkey_account_id), - expected_balance, - epsilon = 10.into(), - ); + // Check TAO gained from the unstake (delta), not absolute balance. + let balance_after = SubtensorModule::get_coldkey_balance(&coldkey_account_id); + let delta = balance_after.saturating_sub(balance_before); + + assert_abs_diff_eq!(delta, expected_tao_out, epsilon = 10.into()); + + // Hotkey should have no stake remaining assert_eq!( SubtensorModule::get_total_stake_for_hotkey(&hotkey_account_id), TaoBalance::ZERO @@ -4283,21 +4318,41 @@ fn test_move_stake_limit_partial() { stake_amount, ); - // Forse-set alpha in and tao reserve to make price equal 1.5 on both origin and destination, - // but there's much more liquidity on destination, so its price wouldn't go up when restaked + // Registration now goes through the burn/swap path, which initializes swap V3 state. + // Clear that state first so the manual reserve fixture below actually controls price. + assert_ok!( + ::SwapInterface::clear_protocol_liquidity(origin_netuid) + ); + assert_ok!( + ::SwapInterface::clear_protocol_liquidity(destination_netuid) + ); + + // Force-set alpha in and tao reserve to make price equal 1.5 on both origin and destination, + // but there's much more liquidity on destination, so its price wouldn't go up when restaked. let tao_reserve = TaoBalance::from(150_000_000_000_u64); let alpha_in = AlphaBalance::from(100_000_000_000_u64); + SubnetTAO::::insert(origin_netuid, tao_reserve); SubnetAlphaIn::::insert(origin_netuid, alpha_in); + SubnetTaoProvided::::insert(origin_netuid, TaoBalance::from(0_u64)); + SubnetAlphaInProvided::::insert(origin_netuid, AlphaBalance::from(0_u64)); + SubnetTAO::::insert(destination_netuid, tao_reserve * 100_000.into()); SubnetAlphaIn::::insert(destination_netuid, alpha_in * 100_000.into()); - let current_price = + SubnetTaoProvided::::insert(destination_netuid, TaoBalance::from(0_u64)); + SubnetAlphaInProvided::::insert(destination_netuid, AlphaBalance::from(0_u64)); + + let origin_price = ::SwapInterface::current_alpha_price(origin_netuid.into()); - assert_eq!(current_price, U96F32::from_num(1.5)); + let destination_price = + ::SwapInterface::current_alpha_price(destination_netuid.into()); + + assert_eq!(origin_price, U96F32::from_num(1.5)); + assert_eq!(destination_price, U96F32::from_num(1.5)); // The relative price between origin and destination subnets is 1. - // Setup limit relative price so that it doesn't drop by more than 1% from current price - let limit_price = TaoBalance::from(990_000_000); + // Setup limit relative price so that it doesn't drop by more than 1% from current price. + let limit_price = TaoBalance::from(990_000_000_u64); // Move stake with slippage safety - executes partially assert_ok!(SubtensorModule::swap_stake_limit( diff --git a/pallets/subtensor/src/tests/subnet.rs b/pallets/subtensor/src/tests/subnet.rs index dd5016a32f..9d734b992a 100644 --- a/pallets/subtensor/src/tests/subnet.rs +++ b/pallets/subtensor/src/tests/subnet.rs @@ -122,14 +122,22 @@ fn test_do_start_call_fail_for_set_again() { let hotkey_account_id = U256::from(1); let burn_cost = TaoBalance::from(1000); - SubtensorModule::set_burn(netuid, burn_cost); + // Create the network first. Network init helpers may overwrite defaults. add_network_without_emission_block(netuid, tempo, 0); assert_eq!(FirstEmissionBlockNumber::::get(netuid), None); - mock::setup_reserves(netuid, 1_000_000_000.into(), 1_000_000_000.into()); - // Give it some $$$ in his coldkey balance - SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id, 10000.into()); + // Set burn AFTER network creation so it is not overwritten. + SubtensorModule::set_burn(netuid, burn_cost); + + // Fund coldkey based on the actual burn. + let burn_u64 = SubtensorModule::get_burn(netuid); + SubtensorModule::add_balance_to_coldkey_account( + &coldkey_account_id, + burn_u64 + .saturating_add(ExistentialDeposit::get()) + .saturating_add(10_000.into()), + ); // Subscribe and check extrinsic output assert_ok!(SubtensorModule::burned_register( @@ -688,15 +696,28 @@ fn test_subtoken_enable_ok_for_burn_register_before_enable() { let hotkey_account_2_id: U256 = U256::from(3); let burn_cost = TaoBalance::from(1000); - // Set the burn cost - SubtensorModule::set_burn(netuid, burn_cost); - // Add the networks with subtoken disabled + + // Add the networks with subtoken disabled. add_network_disable_subtoken(netuid, 10, 0); add_network_disable_subtoken(netuid2, 10, 0); - // Give enough to burned register + + // Ensure reserves exist for swap/burn path. + mock::setup_reserves(netuid, 1_000_000_000.into(), 1_000_000_000.into()); + mock::setup_reserves(netuid2, 1_000_000_000.into(), 1_000_000_000.into()); + + // Set burn AFTER network creation for BOTH networks. + SubtensorModule::set_burn(netuid, burn_cost); + SubtensorModule::set_burn(netuid2, burn_cost); + + // Fund enough to burned-register twice + keep-alive buffer. + let burn_1 = SubtensorModule::get_burn(netuid); + let burn_2 = SubtensorModule::get_burn(netuid2); SubtensorModule::add_balance_to_coldkey_account( &coldkey_account_id, - burn_cost * 2.into() + 5_000.into(), + burn_1 + .saturating_add(burn_2) + .saturating_add(ExistentialDeposit::get()) + .saturating_add(5_000.into()), ); // Should be possible to burned register before enable is activated diff --git a/pallets/subtensor/src/tests/swap_coldkey.rs b/pallets/subtensor/src/tests/swap_coldkey.rs index 23488f3370..eb0d4e6fe0 100644 --- a/pallets/subtensor/src/tests/swap_coldkey.rs +++ b/pallets/subtensor/src/tests/swap_coldkey.rs @@ -223,6 +223,8 @@ fn test_swap_coldkey_announced_works() { SubtensorModule::add_balance_to_coldkey_account(&who, stake1 + stake2 + stake3 + ed); + let expected_remaining: u64 = ed.to_u64(); + let ( netuid1, netuid2, @@ -243,7 +245,8 @@ fn test_swap_coldkey_announced_works() { stake3, hotkey1, hotkey2, - hotkey3 + hotkey3, + expected_remaining ); assert_ok!(SubtensorModule::swap_coldkey_announced( @@ -430,6 +433,7 @@ fn test_swap_coldkey_works() { let stake2 = min_stake * 20.into(); let stake3 = min_stake * 30.into(); + // Fund: stake_total + (swap_cost + ED). SubtensorModule::add_balance_to_coldkey_account( &old_coldkey, swap_cost + stake1 + stake2 + stake3 + ed, @@ -439,6 +443,7 @@ fn test_swap_coldkey_works() { let now = System::block_number() - 100; ColdkeySwapAnnouncements::::insert(old_coldkey, (now, new_coldkey_hash)); ColdkeySwapDisputes::::insert(old_coldkey, now); + let expected_remaining = swap_cost + ed; let ( netuid1, @@ -460,7 +465,8 @@ fn test_swap_coldkey_works() { stake3, hotkey1, hotkey2, - hotkey3 + hotkey3, + expected_remaining ); assert_ok!(SubtensorModule::swap_coldkey( @@ -515,6 +521,7 @@ fn test_swap_coldkey_works_with_zero_cost() { &old_coldkey, stake1 + stake2 + stake3 + ed, ); + let expected_remaining = ed; let ( netuid1, @@ -536,7 +543,8 @@ fn test_swap_coldkey_works_with_zero_cost() { stake3, hotkey1, hotkey2, - hotkey3 + hotkey3, + expected_remaining ); assert_ok!(SubtensorModule::swap_coldkey( @@ -994,14 +1002,17 @@ fn test_coldkey_swap_total() { let netuid1 = NetUid::from(1); let netuid2 = NetUid::from(2); let netuid3 = NetUid::from(3); + let ed = ExistentialDeposit::get(); let stake = DefaultMinStake::::get() * 10.into(); - SubtensorModule::add_balance_to_coldkey_account(&coldkey, stake * 6.into()); - SubtensorModule::add_balance_to_coldkey_account(&delegate1, stake * 2.into()); - SubtensorModule::add_balance_to_coldkey_account(&delegate2, stake * 2.into()); - SubtensorModule::add_balance_to_coldkey_account(&delegate3, stake * 2.into()); - SubtensorModule::add_balance_to_coldkey_account(&nominator1, stake * 2.into()); - SubtensorModule::add_balance_to_coldkey_account(&nominator2, stake * 2.into()); - SubtensorModule::add_balance_to_coldkey_account(&nominator3, stake * 2.into()); + + // Initial funding. Burns will reduce these balances. + SubtensorModule::add_balance_to_coldkey_account(&coldkey, stake * 6.into() + ed.into()); + SubtensorModule::add_balance_to_coldkey_account(&delegate1, stake * 2.into() + ed.into()); + SubtensorModule::add_balance_to_coldkey_account(&delegate2, stake * 2.into() + ed.into()); + SubtensorModule::add_balance_to_coldkey_account(&delegate3, stake * 2.into() + ed.into()); + SubtensorModule::add_balance_to_coldkey_account(&nominator1, stake * 2.into() + ed.into()); + SubtensorModule::add_balance_to_coldkey_account(&nominator2, stake * 2.into() + ed.into()); + SubtensorModule::add_balance_to_coldkey_account(&nominator3, stake * 2.into() + ed.into()); let reserve = u64::from(stake) * 10; mock::setup_reserves(netuid1, reserve.into(), reserve.into()); @@ -1012,12 +1023,42 @@ fn test_coldkey_swap_total() { add_network(netuid1, 13, 0); add_network(netuid2, 14, 0); add_network(netuid3, 15, 0); + + // Registrations (burns happen here) register_ok_neuron(netuid1, hotkey1, coldkey, 0); register_ok_neuron(netuid2, hotkey2, coldkey, 0); register_ok_neuron(netuid3, hotkey3, coldkey, 0); register_ok_neuron(netuid1, delegate1, delegate1, 0); register_ok_neuron(netuid2, delegate2, delegate2, 0); register_ok_neuron(netuid3, delegate3, delegate3, 0); + + // ------------------------------------------------------------ + // After the burn-based registrations, ensure each staking coldkey still + // has enough free balance to perform its staking actions. + // + // Each of these accounts will stake `stake * N`, and we want them to + // also retain ED so they don't get reaped mid-test. + // ------------------------------------------------------------ + let ensure_min_balance = |account: &U256, required: TaoBalance| { + let bal = SubtensorModule::get_coldkey_balance(account); + if bal < required { + SubtensorModule::add_balance_to_coldkey_account(account, required - bal); + } + }; + + // coldkey stakes 6 times + ensure_min_balance(&coldkey, stake * 6.into() + ed); + + // each delegate stakes 2 times + ensure_min_balance(&delegate1, stake * 2.into() + ed); + ensure_min_balance(&delegate2, stake * 2.into() + ed); + ensure_min_balance(&delegate3, stake * 2.into() + ed); + + // each nominator stakes 2 times + ensure_min_balance(&nominator1, stake * 2.into() + ed); + ensure_min_balance(&nominator2, stake * 2.into() + ed); + ensure_min_balance(&nominator3, stake * 2.into() + ed); + Delegates::::insert(hotkey1, u16::MAX / 10); Delegates::::insert(hotkey2, u16::MAX / 10); Delegates::::insert(hotkey3, u16::MAX / 10); @@ -1523,6 +1564,39 @@ macro_rules! comprehensive_setup { $hotkey2:expr, $hotkey3:expr ) => {{ + comprehensive_setup!( + $who, + $new_coldkey, + $new_coldkey_hash, + $stake1, + $stake2, + $stake3, + $hotkey1, + $hotkey2, + $hotkey3, + ExistentialDeposit::get() + ) + }}; + + // New form: caller specifies exactly how much free balance must remain + // after staking (e.g. ED + swap_cost, or ED). + ( + $who:expr, + $new_coldkey:expr, + $new_coldkey_hash:expr, + $stake1:expr, + $stake2:expr, + $stake3:expr, + $hotkey1:expr, + $hotkey2:expr, + $hotkey3:expr, + $expected_remaining_balance:expr + ) => {{ + let stake1: TaoBalance = $stake1.into(); + let stake2: TaoBalance = $stake2.into(); + let stake3: TaoBalance = $stake3.into(); + let expected_remaining: TaoBalance = $expected_remaining_balance.into(); + // Setup networks and subnet ownerships let netuid1 = NetUid::from(1); let netuid2 = NetUid::from(2); @@ -1532,8 +1606,8 @@ macro_rules! comprehensive_setup { SubnetOwner::::insert(netuid2, $who); // Setup reserves - let reserve1 = u64::from($stake1 + $stake3) * 10; - let reserve2 = u64::from($stake2) * 10; + let reserve1 = u64::from(stake1 + stake3) * 10; + let reserve2 = u64::from(stake2) * 10; mock::setup_reserves(netuid1, reserve1.into(), reserve1.into()); mock::setup_reserves(netuid2, reserve2.into(), reserve2.into()); @@ -1563,23 +1637,38 @@ macro_rules! comprehensive_setup { assert_eq!(Owner::::get($hotkey2), $who); assert_eq!(Owner::::get($hotkey3), $who); + // ------------------------------------------------------------ + // After registrations, ensure $who has enough free balance to: + // (stake1 + stake2 + stake3) + expected_remaining_balance + // so the add_stake calls won't fail AND the remaining free balance + // after staking is exactly what the tests expect. + // ------------------------------------------------------------ + let stake_total = stake1 + stake2 + stake3; + let required_free = stake_total + expected_remaining; + + let current_free = SubtensorModule::get_coldkey_balance(&$who); + if current_free < required_free { + SubtensorModule::add_balance_to_coldkey_account(&$who, required_free - current_free); + } + + // Now staking will succeed and leave exactly expected_remaining behind. assert_ok!(SubtensorModule::add_stake( <::RuntimeOrigin>::signed($who), $hotkey1, netuid1, - $stake1.into() + stake1.into() )); assert_ok!(SubtensorModule::add_stake( <::RuntimeOrigin>::signed($who), $hotkey2, netuid2, - $stake2.into() + stake2.into() )); assert_ok!(SubtensorModule::add_stake( <::RuntimeOrigin>::signed($who), $hotkey3, netuid1, - $stake3.into() + stake3.into() )); let hk1_alpha = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&$hotkey1, &$who, netuid1); diff --git a/pallets/subtensor/src/tests/uids.rs b/pallets/subtensor/src/tests/uids.rs index 9cc74e5286..11b41e4667 100644 --- a/pallets/subtensor/src/tests/uids.rs +++ b/pallets/subtensor/src/tests/uids.rs @@ -22,37 +22,26 @@ fn test_replace_neuron() { let netuid = NetUid::from(1); let tempo: u16 = 13; let hotkey_account_id = U256::from(1); - let (nonce, work): (u64, Vec) = SubtensorModule::create_work_for_block_number( - netuid, - block_number, - 111111, - &hotkey_account_id, - ); let coldkey_account_id = U256::from(1234); let new_hotkey_account_id = U256::from(2); let _new_colkey_account_id = U256::from(12345); let evm_address = H160::from_slice(&[1_u8; 20]); - //add network + + System::set_block_number(block_number); + + // Add network. add_network(netuid, tempo, 0); // Register a neuron. - assert_ok!(SubtensorModule::register( - <::RuntimeOrigin>::signed(hotkey_account_id), - netuid, - block_number, - nonce, - work, - hotkey_account_id, - coldkey_account_id - )); + register_ok_neuron(netuid, hotkey_account_id, coldkey_account_id, 0); - // Get UID + // Get UID. let neuron_uid = SubtensorModule::get_uid_for_net_and_hotkey(netuid, &hotkey_account_id); assert_ok!(neuron_uid); let neuron_uid = neuron_uid.unwrap(); - // set non-default values + // Set non-default values. Trust::::mutate(netuid, |v| { SubtensorModule::set_element_at(v, neuron_uid as usize, 5u16) }); @@ -101,7 +90,7 @@ fn test_replace_neuron() { )); assert_eq!(curr_hotkey.unwrap(), new_hotkey_account_id); - // Check neuron certificate was reset + // Check neuron certificate was reset. let certificate = NeuronCertificates::::get(netuid, hotkey_account_id); assert_eq!(certificate, None); @@ -151,38 +140,27 @@ fn test_bonds_cleared_on_replace() { let netuid = NetUid::from(1); let tempo: u16 = 13; let hotkey_account_id = U256::from(1); - let (nonce, work): (u64, Vec) = SubtensorModule::create_work_for_block_number( - netuid, - block_number, - 111111, - &hotkey_account_id, - ); let coldkey_account_id = U256::from(1234); let new_hotkey_account_id = U256::from(2); let _new_colkey_account_id = U256::from(12345); let evm_address = H160::from_slice(&[1_u8; 20]); - //add network + System::set_block_number(block_number); + + // Add network. add_network(netuid, tempo, 0); // Register a neuron. - assert_ok!(SubtensorModule::register( - <::RuntimeOrigin>::signed(hotkey_account_id), - netuid, - block_number, - nonce, - work, - hotkey_account_id, - coldkey_account_id - )); + register_ok_neuron(netuid, hotkey_account_id, coldkey_account_id, 0); - // Get UID + // Get UID. let neuron_uid = SubtensorModule::get_uid_for_net_and_hotkey(netuid, &hotkey_account_id); assert_ok!(neuron_uid); let neuron_uid = neuron_uid.unwrap(); AssociatedEvmAddress::::insert(netuid, neuron_uid, (evm_address, 1)); - // set non-default bonds + + // Set non-default bonds. Bonds::::insert(NetUidStorageIndex::from(netuid), neuron_uid, vec![(0, 1)]); // Replace the neuron. @@ -226,48 +204,23 @@ fn test_replace_neuron_multiple_subnets() { let hotkey_account_id = U256::from(1); let new_hotkey_account_id = U256::from(2); - let (nonce, work): (u64, Vec) = SubtensorModule::create_work_for_block_number( - netuid, - block_number, - 111111, - &hotkey_account_id, - ); - let (nonce1, work1): (u64, Vec) = SubtensorModule::create_work_for_block_number( - netuid1, - block_number, - 111111 * 5, - &hotkey_account_id, - ); - let coldkey_account_id = U256::from(1234); let _new_colkey_account_id = U256::from(12345); let evm_address = H160::from_slice(&[1_u8; 20]); - //add network + + // Make sure the environment is at the expected block. + System::set_block_number(block_number); + + // Add networks. add_network(netuid, tempo, 0); add_network(netuid1, tempo, 0); // Register a neuron on both networks. - assert_ok!(SubtensorModule::register( - <::RuntimeOrigin>::signed(hotkey_account_id), - netuid, - block_number, - nonce, - work, - hotkey_account_id, - coldkey_account_id - )); - assert_ok!(SubtensorModule::register( - <::RuntimeOrigin>::signed(hotkey_account_id), - netuid1, - block_number, - nonce1, - work1, - hotkey_account_id, - coldkey_account_id - )); + register_ok_neuron(netuid, hotkey_account_id, coldkey_account_id, 0); + register_ok_neuron(netuid1, hotkey_account_id, coldkey_account_id, 0); - // Get UID + // Get UID. let neuron_uid = SubtensorModule::get_uid_for_net_and_hotkey(netuid, &hotkey_account_id); assert_ok!(neuron_uid); @@ -286,8 +239,7 @@ fn test_replace_neuron_multiple_subnets() { AssociatedEvmAddress::::insert(netuid, neuron_uid.unwrap(), (evm_address, 1)); - // Replace the neuron. - // Only replace on ONE network. + // Replace the neuron (ONLY on ONE network). SubtensorModule::replace_neuron( netuid, neuron_uid.unwrap(), diff --git a/pallets/subtensor/src/utils/misc.rs b/pallets/subtensor/src/utils/misc.rs index 75f2d6695d..da589435e9 100644 --- a/pallets/subtensor/src/utils/misc.rs +++ b/pallets/subtensor/src/utils/misc.rs @@ -911,4 +911,103 @@ impl Pallet { pub fn set_tao_flow_smoothing_factor(smoothing_factor: u64) { FlowEmaSmoothingFactor::::set(smoothing_factor); } + + /// Saturating exponentiation by squaring. + pub fn saturating_pow_u64(base: u64, exp: u16) -> u64 { + let mut result: u64 = 1; + let mut factor: u64 = base; + let mut power: u32 = u32::from(exp); + + while power > 0 { + if (power & 1) == 1 { + result = result.saturating_mul(factor); + } + + power >>= 1; + + if power > 0 { + factor = factor.saturating_mul(factor); + } + } + + result + } + + /// Multiply an integer `value` by a Q32 fixed-point factor. + /// + /// Q32 means: + /// 1.0 == 1 << 32 + /// 0.5 == 1 << 31 + /// + /// Safe / non-panicking: + /// - uses saturating u128 multiplication + /// - clamps back into u64 range + pub fn mul_by_q32(value: u64, factor_q32: u64) -> u64 { + let product: u128 = (value as u128).saturating_mul(factor_q32 as u128); + let shifted: u128 = product >> 32; + core::cmp::min(shifted, u64::MAX as u128) as u64 + } + + /// Exponentiation-by-squaring for Q32 values. + /// + /// Returns `base_q32 ^ exp` in Q32. + /// + /// Safe / non-panicking: + /// - uses `mul_by_q32`, which is saturating/clamped + pub fn pow_q32(base_q32: u64, exp: u16) -> u64 { + let mut result: u64 = 1u64 << 32; // 1.0 in Q32 + let mut factor: u64 = base_q32; + let mut power: u32 = u32::from(exp); + + while power > 0 { + if (power & 1) == 1 { + result = Self::mul_by_q32(result, factor); + } + + power >>= 1; + + if power > 0 { + factor = Self::mul_by_q32(factor, factor); + } + } + + result + } + + /// Returns the per-block decay factor `f` in Q32 + pub fn decay_factor_q32(half_life: u16) -> u64 { + if half_life == 0 { + return 1u64 << 32; // 1.0 + } + + let one_q32: u64 = 1u64 << 32; + let half_q32: u64 = 1u64 << 31; // 0.5 + + let mut lo: u64 = 0; + let mut hi: u64 = one_q32; + + while lo.saturating_add(1) < hi { + let span: u64 = hi.saturating_sub(lo); + let mid: u64 = lo.saturating_add(span >> 1); + let mid_pow: u64 = Self::pow_q32(mid, half_life); + + if mid_pow > half_q32 { + hi = mid; + } else { + lo = mid; + } + } + + lo + } + + /// Checked division helper that returns 0 on division-by-zero. + /// + /// Safe / non-panicking. + pub fn checked_div_or_zero_u64(numerator: u64, denominator: u64) -> u64 { + match numerator.checked_div(denominator) { + Some(v) => v, + None => 0, + } + } } diff --git a/pallets/transaction-fee/src/tests/mock.rs b/pallets/transaction-fee/src/tests/mock.rs index f0ad323168..7116b432b9 100644 --- a/pallets/transaction-fee/src/tests/mock.rs +++ b/pallets/transaction-fee/src/tests/mock.rs @@ -573,25 +573,70 @@ pub fn register_ok_neuron( netuid: NetUid, hotkey_account_id: U256, coldkey_account_id: U256, - start_nonce: u64, + _start_nonce: u64, ) { - let block_number: u64 = SubtensorModule::get_current_block_as_u64(); - let (nonce, work): (u64, Vec) = SubtensorModule::create_work_for_block_number( - netuid, - block_number, - start_nonce, - &hotkey_account_id, - ); - let result = SubtensorModule::register( - <::RuntimeOrigin>::signed(hotkey_account_id), - netuid, - block_number, - nonce, - work, - hotkey_account_id, - coldkey_account_id, + // Ensure reserves exist for swap/burn path, but do NOT clobber reserves if the test already set them. + let reserve: u64 = 1_000_000_000_000; + let tao_reserve = SubnetTAO::::get(netuid); + let alpha_reserve = SubnetAlphaIn::::get(netuid) + .saturating_add(SubnetAlphaInProvided::::get(netuid)); + + if tao_reserve.is_zero() && alpha_reserve.is_zero() { + setup_reserves(netuid, reserve.into(), reserve.into()); + } + + // Ensure coldkey has enough to pay the current burn AND is not fully drained to zero. + // This avoids ZeroBalanceAfterWithdrawn in burned_register. + let top_up_for_burn = |netuid: NetUid, cold: U256| { + let burn: TaoBalance = SubtensorModule::get_burn(netuid); + + // Make sure something remains after withdrawal even if ED is 0 in tests. + let ed: TaoBalance = ExistentialDeposit::get(); + let min_remaining: TaoBalance = ed.max(1.into()); + + // Small buffer for safety (fees / rounding / future changes). + let buffer: TaoBalance = 10.into(); + + let min_balance_needed: TaoBalance = + burn.saturating_add(min_remaining).saturating_add(buffer); + + let bal: TaoBalance = SubtensorModule::get_coldkey_balance(&cold); + if bal < min_balance_needed { + SubtensorModule::add_balance_to_coldkey_account(&cold, min_balance_needed - bal); + } + }; + + top_up_for_burn(netuid, coldkey_account_id); + + let origin = <::RuntimeOrigin>::signed(coldkey_account_id); + let result = SubtensorModule::burned_register(origin.clone(), netuid, hotkey_account_id); + + match result { + Ok(()) => { + // success + } + Err(e) + if e == Error::::TooManyRegistrationsThisInterval.into() + || e == Error::::NotEnoughBalanceToStake.into() + || e == Error::::ZeroBalanceAfterWithdrawn.into() => + { + // Re-top-up and retry once (burn can be state-dependent). + top_up_for_burn(netuid, coldkey_account_id); + + assert_ok!(SubtensorModule::burned_register( + origin, + netuid, + hotkey_account_id + )); + } + Err(e) => { + panic!("Expected Ok(_). Got Err({e:?})"); + } + } + + log::info!( + "Register ok neuron: netuid: {netuid:?}, coldkey: {coldkey_account_id:?}, hotkey: {hotkey_account_id:?}" ); - assert_ok!(result); } #[allow(dead_code)] @@ -759,18 +804,22 @@ pub(crate) fn remove_stake_rate_limit_for_tests(hotkey: &U256, coldkey: &U256, n } #[allow(dead_code)] -pub fn setup_stake(netuid: NetUid, coldkey: &U256, hotkey: &U256, amount: u64) { - // Stake to hotkey account, and check if the result is ok +pub fn setup_stake( + netuid: subtensor_runtime_common::NetUid, + coldkey: &U256, + hotkey: &U256, + stake_amount: u64, +) { + // Fund enough to stake while keeping the coldkey account alive (KeepAlive / ED). SubtensorModule::add_balance_to_coldkey_account( coldkey, - ExistentialDeposit::get() + amount.into(), + TaoBalance::from(stake_amount) + ExistentialDeposit::get(), ); - remove_stake_rate_limit_for_tests(hotkey, coldkey, netuid); + assert_ok!(SubtensorModule::add_stake( RuntimeOrigin::signed(*coldkey), *hotkey, netuid, - amount.into() + stake_amount.into(), )); - remove_stake_rate_limit_for_tests(hotkey, coldkey, netuid); } diff --git a/pallets/transaction-fee/src/tests/mod.rs b/pallets/transaction-fee/src/tests/mod.rs index 5dd353dcde..2be94446cf 100644 --- a/pallets/transaction-fee/src/tests/mod.rs +++ b/pallets/transaction-fee/src/tests/mod.rs @@ -18,17 +18,59 @@ mod mock; #[test] fn test_remove_stake_fees_tao() { new_test_ext().execute_with(|| { - let stake_amount = TAO; + use frame_support::traits::Hooks; + use sp_runtime::traits::SaturatedConversion; + + type BN = frame_system::pallet_prelude::BlockNumberFor; + + // Advance blocks and run hooks so staking-op rate limit windows reset. + let jump_blocks = |delta: u64| { + let current_bn: BN = frame_system::Pallet::::block_number(); + + // Finish current block. + >::on_finalize(current_bn); + as Hooks>::on_finalize(current_bn); + + let current_u64: u64 = current_bn.saturated_into(); + // Use a delta that won’t land on tempo boundaries (tempo is set to 10 in setup_subnets). + let next_u64: u64 = current_u64.saturating_add(delta); + let next_bn: BN = next_u64.saturated_into(); + + frame_system::Pallet::::set_block_number(next_bn); + + // Start next block. + as Hooks>::on_initialize(next_bn); + >::on_initialize(next_bn); + }; + + let stake_amount = TaoBalance::from(TAO); let unstake_amount = AlphaBalance::from(TAO / 50); + + // setup_subnets() -> register_ok_neuron() calls SubtensorModule::register(...) + // which now requires sufficient balance to stake during registration. + // setup_subnets() uses coldkey=10000 and first neuron hotkey=20001. + let register_prefund = stake_amount + .saturating_mul(10_000.into()) // generous buffer + .saturating_add(ExistentialDeposit::get()); + SubtensorModule::add_balance_to_coldkey_account(&U256::from(10000), register_prefund); + SubtensorModule::add_balance_to_coldkey_account(&U256::from(20001), register_prefund); + let sn = setup_subnets(1, 1); + + // Avoid staking-op rate limit between registration and staking. + jump_blocks(1_000_001); + setup_stake( sn.subnets[0].netuid, &sn.coldkey, &sn.hotkeys[0], - stake_amount, + stake_amount.into(), ); SubtensorModule::add_balance_to_coldkey_account(&sn.coldkey, TaoBalance::from(TAO)); + // Avoid staking-op rate limit between add_stake and remove_stake. + jump_blocks(1_000_001); + // Simulate stake removal to get how much TAO should we get for unstaked Alpha let (expected_unstaked_tao, _swap_fee) = mock::swap_alpha_to_tao(sn.subnets[0].netuid, unstake_amount); @@ -49,13 +91,14 @@ fn test_remove_stake_fees_tao() { // Dispatch the extrinsic with ChargeTransactionPayment extension let info = call.get_dispatch_info(); let ext = pallet_transaction_payment::ChargeTransactionPayment::::from(0.into()); - assert_ok!(ext.dispatch_transaction( - RuntimeOrigin::signed(sn.coldkey).into(), - call, - &info, - 0, - 0, - )); + + // dispatch_transaction() is nested: + // - Outer Result: validation / payment extension checks + // - Inner Result: actual runtime call dispatch result + let inner = ext + .dispatch_transaction(RuntimeOrigin::signed(sn.coldkey).into(), call, &info, 0, 0) + .expect("Expected Ok(_) from dispatch_transaction (validation)"); + assert_ok!(inner); let final_balance = Balances::free_balance(sn.coldkey); let alpha_after = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( @@ -73,7 +116,6 @@ fn test_remove_stake_fees_tao() { assert_eq!(actual_alpha_fee, AlphaBalance::from(0)); }); } - // cargo test --package subtensor-transaction-fee --lib -- tests::test_remove_stake_fees_alpha --exact --show-output #[test] #[ignore] @@ -324,14 +366,21 @@ fn test_remove_stake_completely_fees_alpha() { #[test] fn test_remove_stake_not_enough_balance_for_fees() { new_test_ext().execute_with(|| { - let stake_amount = TAO; + let stake_amount = TaoBalance::from(TAO); let sn = setup_subnets(1, 1); - setup_stake( - sn.subnets[0].netuid, + + SubtensorModule::add_balance_to_coldkey_account( &sn.coldkey, - &sn.hotkeys[0], - stake_amount, + stake_amount + .saturating_mul(2.into()) // buffer so staking doesn't attempt to drain the account + .saturating_add(ExistentialDeposit::get()), ); + assert_ok!(SubtensorModule::add_stake( + RuntimeOrigin::signed(sn.coldkey), + sn.hotkeys[0], + sn.subnets[0].netuid, + stake_amount.into(), + )); // Simulate stake removal to get how much TAO should we get for unstaked Alpha let current_stake = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( @@ -470,15 +519,23 @@ fn test_remove_stake_edge_alpha() { #[test] fn test_remove_stake_failing_transaction_tao_fees() { new_test_ext().execute_with(|| { - let stake_amount = TAO; + let stake_amount = TaoBalance::from(TAO); let unstake_amount = AlphaBalance::from(TAO / 50); let sn = setup_subnets(1, 1); - setup_stake( - sn.subnets[0].netuid, + + SubtensorModule::add_balance_to_coldkey_account( &sn.coldkey, - &sn.hotkeys[0], - stake_amount, + stake_amount + .saturating_mul(2.into()) // buffer so staking doesn't attempt to drain the account + .saturating_add(ExistentialDeposit::get()), ); + assert_ok!(SubtensorModule::add_stake( + RuntimeOrigin::signed(sn.coldkey), + sn.hotkeys[0], + sn.subnets[0].netuid, + stake_amount.into(), + )); + SubtensorModule::add_balance_to_coldkey_account(&sn.coldkey, TAO.into()); // Make unstaking fail by reducing liquidity to critical diff --git a/precompiles/src/neuron.rs b/precompiles/src/neuron.rs index e2602c0246..cb96bf4e01 100644 --- a/precompiles/src/neuron.rs +++ b/precompiles/src/neuron.rs @@ -142,6 +142,34 @@ where handle.try_dispatch_runtime_call::(call, RawOrigin::Signed(coldkey)) } + fn call_register_limit( + handle: &mut impl PrecompileHandle, + netuid: u16, + hotkey: H256, + limit_price: u64, + ) -> EvmResult<()> { + let coldkey = handle.caller_account_id::(); + let hotkey = R::AccountId::from(hotkey.0); + let call = pallet_subtensor::Call::::register_limit { + netuid: netuid.into(), + hotkey, + limit_price, + }; + + handle.try_dispatch_runtime_call::(call, RawOrigin::Signed(coldkey)) + } + + #[precompile::public("registerLimit(uint16,bytes32,uint64)")] + #[precompile::payable] + fn register_limit( + handle: &mut impl PrecompileHandle, + netuid: u16, + hotkey: H256, + limit_price: u64, + ) -> EvmResult<()> { + Self::call_register_limit(handle, netuid, hotkey, limit_price) + } + #[precompile::public("serveAxon(uint16,uint32,uint128,uint16,uint8,uint8,uint8,uint8)")] #[precompile::payable] #[allow(clippy::too_many_arguments)]