diff --git a/pallets/subtensor/src/coinbase/run_coinbase.rs b/pallets/subtensor/src/coinbase/run_coinbase.rs index badb811fa..aa3fa17d2 100644 --- a/pallets/subtensor/src/coinbase/run_coinbase.rs +++ b/pallets/subtensor/src/coinbase/run_coinbase.rs @@ -48,6 +48,9 @@ impl Pallet { // --- 3. Drain the subnet block emission and accumulate it as subnet emission, which increases until the tempo is reached in #4. // subnet_blockwise_emission -> subnet_pending_emission for netuid in subnets.clone().iter() { + if *netuid == 0 { + continue; + } // --- 3.1 Get the network's block-wise emission amount. // This value is newly minted TAO which has not reached staking accounts yet. let subnet_blockwise_emission: u64 = EmissionValues::::get(*netuid); @@ -87,6 +90,11 @@ impl Pallet { Self::set_blocks_since_last_step(*netuid, 0); Self::set_last_mechanism_step_block(*netuid, current_block); + if *netuid == 0 { + // Skip netuid 0 payouts + continue; + } + // --- 4.4 Distribute owner take. if SubnetOwner::::contains_key(netuid) { // Does the subnet have an owner? @@ -295,8 +303,8 @@ impl Pallet { // --- 8 Iterate over each nominator. if total_viable_nominator_stake != 0 { for (nominator, nominator_stake) in Stake::::iter_prefix(hotkey) { - // --- 9 Check if the stake was manually increased by the user since the last emission drain for this hotkey. - // If it was, skip this nominator as they will not receive their proportion of the emission. + // --- 9 Skip emission for any stake the was added by the nominator since the last emission drain. + // This means the nominator will get emission on existing stake, but not on new stake, until the next emission drain. let viable_nominator_stake = nominator_stake.saturating_sub(Self::get_nonviable_stake(hotkey, &nominator)); @@ -323,7 +331,10 @@ impl Pallet { let hotkey_new_tao: u64 = hotkey_take.saturating_add(remainder); Self::increase_stake_on_hotkey_account(hotkey, hotkey_new_tao); - // --- 14 Record new tao creation event and return the amount created. + // --- 14 Reset the stake delta for the hotkey. + let _ = StakeDeltaSinceLastEmissionDrain::::clear_prefix(hotkey, u32::MAX, None); + + // --- 15 Record new tao creation event and return the amount created. total_new_tao = total_new_tao.saturating_add(hotkey_new_tao); total_new_tao } diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index d93253cfa..958ef3480 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -1299,7 +1299,7 @@ pub mod pallet { /// Returns the transaction priority for setting weights. pub fn get_priority_set_weights(hotkey: &T::AccountId, netuid: u16) -> u64 { if let Ok(uid) = Self::get_uid_for_net_and_hotkey(netuid, hotkey) { - let _stake = Self::get_total_stake_for_hotkey(hotkey); + let _stake = Self::get_stake_for_hotkey_on_subnet(hotkey, netuid); let current_block_number: u64 = Self::get_current_block_as_u64(); let default_priority: u64 = current_block_number.saturating_sub(Self::get_last_update_for_uid(netuid, uid)); @@ -1309,9 +1309,9 @@ pub mod pallet { } /// Is the caller allowed to set weights - pub fn check_weights_min_stake(hotkey: &T::AccountId) -> bool { + pub fn check_weights_min_stake(hotkey: &T::AccountId, netuid: u16) -> bool { // Blacklist weights transactions for low stake peers. - Self::get_total_stake_for_hotkey(hotkey) >= Self::get_weights_min_stake() + Self::get_stake_for_hotkey_on_subnet(hotkey, netuid) >= Self::get_weights_min_stake() } /// Helper function to check if register is allowed @@ -1404,8 +1404,8 @@ where Pallet::::get_priority_set_weights(who, netuid) } - pub fn check_weights_min_stake(who: &T::AccountId) -> bool { - Pallet::::check_weights_min_stake(who) + pub fn check_weights_min_stake(who: &T::AccountId, netuid: u16) -> bool { + Pallet::::check_weights_min_stake(who, netuid) } } @@ -1443,7 +1443,7 @@ where ) -> TransactionValidity { match call.is_sub_type() { Some(Call::commit_weights { netuid, .. }) => { - if Self::check_weights_min_stake(who) { + if Self::check_weights_min_stake(who, *netuid) { let priority: u64 = Self::get_priority_set_weights(who, *netuid); Ok(ValidTransaction { priority, @@ -1455,7 +1455,7 @@ where } } Some(Call::reveal_weights { netuid, .. }) => { - if Self::check_weights_min_stake(who) { + if Self::check_weights_min_stake(who, *netuid) { let priority: u64 = Self::get_priority_set_weights(who, *netuid); Ok(ValidTransaction { priority, @@ -1467,7 +1467,7 @@ where } } Some(Call::batch_reveal_weights { netuid, .. }) => { - if Self::check_weights_min_stake(who) { + if Self::check_weights_min_stake(who, *netuid) { let priority: u64 = Self::get_priority_set_weights(who, *netuid); Ok(ValidTransaction { priority, @@ -1479,7 +1479,7 @@ where } } Some(Call::set_weights { netuid, .. }) => { - if Self::check_weights_min_stake(who) { + if Self::check_weights_min_stake(who, *netuid) { let priority: u64 = Self::get_priority_set_weights(who, *netuid); Ok(ValidTransaction { priority, @@ -1491,7 +1491,7 @@ where } } Some(Call::set_root_weights { netuid, hotkey, .. }) => { - if Self::check_weights_min_stake(hotkey) { + if Self::check_weights_min_stake(hotkey, *netuid) { let priority: u64 = Self::get_priority_set_weights(hotkey, *netuid); Ok(ValidTransaction { priority, diff --git a/pallets/subtensor/src/rpc_info/neuron_info.rs b/pallets/subtensor/src/rpc_info/neuron_info.rs index cadd4b6e3..be367a566 100644 --- a/pallets/subtensor/src/rpc_info/neuron_info.rs +++ b/pallets/subtensor/src/rpc_info/neuron_info.rs @@ -1,6 +1,5 @@ use super::*; use frame_support::pallet_prelude::{Decode, Encode}; -use frame_support::storage::IterableStorageDoubleMap; extern crate alloc; use codec::Compact; @@ -179,12 +178,10 @@ impl Pallet { let last_update = Self::get_last_update_for_uid(netuid, uid); let validator_permit = Self::get_validator_permit_for_uid(netuid, uid); - let stake: Vec<(T::AccountId, Compact)> = - as IterableStorageDoubleMap>::iter_prefix( - hotkey.clone(), - ) - .map(|(coldkey, stake)| (coldkey, stake.into())) - .collect(); + let stake: Vec<(T::AccountId, Compact)> = vec![( + coldkey.clone(), + Self::get_stake_for_hotkey_on_subnet(&hotkey, netuid).into(), + )]; let neuron = NeuronInfoLite { hotkey: hotkey.clone(), diff --git a/pallets/subtensor/src/staking/helpers.rs b/pallets/subtensor/src/staking/helpers.rs index 0328d94e6..9fd60ea51 100644 --- a/pallets/subtensor/src/staking/helpers.rs +++ b/pallets/subtensor/src/staking/helpers.rs @@ -297,6 +297,9 @@ impl Pallet { staking_hotkeys.retain(|h| h != hotkey); StakingHotkeys::::insert(coldkey, staking_hotkeys); + // Update stake delta + StakeDeltaSinceLastEmissionDrain::::remove(hotkey, coldkey); + current_stake } @@ -431,6 +434,9 @@ impl Pallet { // Add the balance to the coldkey account. Self::add_balance_to_coldkey_account(&delegate_coldkey_i, stake_i); + + // Remove stake delta + StakeDeltaSinceLastEmissionDrain::::remove(hotkey, &delegate_coldkey_i); } } } diff --git a/pallets/subtensor/src/staking/remove_stake.rs b/pallets/subtensor/src/staking/remove_stake.rs index 4118e8d07..587583f5e 100644 --- a/pallets/subtensor/src/staking/remove_stake.rs +++ b/pallets/subtensor/src/staking/remove_stake.rs @@ -76,6 +76,11 @@ impl Pallet { // We remove the balance from the hotkey. Self::decrease_stake_on_coldkey_hotkey_account(&coldkey, &hotkey, stake_to_be_removed); + // Track this removal in the stake delta. + StakeDeltaSinceLastEmissionDrain::::mutate(&hotkey, &coldkey, |stake_delta| { + *stake_delta = stake_delta.saturating_sub_unsigned(stake_to_be_removed as u128); + }); + // We add the balance to the coldkey. If the above fails we will not credit this coldkey. Self::add_balance_to_coldkey_account(&coldkey, stake_to_be_removed); diff --git a/pallets/subtensor/src/subnets/uids.rs b/pallets/subtensor/src/subnets/uids.rs index 2a5ceedb4..11f59602d 100644 --- a/pallets/subtensor/src/subnets/uids.rs +++ b/pallets/subtensor/src/subnets/uids.rs @@ -120,7 +120,7 @@ impl Pallet { /// pub fn get_stake_for_uid_and_subnetwork(netuid: u16, neuron_uid: u16) -> u64 { if let Ok(hotkey) = Self::get_hotkey_for_net_and_uid(netuid, neuron_uid) { - Self::get_total_stake_for_hotkey(&hotkey) + Self::get_stake_for_hotkey_on_subnet(&hotkey, netuid) } else { 0 } diff --git a/pallets/subtensor/src/subnets/weights.rs b/pallets/subtensor/src/subnets/weights.rs index 87042f456..c511bceae 100644 --- a/pallets/subtensor/src/subnets/weights.rs +++ b/pallets/subtensor/src/subnets/weights.rs @@ -525,9 +525,9 @@ impl Pallet { Error::::HotKeyNotRegisteredInSubNet ); - // --- 6. Check to see if the hotkey has enought stake to set weights. + // --- 6. Check to see if the hotkey has enough stake to set weights. ensure!( - Self::get_total_stake_for_hotkey(&hotkey) >= Self::get_weights_min_stake(), + Self::check_weights_min_stake(&hotkey, netuid), Error::::NotEnoughStakeToSetWeights ); diff --git a/pallets/subtensor/src/swap/swap_coldkey.rs b/pallets/subtensor/src/swap/swap_coldkey.rs index bcbd2a330..4742c3fca 100644 --- a/pallets/subtensor/src/swap/swap_coldkey.rs +++ b/pallets/subtensor/src/swap/swap_coldkey.rs @@ -169,7 +169,20 @@ impl Pallet { weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); } - // 4. Swap total coldkey stake. + // 4. Swap StakeDeltaSinceLastEmissionDrain + for hotkey in StakingHotkeys::::get(old_coldkey) { + let old_stake_delta = StakeDeltaSinceLastEmissionDrain::::get(&hotkey, old_coldkey); + let new_stake_delta = StakeDeltaSinceLastEmissionDrain::::get(&hotkey, new_coldkey); + StakeDeltaSinceLastEmissionDrain::::insert( + &hotkey, + new_coldkey, + new_stake_delta.saturating_add(old_stake_delta), + ); + StakeDeltaSinceLastEmissionDrain::::remove(&hotkey, old_coldkey); + weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); + } + + // 5. Swap total coldkey stake. // TotalColdkeyStake: MAP ( coldkey ) --> u64 | Total stake of the coldkey. let old_coldkey_stake: u64 = TotalColdkeyStake::::get(old_coldkey); // Get the stake of the new coldkey. @@ -183,7 +196,7 @@ impl Pallet { ); weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); - // 5. Swap StakingHotkeys. + // 6. Swap StakingHotkeys. // StakingHotkeys: MAP ( coldkey ) --> Vec | Hotkeys staking for the coldkey. let old_staking_hotkeys: Vec = StakingHotkeys::::get(old_coldkey); let mut new_staking_hotkeys: Vec = StakingHotkeys::::get(new_coldkey); @@ -197,7 +210,7 @@ impl Pallet { StakingHotkeys::::insert(new_coldkey, new_staking_hotkeys); weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); - // 6. Swap hotkey owners. + // 7. Swap hotkey owners. // Owner: MAP ( hotkey ) --> coldkey | Owner of the hotkey. // OwnedHotkeys: MAP ( coldkey ) --> Vec | Hotkeys owned by the coldkey. let old_owned_hotkeys: Vec = OwnedHotkeys::::get(old_coldkey); @@ -216,7 +229,7 @@ impl Pallet { OwnedHotkeys::::insert(new_coldkey, new_owned_hotkeys); weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); - // 7. Transfer remaining balance. + // 8. Transfer remaining balance. // Balance: MAP ( coldkey ) --> u64 | Balance of the coldkey. // Transfer any remaining balance from old_coldkey to new_coldkey let remaining_balance = Self::get_coldkey_balance(old_coldkey); diff --git a/pallets/subtensor/tests/children.rs b/pallets/subtensor/tests/children.rs index 2b99030ab..0182888c0 100644 --- a/pallets/subtensor/tests/children.rs +++ b/pallets/subtensor/tests/children.rs @@ -3237,3 +3237,194 @@ fn test_rank_trust_incentive_calculation_with_parent_child() { }); } + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --test children -- test_childkey_set_weights_single_parent --exact --nocapture +#[test] +fn test_childkey_set_weights_single_parent() { + new_test_ext(1).execute_with(|| { + let netuid: u16 = 1; + add_network(netuid, 1, 0); + + // Define hotkeys + let parent: U256 = U256::from(1); + let child: U256 = U256::from(2); + let weight_setter: U256 = U256::from(3); + + // Define coldkeys with more readable names + let coldkey_parent: U256 = U256::from(100); + let coldkey_child: U256 = U256::from(101); + let coldkey_weight_setter: U256 = U256::from(102); + + let stake_to_give_child = 109_999; + + // Register parent with minimal stake and child with high stake + SubtensorModule::add_balance_to_coldkey_account(&coldkey_parent, 1); + SubtensorModule::add_balance_to_coldkey_account(&coldkey_child, stake_to_give_child + 10); + SubtensorModule::add_balance_to_coldkey_account(&coldkey_weight_setter, 1_000_000); + + // Add neurons for parent, child and weight_setter + register_ok_neuron(netuid, parent, coldkey_parent, 1); + register_ok_neuron(netuid, child, coldkey_child, 1); + register_ok_neuron(netuid, weight_setter, coldkey_weight_setter, 1); + + SubtensorModule::increase_stake_on_coldkey_hotkey_account( + &coldkey_parent, + &parent, + stake_to_give_child, + ); + SubtensorModule::increase_stake_on_coldkey_hotkey_account( + &coldkey_weight_setter, + &weight_setter, + 1_000_000, + ); + + SubtensorModule::set_weights_set_rate_limit(netuid, 0); + + // Set parent-child relationship + assert_ok!(SubtensorModule::do_set_children( + RuntimeOrigin::signed(coldkey_parent), + parent, + netuid, + vec![(u64::MAX, child)] + )); + step_block(7200 + 1); + // Set weights on the child using the weight_setter account + let origin = RuntimeOrigin::signed(weight_setter); + let uids: Vec = vec![1]; // Only set weight for the child (UID 1) + let values: Vec = vec![u16::MAX]; // Use maximum value for u16 + let version_key = SubtensorModule::get_weights_version_key(netuid); + assert_ok!(SubtensorModule::set_weights( + origin, + netuid, + uids.clone(), + values.clone(), + version_key + )); + + // Set the min stake very high + SubtensorModule::set_weights_min_stake(stake_to_give_child * 5); + + // Check the child has less stake than required + assert!( + SubtensorModule::get_stake_for_hotkey_on_subnet(&child, netuid) + < SubtensorModule::get_weights_min_stake() + ); + + // Check the child cannot set weights + assert_noop!( + SubtensorModule::set_weights( + RuntimeOrigin::signed(child), + netuid, + uids.clone(), + values.clone(), + version_key + ), + Error::::NotEnoughStakeToSetWeights + ); + + assert!(!SubtensorModule::check_weights_min_stake(&child, netuid)); + + // Set a minimum stake to set weights + SubtensorModule::set_weights_min_stake(stake_to_give_child - 5); + + // Check if the stake for the child is above + assert!( + SubtensorModule::get_stake_for_hotkey_on_subnet(&child, netuid) + >= SubtensorModule::get_weights_min_stake() + ); + + // Check the child can set weights + assert_ok!(SubtensorModule::set_weights( + RuntimeOrigin::signed(child), + netuid, + uids, + values, + version_key + )); + + assert!(SubtensorModule::check_weights_min_stake(&child, netuid)); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --test children -- test_set_weights_no_parent --exact --nocapture +#[test] +fn test_set_weights_no_parent() { + // Verify that a regular key without a parent delegation is effected by the minimum stake requirements + new_test_ext(1).execute_with(|| { + let netuid: u16 = 1; + add_network(netuid, 1, 0); + + let hotkey: U256 = U256::from(2); + let spare_hk: U256 = U256::from(3); + + let coldkey: U256 = U256::from(101); + let spare_ck = U256::from(102); + + let stake_to_give_child = 109_999; + + SubtensorModule::add_balance_to_coldkey_account(&coldkey, stake_to_give_child + 10); + + // Is registered + register_ok_neuron(netuid, hotkey, coldkey, 1); + // Register a spare key + register_ok_neuron(netuid, spare_hk, spare_ck, 1); + + SubtensorModule::increase_stake_on_coldkey_hotkey_account( + &coldkey, + &hotkey, + stake_to_give_child, + ); + + SubtensorModule::set_weights_set_rate_limit(netuid, 0); + + // Has stake and no parent + step_block(7200 + 1); + + let uids: Vec = vec![1]; // Set weights on the other hotkey + let values: Vec = vec![u16::MAX]; // Use maximum value for u16 + let version_key = SubtensorModule::get_weights_version_key(netuid); + + // Set the min stake very high + SubtensorModule::set_weights_min_stake(stake_to_give_child * 5); + + // Check the key has less stake than required + assert!( + SubtensorModule::get_stake_for_hotkey_on_subnet(&hotkey, netuid) + < SubtensorModule::get_weights_min_stake() + ); + + // Check the hotkey cannot set weights + assert_noop!( + SubtensorModule::set_weights( + RuntimeOrigin::signed(hotkey), + netuid, + uids.clone(), + values.clone(), + version_key + ), + Error::::NotEnoughStakeToSetWeights + ); + + assert!(!SubtensorModule::check_weights_min_stake(&hotkey, netuid)); + + // Set a minimum stake to set weights + SubtensorModule::set_weights_min_stake(stake_to_give_child - 5); + + // Check if the stake for the hotkey is above + assert!( + SubtensorModule::get_stake_for_hotkey_on_subnet(&hotkey, netuid) + >= SubtensorModule::get_weights_min_stake() + ); + + // Check the hotkey can set weights + assert_ok!(SubtensorModule::set_weights( + RuntimeOrigin::signed(hotkey), + netuid, + uids, + values, + version_key + )); + + assert!(SubtensorModule::check_weights_min_stake(&hotkey, netuid)); + }); +} diff --git a/pallets/subtensor/tests/coinbase.rs b/pallets/subtensor/tests/coinbase.rs index a6c1acde1..184f7d837 100644 --- a/pallets/subtensor/tests/coinbase.rs +++ b/pallets/subtensor/tests/coinbase.rs @@ -1,8 +1,11 @@ #![allow(unused, clippy::indexing_slicing, clippy::panic, clippy::unwrap_used)] use crate::mock::*; mod mock; -// use frame_support::{assert_err, assert_ok}; +use frame_support::assert_ok; use sp_core::U256; +use substrate_fixed::types::I64F64; + +use pallet_subtensor::TargetStakesPerInterval; // Test the ability to hash all sorts of hotkeys. #[test] @@ -154,3 +157,1294 @@ fn test_set_and_get_hotkey_emission_tempo() { assert_eq!(updated_tempo, new_tempo); }); } + +// Test getting nonviable stake +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test coinbase test_get_nonviable_stake -- --nocapture +#[test] +fn test_get_nonviable_stake() { + new_test_ext(1).execute_with(|| { + let netuid = 1u16; + let delegate_coldkey = U256::from(1); + let delegate_hotkey = U256::from(2); + let delegator = U256::from(3); + + let owner_added_stake = 123; + let owner_removed_stake = 456; + let owner_stake = 1_000 + owner_removed_stake; + // Add more than removed to test that the delta is updated correctly + let owner_adds_more_stake = owner_removed_stake + 1; + + let delegator_added_stake = 999; + + // Set stake rate limit very high + TargetStakesPerInterval::::put(1e9 as u64); + + add_network(netuid, 0, 0); + register_ok_neuron(netuid, delegate_hotkey, delegate_coldkey, 0); + // Give extra stake to the owner + SubtensorModule::increase_stake_on_coldkey_hotkey_account( + &delegate_coldkey, + &delegate_hotkey, + owner_stake, + ); + + // Register as a delegate + assert_ok!(SubtensorModule::become_delegate( + RuntimeOrigin::signed(delegate_coldkey), + delegate_hotkey + )); + + // Verify that the key starts with 0 nonviable stake + assert_eq!( + SubtensorModule::get_nonviable_stake(&delegate_hotkey, &delegate_coldkey), + 0 + ); + + // Give the coldkey some balance; extra just in-case + SubtensorModule::add_balance_to_coldkey_account( + &delegate_coldkey, + owner_added_stake + owner_adds_more_stake, + ); + + // Add some stake + assert_ok!(SubtensorModule::add_stake( + RuntimeOrigin::signed(delegate_coldkey), + delegate_hotkey, + owner_added_stake + )); + + // Verify the nonviable stake is the same as the added stake + assert_eq!( + SubtensorModule::get_nonviable_stake(&delegate_hotkey, &delegate_coldkey), + owner_added_stake + ); + + // Add some stake from a delegator + SubtensorModule::add_balance_to_coldkey_account(&delegator, delegator_added_stake); + assert_ok!(SubtensorModule::add_stake( + RuntimeOrigin::signed(delegator), + delegate_hotkey, + delegator_added_stake + )); + + // Verify that the nonviable stake doesn't change when a different account adds stake + assert_eq!( + SubtensorModule::get_nonviable_stake(&delegate_hotkey, &delegate_coldkey), + owner_added_stake + ); + + // Remove some stake + assert_ok!(SubtensorModule::remove_stake( + RuntimeOrigin::signed(delegate_coldkey), + delegate_hotkey, + owner_removed_stake + )); + + // The stake delta is negative, so the nonviable stake should be 0 + assert_eq!( + SubtensorModule::get_nonviable_stake(&delegate_hotkey, &delegate_coldkey), + 0 + ); + + // Add more stake than was removed + assert_ok!(SubtensorModule::add_stake( + RuntimeOrigin::signed(delegate_coldkey), + delegate_hotkey, + owner_adds_more_stake + )); + + // Verify that the nonviable stake is the net of the operations + assert_eq!( + SubtensorModule::get_nonviable_stake(&delegate_hotkey, &delegate_coldkey), + owner_adds_more_stake - owner_removed_stake + owner_added_stake + ); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --test coinbase test_coinbase_nominator_drainage_overflow -- --nocapture +#[test] +fn test_coinbase_nominator_drainage_overflow() { + new_test_ext(1).execute_with(|| { + // 1. Set up the network and accounts + let netuid: u16 = 1; + let hotkey = U256::from(0); + let coldkey = U256::from(3); + let nominator1 = U256::from(1); + let nominator2 = U256::from(2); + + log::debug!("Setting up network with netuid: {}", netuid); + log::debug!("Hotkey: {:?}, Coldkey: {:?}", hotkey, coldkey); + log::debug!("Nominators: {:?}, {:?}", nominator1, nominator2); + + // 2. Create network and register neuron + add_network(netuid, 1, 0); + register_ok_neuron(netuid, hotkey, coldkey, 100000); + SubtensorModule::create_account_if_non_existent(&coldkey, &hotkey); + + log::debug!("Network created and neuron registered"); + + // 3. Set up balances and stakes + SubtensorModule::add_balance_to_coldkey_account(&coldkey, 1000); + SubtensorModule::add_balance_to_coldkey_account(&nominator1, 1500); + SubtensorModule::add_balance_to_coldkey_account(&nominator2, 1500); + + log::debug!("Balances added to accounts"); + + // 4. Make the hotkey a delegate + let vali_take = (u16::MAX as u64 / 10); + assert_ok!(SubtensorModule::do_become_delegate( + RuntimeOrigin::signed(coldkey), + hotkey, + vali_take as u16 + )); + + log::debug!("Hotkey became a delegate with minimum take"); + + // Add stakes for nominators + // Add the stake directly to their coldkey-hotkey account + // This bypasses the accounting in stake delta + SubtensorModule::increase_stake_on_coldkey_hotkey_account(&nominator1, &hotkey, 5e9 as u64); + SubtensorModule::increase_stake_on_coldkey_hotkey_account(&nominator2, &hotkey, 5e9 as u64); + let initial_stake = 5e9 as u64; + + // Log the stakes for hotkey, nominator1, and nominator2 + log::debug!( + "Initial stakes - Hotkey: {}, Nominator1: {}, Nominator2: {}", + SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey, &hotkey), + SubtensorModule::get_stake_for_coldkey_and_hotkey(&nominator1, &hotkey), + SubtensorModule::get_stake_for_coldkey_and_hotkey(&nominator2, &hotkey) + ); + log::debug!("Stakes added for nominators"); + + // 5. Set emission and verify initial states + let to_emit = 20_000e9 as u64; + SubtensorModule::set_emission_values(&[netuid], vec![to_emit]).unwrap(); + assert_eq!(SubtensorModule::get_subnet_emission_value(netuid), to_emit); + assert_eq!(SubtensorModule::get_pending_hotkey_emission(&hotkey), 0); + assert_eq!( + SubtensorModule::get_total_stake_for_hotkey(&hotkey), + initial_stake * 2 + ); + assert_eq!(SubtensorModule::get_pending_emission(netuid), 0); + + log::debug!("Emission set and initial states verified"); + + // 6. Set hotkey emission tempo + SubtensorModule::set_hotkey_emission_tempo(1); + log::debug!("Hotkey emission tempo set to 1"); + + // 7. Simulate blocks and check emissions + next_block(); + assert_eq!(SubtensorModule::get_pending_emission(netuid), to_emit); + log::debug!( + "After first block, pending emission: {}", + SubtensorModule::get_pending_emission(netuid) + ); + + next_block(); + assert_eq!(SubtensorModule::get_pending_emission(netuid), 0); + assert_eq!(SubtensorModule::get_pending_hotkey_emission(&hotkey), 0); + log::debug!("After second block, pending emission drained"); + + // 8. Check final stakes + let hotkey_stake = SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey, &hotkey); + let nominator1_stake = + SubtensorModule::get_stake_for_coldkey_and_hotkey(&nominator1, &hotkey); + let nominator2_stake = + SubtensorModule::get_stake_for_coldkey_and_hotkey(&nominator2, &hotkey); + + log::debug!( + "Final stakes - Hotkey: {}, Nominator1: {}, Nominator2: {}", + hotkey_stake, + nominator1_stake, + nominator2_stake + ); + + // 9. Verify distribution + let total_emission = to_emit * 2; // to_emit per block for 2 blocks + let hotkey_emission = (I64F64::from_num(total_emission) / I64F64::from_num(u16::MAX) + * I64F64::from_num(vali_take)) + .to_num::(); + let remaining_emission = total_emission - hotkey_emission; + let nominator_emission = remaining_emission / 2; + + log::debug!( + "Calculated emissions - Hotkey: {}, Each Nominator: {}", + hotkey_emission, + nominator_emission + ); + + // Debug: Print the actual stakes + log::debug!("Actual hotkey stake: {}", hotkey_stake); + log::debug!("Actual nominator1 stake: {}", nominator1_stake); + log::debug!("Actual nominator2 stake: {}", nominator2_stake); + + // Debug: Check the total stake for the hotkey + let total_stake = SubtensorModule::get_total_stake_for_hotkey(&hotkey); + log::debug!("Total stake for hotkey: {}", total_stake); + + // Assertions + let expected_hotkey_stake = 4_000e9 as u64; + let eps = 0.5e9 as u64; + assert!( + hotkey_stake >= expected_hotkey_stake - eps + && hotkey_stake <= expected_hotkey_stake + eps, + "Hotkey stake mismatch - expected: {}, actual: {}", + expected_hotkey_stake, + hotkey_stake + ); + assert_eq!( + nominator1_stake, + initial_stake + nominator_emission, + "Nominator1 stake mismatch" + ); + assert_eq!( + nominator2_stake, + initial_stake + nominator_emission, + "Nominator2 stake mismatch" + ); + + // 10. Check total stake + assert_eq!( + total_stake, + initial_stake + initial_stake + total_emission, + "Total stake mismatch" + ); + + log::debug!("Test completed"); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --test coinbase test_coinbase_nominator_drainage_no_deltas -- --nocapture +#[test] +fn test_coinbase_nominator_drainage_no_deltas() { + new_test_ext(1).execute_with(|| { + // 1. Set up the network and accounts + let netuid: u16 = 1; + let hotkey = U256::from(0); + let coldkey = U256::from(3); + let nominator1 = U256::from(1); + let nominator2 = U256::from(2); + + log::debug!("Setting up network with netuid: {}", netuid); + log::debug!("Hotkey: {:?}, Coldkey: {:?}", hotkey, coldkey); + log::debug!("Nominators: {:?}, {:?}", nominator1, nominator2); + + // 2. Create network and register neuron + add_network(netuid, 1, 0); + register_ok_neuron(netuid, hotkey, coldkey, 100000); + SubtensorModule::create_account_if_non_existent(&coldkey, &hotkey); + + log::debug!("Network created and neuron registered"); + + // 3. Set up balances and stakes + SubtensorModule::add_balance_to_coldkey_account(&coldkey, 1000); + SubtensorModule::add_balance_to_coldkey_account(&nominator1, 1500); + SubtensorModule::add_balance_to_coldkey_account(&nominator2, 1500); + + log::debug!("Balances added to accounts"); + + // 4. Make the hotkey a delegate + let val_take = (u16::MAX as u64 / 10); + assert_ok!(SubtensorModule::do_become_delegate( + RuntimeOrigin::signed(coldkey), + hotkey, + val_take as u16 + )); + + log::debug!("Hotkey became a delegate with minimum take"); + + // Add stakes for nominators + // Add the stake directly to their coldkey-hotkey account + // This bypasses the accounting in stake delta + SubtensorModule::increase_stake_on_coldkey_hotkey_account(&nominator1, &hotkey, 100); + SubtensorModule::increase_stake_on_coldkey_hotkey_account(&nominator2, &hotkey, 100); + + // Log the stakes for hotkey, nominator1, and nominator2 + log::debug!( + "Initial stakes - Hotkey: {}, Nominator1: {}, Nominator2: {}", + SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey, &hotkey), + SubtensorModule::get_stake_for_coldkey_and_hotkey(&nominator1, &hotkey), + SubtensorModule::get_stake_for_coldkey_and_hotkey(&nominator2, &hotkey) + ); + log::debug!("Stakes added for nominators"); + + // 5. Set emission and verify initial states + SubtensorModule::set_emission_values(&[netuid], vec![10]).unwrap(); + assert_eq!(SubtensorModule::get_subnet_emission_value(netuid), 10); + assert_eq!(SubtensorModule::get_pending_hotkey_emission(&hotkey), 0); + assert_eq!(SubtensorModule::get_total_stake_for_hotkey(&hotkey), 200); + assert_eq!(SubtensorModule::get_pending_emission(netuid), 0); + + log::debug!("Emission set and initial states verified"); + + // 6. Set hotkey emission tempo + SubtensorModule::set_hotkey_emission_tempo(1); + log::debug!("Hotkey emission tempo set to 1"); + + // 7. Simulate blocks and check emissions + next_block(); + assert_eq!(SubtensorModule::get_pending_emission(netuid), 10); + log::debug!( + "After first block, pending emission: {}", + SubtensorModule::get_pending_emission(netuid) + ); + + next_block(); + assert_eq!(SubtensorModule::get_pending_emission(netuid), 0); + assert_eq!(SubtensorModule::get_pending_hotkey_emission(&hotkey), 0); + log::debug!("After second block, pending emission drained"); + + // 8. Check final stakes + let hotkey_stake = SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey, &hotkey); + let nominator1_stake = + SubtensorModule::get_stake_for_coldkey_and_hotkey(&nominator1, &hotkey); + let nominator2_stake = + SubtensorModule::get_stake_for_coldkey_and_hotkey(&nominator2, &hotkey); + + log::debug!( + "Final stakes - Hotkey: {}, Nominator1: {}, Nominator2: {}", + hotkey_stake, + nominator1_stake, + nominator2_stake + ); + + // 9. Verify distribution + let min_take = val_take; + let total_emission = 20; // 10 per block for 2 blocks + let hotkey_emission = total_emission * min_take / u16::MAX as u64; + let remaining_emission = total_emission - hotkey_emission; + let nominator_emission = remaining_emission / 2; + + log::debug!( + "Calculated emissions - Hotkey: {}, Each Nominator: {}", + hotkey_emission, + nominator_emission + ); + + // Debug: Print the actual stakes + log::debug!("Actual hotkey stake: {}", hotkey_stake); + log::debug!("Actual nominator1 stake: {}", nominator1_stake); + log::debug!("Actual nominator2 stake: {}", nominator2_stake); + + // Debug: Check the total stake for the hotkey + let total_stake = SubtensorModule::get_total_stake_for_hotkey(&hotkey); + log::debug!("Total stake for hotkey: {}", total_stake); + + // Assertions + assert_eq!(hotkey_stake, 2, "Hotkey stake mismatch"); + assert_eq!( + nominator1_stake, + 100 + nominator_emission, + "Nominator1 stake mismatch" + ); + assert_eq!( + nominator2_stake, + 100 + nominator_emission, + "Nominator2 stake mismatch" + ); + + // 10. Check total stake + assert_eq!(total_stake, 200 + total_emission, "Total stake mismatch"); + + log::debug!("Test completed"); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --test coinbase test_coinbase_nominator_drainage_with_positive_delta -- --nocapture +#[test] +fn test_coinbase_nominator_drainage_with_positive_delta() { + new_test_ext(1).execute_with(|| { + // 1. Set up the network and accounts + let netuid: u16 = 1; + let hotkey = U256::from(0); + let coldkey = U256::from(3); + let nominator1 = U256::from(1); + let nominator2 = U256::from(2); + + log::debug!("Setting up network with netuid: {}", netuid); + log::debug!("Hotkey: {:?}, Coldkey: {:?}", hotkey, coldkey); + log::debug!("Nominators: {:?}, {:?}", nominator1, nominator2); + + // 2. Create network and register neuron + add_network(netuid, 1, 0); + register_ok_neuron(netuid, hotkey, coldkey, 100000); + SubtensorModule::create_account_if_non_existent(&coldkey, &hotkey); + + log::debug!("Network created and neuron registered"); + + // 3. Set up balances and stakes + SubtensorModule::add_balance_to_coldkey_account(&coldkey, 1000); + SubtensorModule::add_balance_to_coldkey_account(&nominator1, 1500); + SubtensorModule::add_balance_to_coldkey_account(&nominator2, 1500); + + log::debug!("Balances added to accounts"); + + // 4. Make the hotkey a delegate + let val_take = (u16::MAX as u64 / 10); + assert_ok!(SubtensorModule::do_become_delegate( + RuntimeOrigin::signed(coldkey), + hotkey, + val_take as u16 + )); + + log::debug!("Hotkey became a delegate with minimum take"); + + // Add stakes for nominators + // Add the stake directly to their coldkey-hotkey account + // This bypasses the accounting in stake delta + SubtensorModule::increase_stake_on_coldkey_hotkey_account(&nominator1, &hotkey, 100); + SubtensorModule::increase_stake_on_coldkey_hotkey_account(&nominator2, &hotkey, 100); + + // Do an add_stake for nominator 1 + assert_ok!(SubtensorModule::do_add_stake( + RuntimeOrigin::signed(nominator1), + hotkey, + 123 + )); // We should not expect this to impact the emissions + + // Log the stakes for hotkey, nominator1, and nominator2 + log::debug!( + "Initial stakes - Hotkey: {}, Nominator1: {}, Nominator2: {}", + SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey, &hotkey), + SubtensorModule::get_stake_for_coldkey_and_hotkey(&nominator1, &hotkey), + SubtensorModule::get_stake_for_coldkey_and_hotkey(&nominator2, &hotkey) + ); + log::debug!("Stakes added for nominators"); + + let nominator1_stake_before = + SubtensorModule::get_stake_for_coldkey_and_hotkey(&nominator1, &hotkey); + assert_eq!(nominator1_stake_before, 100 + 123); // The stake should include the added stake + + // 5. Set emission and verify initial states + SubtensorModule::set_emission_values(&[netuid], vec![10]).unwrap(); + assert_eq!(SubtensorModule::get_subnet_emission_value(netuid), 10); + assert_eq!(SubtensorModule::get_pending_hotkey_emission(&hotkey), 0); + assert_eq!( + SubtensorModule::get_total_stake_for_hotkey(&hotkey), + 200 + 123 + ); + assert_eq!(SubtensorModule::get_pending_emission(netuid), 0); + + log::debug!("Emission set and initial states verified"); + + // 6. Set hotkey emission tempo + SubtensorModule::set_hotkey_emission_tempo(1); + log::debug!("Hotkey emission tempo set to 1"); + + // 7. Simulate blocks and check emissions + next_block(); + assert_eq!(SubtensorModule::get_pending_emission(netuid), 10); + log::debug!( + "After first block, pending emission: {}", + SubtensorModule::get_pending_emission(netuid) + ); + + next_block(); + assert_eq!(SubtensorModule::get_pending_emission(netuid), 0); + assert_eq!(SubtensorModule::get_pending_hotkey_emission(&hotkey), 0); + log::debug!("After second block, pending emission drained"); + + // 8. Check final stakes + let hotkey_stake = SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey, &hotkey); + let nominator1_stake = + SubtensorModule::get_stake_for_coldkey_and_hotkey(&nominator1, &hotkey); + let nominator2_stake = + SubtensorModule::get_stake_for_coldkey_and_hotkey(&nominator2, &hotkey); + + log::debug!( + "Final stakes - Hotkey: {}, Nominator1: {}, Nominator2: {}", + hotkey_stake, + nominator1_stake, + nominator2_stake + ); + + // 9. Verify distribution + let min_take = val_take; + let total_emission = 20; // 10 per block for 2 blocks + let hotkey_emission = total_emission * min_take / u16::MAX as u64; + let remaining_emission = total_emission - hotkey_emission; + let nominator_emission = remaining_emission / 2; + // Notice that nominator emission is equal for both nominators, even though nominator1 added stake + + log::debug!( + "Calculated emissions - Hotkey: {}, Each Nominator: {}", + hotkey_emission, + nominator_emission + ); + + // Debug: Print the actual stakes + log::debug!("Actual hotkey stake: {}", hotkey_stake); + log::debug!("Actual nominator1 stake: {}", nominator1_stake); + log::debug!("Actual nominator2 stake: {}", nominator2_stake); + + // Debug: Check the total stake for the hotkey + let total_stake = SubtensorModule::get_total_stake_for_hotkey(&hotkey); + log::debug!("Total stake for hotkey: {}", total_stake); + + // Assertions + assert_eq!(hotkey_stake, 2, "Hotkey stake mismatch"); + assert_eq!( + nominator1_stake, + 100 + 123 + nominator_emission, + "Nominator1 stake mismatch" + ); + assert_eq!( + nominator2_stake, + 100 + nominator_emission, + "Nominator2 stake mismatch" + ); + + // 10. Check total stake + // Includes the added stake from nominator1 + assert_eq!( + total_stake, + 200 + 123 + total_emission, + "Total stake mismatch" + ); + + log::debug!("Test completed"); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --test coinbase test_coinbase_nominator_drainage_with_negative_delta -- --nocapture +#[test] +fn test_coinbase_nominator_drainage_with_negative_delta() { + new_test_ext(1).execute_with(|| { + // 1. Set up the network and accounts + let netuid: u16 = 1; + let hotkey = U256::from(0); + let coldkey = U256::from(3); + let nominator1 = U256::from(1); + let nominator2 = U256::from(2); + + log::debug!("Setting up network with netuid: {}", netuid); + log::debug!("Hotkey: {:?}, Coldkey: {:?}", hotkey, coldkey); + log::debug!("Nominators: {:?}, {:?}", nominator1, nominator2); + + // 2. Create network and register neuron + add_network(netuid, 1, 0); + register_ok_neuron(netuid, hotkey, coldkey, 100000); + SubtensorModule::create_account_if_non_existent(&coldkey, &hotkey); + + log::debug!("Network created and neuron registered"); + + // 3. Set up balances and stakes + SubtensorModule::add_balance_to_coldkey_account(&coldkey, 1000); + SubtensorModule::add_balance_to_coldkey_account(&nominator1, 1500); + SubtensorModule::add_balance_to_coldkey_account(&nominator2, 1500); + + log::debug!("Balances added to accounts"); + + // 4. Make the hotkey a delegate + let val_take = (u16::MAX as u64 / 10); + assert_ok!(SubtensorModule::do_become_delegate( + RuntimeOrigin::signed(coldkey), + hotkey, + val_take as u16 + )); + + log::debug!("Hotkey became a delegate with minimum take"); + + // Add stakes for nominators + // Add the stake directly to their coldkey-hotkey account + // This bypasses the accounting in stake delta + SubtensorModule::increase_stake_on_coldkey_hotkey_account(&nominator1, &hotkey, 100); + SubtensorModule::increase_stake_on_coldkey_hotkey_account(&nominator2, &hotkey, 100); + + // Do an remove_stake for nominator 1 + assert_ok!(SubtensorModule::remove_stake( + RuntimeOrigin::signed(nominator1), + hotkey, + 12 + )); // We should expect the emissions to be impacted; + // The viable stake should be the *new* stake for nominator 1 + + // Log the stakes for hotkey, nominator1, and nominator2 + log::debug!( + "Initial stakes - Hotkey: {}, Nominator1: {}, Nominator2: {}", + SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey, &hotkey), + SubtensorModule::get_stake_for_coldkey_and_hotkey(&nominator1, &hotkey), + SubtensorModule::get_stake_for_coldkey_and_hotkey(&nominator2, &hotkey) + ); + log::debug!("Stakes added for nominators"); + + let nominator_1_stake_before = + SubtensorModule::get_stake_for_coldkey_and_hotkey(&nominator1, &hotkey); + // Notice that nominator1 stake is the new stake, including the removed stake + assert_eq!(nominator_1_stake_before, 100 - 12); + + // 5. Set emission and verify initial states + SubtensorModule::set_emission_values(&[netuid], vec![10]).unwrap(); + assert_eq!(SubtensorModule::get_subnet_emission_value(netuid), 10); + assert_eq!(SubtensorModule::get_pending_hotkey_emission(&hotkey), 0); + assert_eq!( + SubtensorModule::get_total_stake_for_hotkey(&hotkey), + 200 - 12 + ); + assert_eq!(SubtensorModule::get_pending_emission(netuid), 0); + + log::debug!("Emission set and initial states verified"); + + // 6. Set hotkey emission tempo + SubtensorModule::set_hotkey_emission_tempo(1); + log::debug!("Hotkey emission tempo set to 1"); + + // 7. Simulate blocks and check emissions + next_block(); + assert_eq!(SubtensorModule::get_pending_emission(netuid), 10); + log::debug!( + "After first block, pending emission: {}", + SubtensorModule::get_pending_emission(netuid) + ); + + next_block(); + assert_eq!(SubtensorModule::get_pending_emission(netuid), 0); + assert_eq!(SubtensorModule::get_pending_hotkey_emission(&hotkey), 0); + log::debug!("After second block, pending emission drained"); + + // 8. Check final stakes + let delegate_stake = SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey, &hotkey); + let total_hotkey_stake = SubtensorModule::get_total_stake_for_hotkey(&hotkey); + let nominator1_stake = + SubtensorModule::get_stake_for_coldkey_and_hotkey(&nominator1, &hotkey); + let nominator2_stake = + SubtensorModule::get_stake_for_coldkey_and_hotkey(&nominator2, &hotkey); + + log::debug!( + "Final stakes - Hotkey: {}, Nominator1: {}, Nominator2: {}, Total Hotkey Stake: {}", + delegate_stake, + nominator1_stake, + nominator2_stake, + total_hotkey_stake + ); + + // 9. Verify distribution + let min_take = val_take; + let total_emission = 20; // 10 per block for 2 blocks + let hotkey_emission = total_emission * min_take / u16::MAX as u64; + let remaining_emission = total_emission - hotkey_emission; + + let nominator_1_emission = remaining_emission * nominator1_stake / total_hotkey_stake; + let nominator_2_emission = remaining_emission * nominator2_stake / total_hotkey_stake; + + log::debug!( + "Calculated emissions - Hotkey: {}, Each Nominator: 1;{}, 2;{}", + hotkey_emission, + nominator_1_emission, + nominator_2_emission + ); + + // Debug: Print the actual stakes + log::debug!("Actual hotkey stake: {}", delegate_stake); + log::debug!("Actual nominator1 stake: {}", nominator1_stake); + log::debug!("Actual nominator2 stake: {}", nominator2_stake); + + // Debug: Check the total stake for the hotkey + let total_stake = SubtensorModule::get_total_stake_for_hotkey(&hotkey); + log::debug!("Total stake for hotkey: {}", total_stake); + + // Assertions + assert_eq!(delegate_stake, 2, "Hotkey stake mismatch"); + assert_eq!( + nominator1_stake, + 100 - 12 + nominator_1_emission, + "Nominator1 stake mismatch" + ); + assert_eq!( + nominator2_stake, + 100 + nominator_2_emission, + "Nominator2 stake mismatch" + ); + + // 10. Check total stake + assert_eq!( + total_stake, + 200 - 12 + total_emission, + "Total stake mismatch" + ); + + log::debug!("Test completed"); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --test coinbase test_coinbase_nominator_drainage_with_neutral_delta -- --nocapture +#[test] +fn test_coinbase_nominator_drainage_with_neutral_delta() { + new_test_ext(1).execute_with(|| { + // 1. Set up the network and accounts + let netuid: u16 = 1; + let hotkey = U256::from(0); + let coldkey = U256::from(3); + let nominator1 = U256::from(1); + let nominator2 = U256::from(2); + + log::debug!("Setting up network with netuid: {}", netuid); + log::debug!("Hotkey: {:?}, Coldkey: {:?}", hotkey, coldkey); + log::debug!("Nominators: {:?}, {:?}", nominator1, nominator2); + + // 2. Create network and register neuron + add_network(netuid, 1, 0); + register_ok_neuron(netuid, hotkey, coldkey, 100000); + SubtensorModule::create_account_if_non_existent(&coldkey, &hotkey); + + log::debug!("Network created and neuron registered"); + + // 3. Set up balances and stakes + SubtensorModule::add_balance_to_coldkey_account(&coldkey, 1000); + SubtensorModule::add_balance_to_coldkey_account(&nominator1, 1500); + SubtensorModule::add_balance_to_coldkey_account(&nominator2, 1500); + + log::debug!("Balances added to accounts"); + + // 4. Make the hotkey a delegate + let val_take = (u16::MAX as u64 / 10); + assert_ok!(SubtensorModule::do_become_delegate( + RuntimeOrigin::signed(coldkey), + hotkey, + val_take as u16 + )); + + log::debug!("Hotkey became a delegate with minimum take"); + + // Add stakes for nominators + // Add the stake directly to their coldkey-hotkey account + // This bypasses the accounting in stake delta + SubtensorModule::increase_stake_on_coldkey_hotkey_account(&nominator1, &hotkey, 100); + SubtensorModule::increase_stake_on_coldkey_hotkey_account(&nominator2, &hotkey, 100); + + // Do an remove_stake for nominator 1 + assert_ok!(SubtensorModule::remove_stake( + RuntimeOrigin::signed(nominator1), + hotkey, + 12 + )); + // Do an add_stake for nominator 1 of the same amount + assert_ok!(SubtensorModule::add_stake( + RuntimeOrigin::signed(nominator1), + hotkey, + 12 + )); // The viable stake should match the initial stake, because the delta is 0 + + // Log the stakes for hotkey, nominator1, and nominator2 + log::debug!( + "Initial stakes - Hotkey: {}, Nominator1: {}, Nominator2: {}", + SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey, &hotkey), + SubtensorModule::get_stake_for_coldkey_and_hotkey(&nominator1, &hotkey), + SubtensorModule::get_stake_for_coldkey_and_hotkey(&nominator2, &hotkey) + ); + log::debug!("Stakes added for nominators"); + + let nominator1_stake_before = + SubtensorModule::get_stake_for_coldkey_and_hotkey(&nominator1, &hotkey); + // Notice that nominator1 stake is the unchanged from the initial stake + assert_eq!(nominator1_stake_before, 100); + + // 5. Set emission and verify initial states + SubtensorModule::set_emission_values(&[netuid], vec![10]).unwrap(); + assert_eq!(SubtensorModule::get_subnet_emission_value(netuid), 10); + assert_eq!(SubtensorModule::get_pending_hotkey_emission(&hotkey), 0); + assert_eq!(SubtensorModule::get_total_stake_for_hotkey(&hotkey), 200); + assert_eq!(SubtensorModule::get_pending_emission(netuid), 0); + + log::debug!("Emission set and initial states verified"); + + // 6. Set hotkey emission tempo + SubtensorModule::set_hotkey_emission_tempo(1); + log::debug!("Hotkey emission tempo set to 1"); + + // 7. Simulate blocks and check emissions + next_block(); + assert_eq!(SubtensorModule::get_pending_emission(netuid), 10); + log::debug!( + "After first block, pending emission: {}", + SubtensorModule::get_pending_emission(netuid) + ); + + next_block(); + assert_eq!(SubtensorModule::get_pending_emission(netuid), 0); + assert_eq!(SubtensorModule::get_pending_hotkey_emission(&hotkey), 0); + log::debug!("After second block, pending emission drained"); + + // 8. Check final stakes + let delegate_stake = SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey, &hotkey); + let total_hotkey_stake = SubtensorModule::get_total_stake_for_hotkey(&hotkey); + let nominator1_stake = + SubtensorModule::get_stake_for_coldkey_and_hotkey(&nominator1, &hotkey); + let nominator2_stake = + SubtensorModule::get_stake_for_coldkey_and_hotkey(&nominator2, &hotkey); + + log::debug!( + "Final stakes - Hotkey: {}, Nominator1: {}, Nominator2: {}, Total Hotkey Stake: {}", + delegate_stake, + nominator1_stake, + nominator2_stake, + total_hotkey_stake + ); + + // 9. Verify distribution + let min_take = val_take; + let total_emission = 20; // 10 per block for 2 blocks + let hotkey_emission = total_emission * min_take / u16::MAX as u64; + let remaining_emission = total_emission - hotkey_emission; + + let nominator_1_emission = remaining_emission * nominator1_stake / total_hotkey_stake; + let nominator_2_emission = remaining_emission * nominator2_stake / total_hotkey_stake; + + log::debug!( + "Calculated emissions - Hotkey: {}, Each Nominator: 1;{}, 2;{}", + hotkey_emission, + nominator_1_emission, + nominator_2_emission + ); + + // Debug: Print the actual stakes + log::debug!("Actual hotkey stake: {}", delegate_stake); + log::debug!("Actual nominator1 stake: {}", nominator1_stake); + log::debug!("Actual nominator2 stake: {}", nominator2_stake); + + // Debug: Check the total stake for the hotkey + let total_stake = SubtensorModule::get_total_stake_for_hotkey(&hotkey); + log::debug!("Total stake for hotkey: {}", total_stake); + + // Assertions + assert_eq!(delegate_stake, 2, "Hotkey stake mismatch"); + assert_eq!( + nominator1_stake, + 100 + nominator_1_emission, // We expect the emission to be calculated based on the initial stake + // Because the delta is 0. + "Nominator1 stake mismatch" + ); + assert_eq!( + nominator2_stake, + 100 + nominator_2_emission, + "Nominator2 stake mismatch" + ); + + // 10. Check total stake + assert_eq!(total_stake, 200 + total_emission, "Total stake mismatch"); + + log::debug!("Test completed"); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --test coinbase test_coinbase_nominator_drainage_with_net_positive_delta -- --nocapture +#[test] +fn test_coinbase_nominator_drainage_with_net_positive_delta() { + new_test_ext(1).execute_with(|| { + // 1. Set up the network and accounts + let netuid: u16 = 1; + let hotkey = U256::from(0); + let coldkey = U256::from(3); + let nominator1 = U256::from(1); + let nominator2 = U256::from(2); + + log::debug!("Setting up network with netuid: {}", netuid); + log::debug!("Hotkey: {:?}, Coldkey: {:?}", hotkey, coldkey); + log::debug!("Nominators: {:?}, {:?}", nominator1, nominator2); + + // 2. Create network and register neuron + add_network(netuid, 1, 0); + register_ok_neuron(netuid, hotkey, coldkey, 100000); + SubtensorModule::create_account_if_non_existent(&coldkey, &hotkey); + + log::debug!("Network created and neuron registered"); + + // 3. Set up balances and stakes + SubtensorModule::add_balance_to_coldkey_account(&coldkey, 1000); + SubtensorModule::add_balance_to_coldkey_account(&nominator1, 1500); + SubtensorModule::add_balance_to_coldkey_account(&nominator2, 1500); + + log::debug!("Balances added to accounts"); + + // 4. Make the hotkey a delegate + let val_take = (u16::MAX as u64 / 10); + assert_ok!(SubtensorModule::do_become_delegate( + RuntimeOrigin::signed(coldkey), + hotkey, + val_take as u16 + )); + + log::debug!("Hotkey became a delegate with minimum take"); + + // Add stakes for nominators + // Add the stake directly to their coldkey-hotkey account + // This bypasses the accounting in stake delta + SubtensorModule::increase_stake_on_coldkey_hotkey_account(&nominator1, &hotkey, 100); + SubtensorModule::increase_stake_on_coldkey_hotkey_account(&nominator2, &hotkey, 100); + + let initial_nominator1_stake = + SubtensorModule::get_stake_for_coldkey_and_hotkey(&nominator1, &hotkey); + let intial_total_hotkey_stake = SubtensorModule::get_total_stake_for_hotkey(&hotkey); + let initial_nominator2_stake = + SubtensorModule::get_stake_for_coldkey_and_hotkey(&nominator2, &hotkey); + + assert_eq!(initial_nominator1_stake, initial_nominator2_stake); // Initial stakes should be equal + + let removed_stake = 12; + // Do an add_stake for nominator 1 of MORE than was removed + let added_stake = removed_stake + 1; + let net_change: i128 = i128::from(added_stake) - i128::from(removed_stake); // Positive net change + + // Do an remove_stake for nominator 1 + assert_ok!(SubtensorModule::remove_stake( + RuntimeOrigin::signed(nominator1), + hotkey, + removed_stake + )); + + // Do an add_stake for nominator 1 of MORE than was removed + assert_ok!(SubtensorModule::add_stake( + RuntimeOrigin::signed(nominator1), + hotkey, + added_stake + )); // We should expect the emissions to be impacted; + // The viable stake should be the same initial stake for nominator 1 + // NOT the new stake amount, because the delta is net positive + + // Log the stakes for hotkey, nominator1, and nominator2 + log::debug!( + "Initial stakes - Hotkey: {}, Nominator1: {}, Nominator2: {}", + SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey, &hotkey), + SubtensorModule::get_stake_for_coldkey_and_hotkey(&nominator1, &hotkey), + SubtensorModule::get_stake_for_coldkey_and_hotkey(&nominator2, &hotkey) + ); + log::debug!("Stakes added for nominators"); + + let nominator_1_stake_before = + SubtensorModule::get_stake_for_coldkey_and_hotkey(&nominator1, &hotkey); + // Notice that nominator1 stake is the new stake, including the removed stake + assert_eq!( + nominator_1_stake_before, + u64::try_from(100 + net_change).unwrap() + ); + + // 5. Set emission and verify initial states + SubtensorModule::set_emission_values(&[netuid], vec![10]).unwrap(); + assert_eq!(SubtensorModule::get_subnet_emission_value(netuid), 10); + assert_eq!(SubtensorModule::get_pending_hotkey_emission(&hotkey), 0); + assert_eq!( + SubtensorModule::get_total_stake_for_hotkey(&hotkey), + u64::try_from(200 + net_change).unwrap() + ); + assert_eq!(SubtensorModule::get_pending_emission(netuid), 0); + + log::debug!("Emission set and initial states verified"); + + // 6. Set hotkey emission tempo + SubtensorModule::set_hotkey_emission_tempo(1); + log::debug!("Hotkey emission tempo set to 1"); + + // 7. Simulate blocks and check emissions + next_block(); + assert_eq!(SubtensorModule::get_pending_emission(netuid), 10); + log::debug!( + "After first block, pending emission: {}", + SubtensorModule::get_pending_emission(netuid) + ); + + next_block(); + assert_eq!(SubtensorModule::get_pending_emission(netuid), 0); + assert_eq!(SubtensorModule::get_pending_hotkey_emission(&hotkey), 0); + log::debug!("After second block, pending emission drained"); + + // 8. Check final stakes + let delegate_stake = SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey, &hotkey); + let total_hotkey_stake = SubtensorModule::get_total_stake_for_hotkey(&hotkey); + let nominator1_stake = + SubtensorModule::get_stake_for_coldkey_and_hotkey(&nominator1, &hotkey); + let nominator2_stake = + SubtensorModule::get_stake_for_coldkey_and_hotkey(&nominator2, &hotkey); + + log::debug!( + "Final stakes - Hotkey: {}, Nominator1: {}, Nominator2: {}, Total Hotkey Stake: {}", + delegate_stake, + nominator1_stake, + nominator2_stake, + total_hotkey_stake + ); + + // 9. Verify distribution + let min_take = val_take; + let total_emission = 20; // 10 per block for 2 blocks + let hotkey_emission = total_emission * min_take / u16::MAX as u64; + let remaining_emission = total_emission - hotkey_emission; + + // We expect to distribute using the initial stake for nominator 1; because the delta is net positive + // We also use the INITIAL total hotkey stake + let nominator_1_emission = + remaining_emission * initial_nominator1_stake / intial_total_hotkey_stake; + let nominator_2_emission = + remaining_emission * initial_nominator2_stake / intial_total_hotkey_stake; + + log::debug!( + "Calculated emissions - Hotkey: {}, Each Nominator: 1;{}, 2;{}", + hotkey_emission, + nominator_1_emission, + nominator_2_emission + ); + + // Debug: Print the actual stakes + log::debug!("Actual hotkey stake: {}", delegate_stake); + log::debug!("Actual nominator1 stake: {}", nominator1_stake); + log::debug!("Actual nominator2 stake: {}", nominator2_stake); + + // Debug: Check the total stake for the hotkey + let total_stake = SubtensorModule::get_total_stake_for_hotkey(&hotkey); + log::debug!("Total stake for hotkey: {}", total_stake); + + // Assertions + assert_eq!(delegate_stake, 2, "Hotkey stake mismatch"); + assert_eq!( + nominator1_stake, + u64::try_from( + net_change + .checked_add_unsigned(100 + nominator_1_emission as u128) + .unwrap() + ) + .unwrap(), + "Nominator1 stake mismatch" + ); + assert_eq!( + nominator2_stake, + initial_nominator2_stake + nominator_2_emission, + "Nominator2 stake mismatch" + ); + + // 10. Check total stake + assert_eq!( + total_stake, + u64::try_from( + net_change + .checked_add_unsigned(200 + total_emission as u128) + .unwrap() + ) + .unwrap(), + "Total stake mismatch" + ); + + log::debug!("Test completed"); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --test coinbase test_coinbase_nominator_drainage_with_net_negative_delta -- --nocapture +#[test] +fn test_coinbase_nominator_drainage_with_net_negative_delta() { + new_test_ext(1).execute_with(|| { + // 1. Set up the network and accounts + let netuid: u16 = 1; + let hotkey = U256::from(0); + let coldkey = U256::from(3); + let nominator1 = U256::from(1); + let nominator2 = U256::from(2); + + log::debug!("Setting up network with netuid: {}", netuid); + log::debug!("Hotkey: {:?}, Coldkey: {:?}", hotkey, coldkey); + log::debug!("Nominators: {:?}, {:?}", nominator1, nominator2); + + // 2. Create network and register neuron + add_network(netuid, 1, 0); + register_ok_neuron(netuid, hotkey, coldkey, 100000); + SubtensorModule::create_account_if_non_existent(&coldkey, &hotkey); + + log::debug!("Network created and neuron registered"); + + // 3. Set up balances and stakes + SubtensorModule::add_balance_to_coldkey_account(&coldkey, 1000); + SubtensorModule::add_balance_to_coldkey_account(&nominator1, 1500); + SubtensorModule::add_balance_to_coldkey_account(&nominator2, 1500); + + log::debug!("Balances added to accounts"); + + // 4. Make the hotkey a delegate + let val_take = (u16::MAX as u64 / 10); + assert_ok!(SubtensorModule::do_become_delegate( + RuntimeOrigin::signed(coldkey), + hotkey, + val_take as u16 + )); + + log::debug!("Hotkey became a delegate with minimum take"); + + // Add stakes for nominators + // Add the stake directly to their coldkey-hotkey account + // This bypasses the accounting in stake delta + SubtensorModule::increase_stake_on_coldkey_hotkey_account(&nominator1, &hotkey, 300); + SubtensorModule::increase_stake_on_coldkey_hotkey_account(&nominator2, &hotkey, 300); + + let initial_nominator1_stake = + SubtensorModule::get_stake_for_coldkey_and_hotkey(&nominator1, &hotkey); + let intial_total_hotkey_stake = SubtensorModule::get_total_stake_for_hotkey(&hotkey); + let initial_nominator2_stake = + SubtensorModule::get_stake_for_coldkey_and_hotkey(&nominator2, &hotkey); + + assert_eq!(initial_nominator1_stake, initial_nominator2_stake); // Initial stakes should be equal + + let removed_stake = 220; + // Do an add_stake for nominator 1 of LESS than was removed + let added_stake = removed_stake - 188; + let net_change: i128 = i128::from(added_stake) - i128::from(removed_stake); // Negative net change + assert!(net_change < 0); + + // Do an remove_stake for nominator 1 + assert_ok!(SubtensorModule::remove_stake( + RuntimeOrigin::signed(nominator1), + hotkey, + removed_stake + )); + + // Do an add_stake for nominator 1 of MORE than was removed + assert_ok!(SubtensorModule::add_stake( + RuntimeOrigin::signed(nominator1), + hotkey, + added_stake + )); // We should expect the emissions to be impacted; + // The viable stake should be the LESS than the initial stake for nominator 1 + // Which IS the new stake amount, because the delta is net negative + + // Log the stakes for hotkey, nominator1, and nominator2 + log::debug!( + "Initial stakes - Hotkey: {}, Nominator1: {}, Nominator2: {}", + SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey, &hotkey), + SubtensorModule::get_stake_for_coldkey_and_hotkey(&nominator1, &hotkey), + SubtensorModule::get_stake_for_coldkey_and_hotkey(&nominator2, &hotkey) + ); + log::debug!("Stakes added for nominators"); + + let total_stake_before = SubtensorModule::get_total_stake_for_hotkey(&hotkey); + let nominator_1_stake_before = + SubtensorModule::get_stake_for_coldkey_and_hotkey(&nominator1, &hotkey); + // Notice that nominator1 stake is the new stake, including the removed stake + assert_eq!( + nominator_1_stake_before, + u64::try_from(300 + net_change).unwrap() + ); + + // 5. Set emission and verify initial states + let to_emit = 10_000e9 as u64; + SubtensorModule::set_emission_values(&[netuid], vec![to_emit]).unwrap(); + assert_eq!(SubtensorModule::get_subnet_emission_value(netuid), to_emit); + assert_eq!(SubtensorModule::get_pending_hotkey_emission(&hotkey), 0); + assert_eq!( + SubtensorModule::get_total_stake_for_hotkey(&hotkey), + u64::try_from(600 + net_change).unwrap() + ); + assert_eq!(SubtensorModule::get_pending_emission(netuid), 0); + + log::debug!("Emission set and initial states verified"); + + // 6. Set hotkey emission tempo + SubtensorModule::set_hotkey_emission_tempo(1); + log::debug!("Hotkey emission tempo set to 1"); + + // 7. Simulate blocks and check emissions + next_block(); + assert_eq!(SubtensorModule::get_pending_emission(netuid), to_emit); + log::debug!( + "After first block, pending emission: {}", + SubtensorModule::get_pending_emission(netuid) + ); + + next_block(); + assert_eq!(SubtensorModule::get_pending_emission(netuid), 0); + assert_eq!(SubtensorModule::get_pending_hotkey_emission(&hotkey), 0); + log::debug!("After second block, pending emission drained"); + + // 8. Check final stakes + let delegate_stake = SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey, &hotkey); + let total_hotkey_stake = SubtensorModule::get_total_stake_for_hotkey(&hotkey); + let nominator1_stake = + SubtensorModule::get_stake_for_coldkey_and_hotkey(&nominator1, &hotkey); + let nominator2_stake = + SubtensorModule::get_stake_for_coldkey_and_hotkey(&nominator2, &hotkey); + + log::debug!( + "Final stakes - Hotkey: {}, Nominator1: {}, Nominator2: {}, Total Hotkey Stake: {}", + delegate_stake, + nominator1_stake, + nominator2_stake, + total_hotkey_stake + ); + + // 9. Verify distribution + let min_take = val_take; + let total_emission = to_emit * 2; // 10 per block for 2 blocks + let hotkey_emission = total_emission * min_take / u16::MAX as u64; + let remaining_emission = total_emission - hotkey_emission; + + // We expect to distribute using the NEW stake for nominator 1; because the delta is net negative + // We also use the INITIAL total hotkey stake + // Note: nominator_1_stake_before is the new stake for nominator 1, before the epochs run + let nominator_1_emission = + remaining_emission * nominator_1_stake_before / total_stake_before; + let nominator_2_emission = + remaining_emission * initial_nominator2_stake / total_stake_before; + + log::debug!( + "Calculated emissions - Hotkey: {}, Each Nominator: 1;{}, 2;{}", + hotkey_emission, + nominator_1_emission, + nominator_2_emission + ); + + // Debug: Print the actual stakes + log::debug!("Actual hotkey stake: {}", delegate_stake); + log::debug!("Actual nominator1 stake: {}", nominator1_stake); + log::debug!("Actual nominator2 stake: {}", nominator2_stake); + + // Debug: Check the total stake for the hotkey + let total_stake = SubtensorModule::get_total_stake_for_hotkey(&hotkey); + log::debug!("Total stake for hotkey: {}", total_stake); + + // Do a fuzzy check on the final stakes + let eps = 0.2e9 as u64; + + let expected_delegate_stake: u64 = 2_000e9 as u64; + assert!( + expected_delegate_stake - eps <= delegate_stake + && expected_delegate_stake + eps >= delegate_stake, + "Hotkey stake mismatch - Expected: {}, Actual: {}", + expected_delegate_stake, + delegate_stake + ); + + let expected_1_stake = u64::try_from( + net_change + .checked_add_unsigned((initial_nominator1_stake + nominator_1_emission) as u128) + .unwrap(), + ) + .unwrap(); + assert!( + expected_1_stake - eps <= nominator1_stake + && expected_1_stake + eps >= nominator1_stake, + "Nominator1 stake mismatch - Expected: {}, Actual: {}", + expected_1_stake, + nominator1_stake + ); + let expected_2_stake = initial_nominator2_stake + nominator_2_emission; + assert!( + expected_2_stake - eps <= nominator2_stake + && expected_2_stake + eps >= nominator2_stake, + "Nominator2 stake mismatch - Expected: {}, Actual: {}", + expected_2_stake, + nominator2_stake + ); + + // 10. Check total stake + assert_eq!( + total_stake, + u64::try_from( + net_change + .checked_add_unsigned( + (initial_nominator2_stake + initial_nominator1_stake + total_emission) + as u128 + ) + .unwrap() + ) + .unwrap(), + "Total stake mismatch" + ); + + log::debug!("Test completed"); + }); +} diff --git a/pallets/subtensor/tests/staking.rs b/pallets/subtensor/tests/staking.rs index f053c7ca6..a55db996b 100644 --- a/pallets/subtensor/tests/staking.rs +++ b/pallets/subtensor/tests/staking.rs @@ -2306,3 +2306,106 @@ fn test_get_total_delegated_stake_exclude_owner_stake() { ); }); } + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test staking -- test_stake_delta_tracks_adds_and_removes --exact --nocapture +#[test] +fn test_stake_delta_tracks_adds_and_removes() { + new_test_ext(1).execute_with(|| { + let netuid = 1u16; + let delegate_coldkey = U256::from(1); + let delegate_hotkey = U256::from(2); + let delegator = U256::from(3); + + let owner_stake = 1000; + let owner_added_stake = 123; + let owner_removed_stake = 456; + // Add more than removed to test that the delta is updated correctly + let owner_adds_more_stake = owner_removed_stake + 1; + + let delegator_added_stake = 999; + + // Set stake rate limit very high + TargetStakesPerInterval::::put(1e9 as u64); + + add_network(netuid, 0, 0); + register_ok_neuron(netuid, delegate_hotkey, delegate_coldkey, 0); + // Give extra stake to the owner + SubtensorModule::increase_stake_on_coldkey_hotkey_account( + &delegate_coldkey, + &delegate_hotkey, + owner_stake, + ); + + // Register as a delegate + assert_ok!(SubtensorModule::become_delegate( + RuntimeOrigin::signed(delegate_coldkey), + delegate_hotkey + )); + + // Verify that the stake delta is empty + assert_eq!( + StakeDeltaSinceLastEmissionDrain::::get(delegate_hotkey, delegate_coldkey), + 0 + ); + + // Give the coldkey some balance; extra just in case + SubtensorModule::add_balance_to_coldkey_account( + &delegate_coldkey, + owner_added_stake + owner_adds_more_stake, + ); + + // Add some stake + assert_ok!(SubtensorModule::add_stake( + RuntimeOrigin::signed(delegate_coldkey), + delegate_hotkey, + owner_added_stake + )); + + // Verify that the stake delta is correct + assert_eq!( + StakeDeltaSinceLastEmissionDrain::::get(delegate_hotkey, delegate_coldkey), + i128::from(owner_added_stake) + ); + + // Add some stake from a delegator + SubtensorModule::add_balance_to_coldkey_account(&delegator, delegator_added_stake); + assert_ok!(SubtensorModule::add_stake( + RuntimeOrigin::signed(delegator), + delegate_hotkey, + delegator_added_stake + )); + + // Verify that the stake delta is unchanged for the owner + assert_eq!( + StakeDeltaSinceLastEmissionDrain::::get(delegate_hotkey, delegate_coldkey), + i128::from(owner_added_stake) + ); + + // Remove some stake + assert_ok!(SubtensorModule::remove_stake( + RuntimeOrigin::signed(delegate_coldkey), + delegate_hotkey, + owner_removed_stake + )); + + // Verify that the stake delta is correct + assert_eq!( + StakeDeltaSinceLastEmissionDrain::::get(delegate_hotkey, delegate_coldkey), + i128::from(owner_added_stake).saturating_sub_unsigned(owner_removed_stake.into()) + ); + + // Add more stake than was removed + assert_ok!(SubtensorModule::add_stake( + RuntimeOrigin::signed(delegate_coldkey), + delegate_hotkey, + owner_adds_more_stake + )); + + // Verify that the stake delta is correct + assert_eq!( + StakeDeltaSinceLastEmissionDrain::::get(delegate_hotkey, delegate_coldkey), + i128::from(owner_added_stake) + .saturating_add_unsigned((owner_adds_more_stake - owner_removed_stake).into()) + ); + }); +} diff --git a/pallets/subtensor/tests/swap_coldkey.rs b/pallets/subtensor/tests/swap_coldkey.rs index 0fe601cab..37467646f 100644 --- a/pallets/subtensor/tests/swap_coldkey.rs +++ b/pallets/subtensor/tests/swap_coldkey.rs @@ -1628,3 +1628,41 @@ fn test_coldkey_swap_no_identity_no_changes_newcoldkey_exists() { assert!(Identities::::get(new_coldkey).is_some()); }); } + +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test swap_coldkey -- test_coldkey_swap_stake_delta --exact --nocapture +#[test] +fn test_coldkey_swap_stake_delta() { + new_test_ext(1).execute_with(|| { + let old_coldkey = U256::from(3); + let new_coldkey = U256::from(4); + let hotkey = U256::from(5); + + let netuid = 1; + let burn_cost = 10; + let tempo = 1; + + // Give the old coldkey a stake delta on hotkey + StakeDeltaSinceLastEmissionDrain::::insert(hotkey, old_coldkey, 123); + // Give the new coldkey a stake delta on hotkey + StakeDeltaSinceLastEmissionDrain::::insert(hotkey, new_coldkey, 456); + let expected_stake_delta = 123 + 456; + // Add StakingHotkeys entry + StakingHotkeys::::insert(old_coldkey, vec![hotkey]); + + // Give balance for the swap fees + SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, 100e9 as u64); + + // Perform the coldkey swap + assert_ok!(SubtensorModule::do_swap_coldkey(&old_coldkey, &new_coldkey)); + + // Ensure the stake delta is correctly transferred + assert_eq!( + StakeDeltaSinceLastEmissionDrain::::get(hotkey, new_coldkey), + expected_stake_delta + ); + assert_eq!( + StakeDeltaSinceLastEmissionDrain::::get(hotkey, old_coldkey), + 0 + ); + }); +} diff --git a/pallets/subtensor/tests/weights.rs b/pallets/subtensor/tests/weights.rs index 7dbeba288..573e5d351 100644 --- a/pallets/subtensor/tests/weights.rs +++ b/pallets/subtensor/tests/weights.rs @@ -481,11 +481,11 @@ fn test_set_weights_min_stake_failed() { // Check the signed extension function. assert_eq!(SubtensorModule::get_weights_min_stake(), 20_000_000_000_000); - assert!(!SubtensorModule::check_weights_min_stake(&hotkey)); + assert!(!SubtensorModule::check_weights_min_stake(&hotkey, netuid)); SubtensorModule::increase_stake_on_hotkey_account(&hotkey, 19_000_000_000_000); - assert!(!SubtensorModule::check_weights_min_stake(&hotkey)); + assert!(!SubtensorModule::check_weights_min_stake(&hotkey, netuid)); SubtensorModule::increase_stake_on_hotkey_account(&hotkey, 20_000_000_000_000); - assert!(SubtensorModule::check_weights_min_stake(&hotkey)); + assert!(SubtensorModule::check_weights_min_stake(&hotkey, netuid)); // Check that it fails at the pallet level. SubtensorModule::set_weights_min_stake(100_000_000_000_000); diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 7d33bff03..17b23df0c 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -146,7 +146,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // `spec_version`, and `authoring_version` are the same between Wasm and native. // This value is set to 100 to notify Polkadot-JS App (https://polkadot.js.org/apps) to use // the compatible custom types. - spec_version: 205, + spec_version: 206, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1,