From 0a341470ada541573f2796c3c31729f25f4ec156 Mon Sep 17 00:00:00 2001 From: Greg Zaitsev Date: Wed, 16 Oct 2024 16:22:23 -0400 Subject: [PATCH 01/29] Add swapping child's parents to hotkey swap --- pallets/subtensor/src/swap/swap_hotkey.rs | 15 +++- pallets/subtensor/tests/swap_hotkey.rs | 97 +++++++++++++++++++++++ 2 files changed, 111 insertions(+), 1 deletion(-) diff --git a/pallets/subtensor/src/swap/swap_hotkey.rs b/pallets/subtensor/src/swap/swap_hotkey.rs index 54095d5fb..d7f62cf53 100644 --- a/pallets/subtensor/src/swap/swap_hotkey.rs +++ b/pallets/subtensor/src/swap/swap_hotkey.rs @@ -325,7 +325,20 @@ impl Pallet { // Remove the old hotkey's child entries ChildKeys::::remove(old_hotkey, netuid); // Insert the same child entries for the new hotkey - ChildKeys::::insert(new_hotkey, netuid, my_children); + ChildKeys::::insert(new_hotkey, netuid, my_children.clone()); + for (_, child_key_i) in my_children { + // For each child, update their parent list + let mut child_parents: Vec<(u64, T::AccountId)> = + ParentKeys::::get(child_key_i.clone(), netuid); + for parent in child_parents.iter_mut() { + // If the parent is the old hotkey, replace it with the new hotkey + if parent.1 == *old_hotkey { + parent.1 = new_hotkey.clone(); + } + } + // Update the child's parent list + ParentKeys::::insert(child_key_i, netuid, child_parents); + } } // 13. Swap ParentKeys. diff --git a/pallets/subtensor/tests/swap_hotkey.rs b/pallets/subtensor/tests/swap_hotkey.rs index 57f206452..efac0eb9d 100644 --- a/pallets/subtensor/tests/swap_hotkey.rs +++ b/pallets/subtensor/tests/swap_hotkey.rs @@ -1204,3 +1204,100 @@ fn test_swap_hotkey_with_pending_emissions() { assert!(!PendingdHotkeyEmission::::contains_key(old_hotkey)); }); } + +#[test] +fn test_swap_parent_hotkey_childkey_maps() { + new_test_ext(1).execute_with(|| { + let netuid: u16 = 1; + let parent_old = U256::from(1); + let coldkey = U256::from(2); + let child = U256::from(3); + let parent_new = U256::from(4); + add_network(netuid, 0, 0); + SubtensorModule::create_account_if_non_existent(&coldkey, &parent_old); + + // Set child and verify state maps + assert_ok!(SubtensorModule::do_set_children( + RuntimeOrigin::signed(coldkey), + parent_old, + netuid, + vec![(u64::MAX, child)] + )); + assert_eq!( + ParentKeys::::get(child, netuid), + vec![(u64::MAX, parent_old)] + ); + assert_eq!( + ChildKeys::::get(parent_old, netuid), + vec![(u64::MAX, child)] + ); + + // Swap + let mut weight = Weight::zero(); + assert_ok!(SubtensorModule::perform_hotkey_swap( + &parent_old, + &parent_new, + &coldkey, + &mut weight + )); + + // Verify parent and child keys updates + assert_eq!( + ParentKeys::::get(child, netuid), + vec![(u64::MAX, parent_new)] + ); + assert_eq!( + ChildKeys::::get(parent_new, netuid), + vec![(u64::MAX, child)] + ); + }) +} + +#[test] +fn test_swap_child_hotkey_childkey_maps() { + new_test_ext(1).execute_with(|| { + let netuid: u16 = 1; + let parent = U256::from(1); + let coldkey = U256::from(2); + let child_old = U256::from(3); + let child_new = U256::from(4); + add_network(netuid, 0, 0); + SubtensorModule::create_account_if_non_existent(&coldkey, &child_old); + SubtensorModule::create_account_if_non_existent(&coldkey, &parent); + + // Set child and verify state maps + assert_ok!(SubtensorModule::do_set_children( + RuntimeOrigin::signed(coldkey), + parent, + netuid, + vec![(u64::MAX, child_old)] + )); + assert_eq!( + ParentKeys::::get(child_old, netuid), + vec![(u64::MAX, parent)] + ); + assert_eq!( + ChildKeys::::get(parent, netuid), + vec![(u64::MAX, child_old)] + ); + + // Swap + let mut weight = Weight::zero(); + assert_ok!(SubtensorModule::perform_hotkey_swap( + &child_old, + &child_new, + &coldkey, + &mut weight + )); + + // Verify parent and child keys updates + assert_eq!( + ParentKeys::::get(child_new, netuid), + vec![(u64::MAX, parent)] + ); + assert_eq!( + ChildKeys::::get(parent, netuid), + vec![(u64::MAX, child_new)] + ); + }) +} From 71d7c7095be6023432e3cec3f13a657b73deb5d2 Mon Sep 17 00:00:00 2001 From: Greg Zaitsev Date: Tue, 29 Oct 2024 11:58:49 -0400 Subject: [PATCH 02/29] Resolve merge conflicts around stake delta feature --- .../subtensor/src/coinbase/run_coinbase.rs | 14 ++++++++++ pallets/subtensor/src/lib.rs | 10 +++---- pallets/subtensor/src/swap/swap_hotkey.rs | 28 +++++++++++++++++-- 3 files changed, 44 insertions(+), 8 deletions(-) diff --git a/pallets/subtensor/src/coinbase/run_coinbase.rs b/pallets/subtensor/src/coinbase/run_coinbase.rs index 723edc423..db86c7c58 100644 --- a/pallets/subtensor/src/coinbase/run_coinbase.rs +++ b/pallets/subtensor/src/coinbase/run_coinbase.rs @@ -382,4 +382,18 @@ impl Pallet { let remainder = block_plus_netuid.rem_euclid(tempo_plus_one); (tempo as u64).saturating_sub(remainder) } + + /// Calculates the nonviable stake for a nominator. + /// The nonviable stake is the stake that was added by the nominator since the last emission drain. + /// This stake will not receive emission until the next emission drain. + /// Note: if the stake delta is below zero, we return zero. We don't allow more stake than the nominator has. + pub fn get_nonviable_stake(hotkey: &T::AccountId, nominator: &T::AccountId) -> u64 { + let stake_delta = StakeDeltaSinceLastEmissionDrain::::get(hotkey, nominator); + if stake_delta.is_negative() { + 0 + } else { + // Should never fail the into, but we handle it anyway. + stake_delta.try_into().unwrap_or(u64::MAX) + } + } } diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 5081d69d3..9fb2adeb7 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -791,17 +791,17 @@ pub mod pallet { DefaultAccumulatedEmission, >; #[pallet::storage] - /// Map ( hot, cold ) --> block_number | Last add stake increase. - pub type LastAddStakeIncrease = StorageDoubleMap< + /// Map ( hot, cold ) --> stake: i128 | Stake added/removed since last emission drain. + pub type StakeDeltaSinceLastEmissionDrain = StorageDoubleMap< _, Blake2_128Concat, T::AccountId, Identity, T::AccountId, - u64, + i128, ValueQuery, - DefaultAccountTake, - >; + DefaultStakeDelta, + >; #[pallet::storage] /// DMAP ( parent, netuid ) --> Vec<(proportion,child)> pub type ChildKeys = StorageDoubleMap< diff --git a/pallets/subtensor/src/swap/swap_hotkey.rs b/pallets/subtensor/src/swap/swap_hotkey.rs index ca3d0b5a7..cf2ace997 100644 --- a/pallets/subtensor/src/swap/swap_hotkey.rs +++ b/pallets/subtensor/src/swap/swap_hotkey.rs @@ -206,7 +206,16 @@ impl Pallet { Delegates::::insert(new_hotkey, old_delegate_take); weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); } - // 9. Swap all subnet specific info. + + // 9. swap PendingdHotkeyEmission + if PendingdHotkeyEmission::::contains_key(old_hotkey) { + let old_pending_hotkey_emission = PendingdHotkeyEmission::::get(old_hotkey); + PendingdHotkeyEmission::::remove(old_hotkey); + PendingdHotkeyEmission::::insert(new_hotkey, old_pending_hotkey_emission); + weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); + } + + // 10. Swap all subnet specific info. let all_netuids: Vec = Self::get_all_subnet_netuids(); for netuid in all_netuids { // 9.1 Remove the previous hotkey and insert the new hotkey from membership. @@ -226,7 +235,7 @@ impl Pallet { Uids::::insert(netuid, new_hotkey, old_uid); weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); - // 9.2.2 Swap the keys. + // 10.2.2 Swap the keys. Keys::::insert(netuid, old_uid, new_hotkey.clone()); weight.saturating_accrue(T::DbWeight::get().reads_writes(0, 1)); } @@ -277,7 +286,7 @@ impl Pallet { } } - // 9.7. Swap neuron TLS certificates. + // 10.7. Swap neuron TLS certificates. // NeuronCertificates( netuid, hotkey ) -> Vec -- the neuron certificate for the hotkey. if is_network_member { if let Ok(old_neuron_certificates) = @@ -355,6 +364,19 @@ impl Pallet { } } + // 14. Swap Stake Delta for all coldkeys. + for (coldkey, stake_delta) in StakeDeltaSinceLastEmissionDrain::::iter_prefix(old_hotkey) + { + let new_stake_delta = StakeDeltaSinceLastEmissionDrain::::get(new_hotkey, &coldkey); + StakeDeltaSinceLastEmissionDrain::::insert( + new_hotkey, + &coldkey, + new_stake_delta.saturating_add(stake_delta), + ); + StakeDeltaSinceLastEmissionDrain::::remove(old_hotkey, &coldkey); + weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); + } + // Return successful after swapping all the relevant terms. Ok(()) } From 0b117650bcaf4c69dd628abd299d69afc0eb5ba9 Mon Sep 17 00:00:00 2001 From: Greg Zaitsev Date: Tue, 29 Oct 2024 12:10:27 -0400 Subject: [PATCH 03/29] Make testnet compatible with mainnet --- Cargo.lock | 93 ++++++++++--------- .../subtensor/src/coinbase/run_coinbase.rs | 50 +++++----- pallets/subtensor/src/lib.rs | 5 + pallets/subtensor/src/staking/add_stake.rs | 6 +- 4 files changed, 81 insertions(+), 73 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0902ad6de..ade99b423 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2357,6 +2357,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2" + [[package]] name = "foreign-types" version = "0.3.2" @@ -3071,6 +3077,11 @@ name = "hashbrown" version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] [[package]] name = "hashlink" @@ -3282,9 +3293,9 @@ dependencies = [ [[package]] name = "hyper" -version = "1.4.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" +checksum = "bbbff0a806a4728c99295b254c8838933b5b082d75e3cb70c8dab21fdfbcfa9a" dependencies = [ "bytes", "futures-channel", @@ -3318,15 +3329,15 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41296eb09f183ac68eec06e03cdbea2e759633d4067b2f6552fc2e009bcad08b" +checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" dependencies = [ "bytes", "futures-util", "http 1.1.0", "http-body 1.0.1", - "hyper 1.4.1", + "hyper 1.5.0", "pin-project-lite", "tokio", "tower-service", @@ -3650,9 +3661,9 @@ dependencies = [ [[package]] name = "jsonrpsee" -version = "0.24.5" +version = "0.24.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "126b48a5acc3c52fbd5381a77898cb60e145123179588a29e7ac48f9c06e401b" +checksum = "c5c71d8c1a731cc4227c2f698d377e7848ca12c8a48866fc5e6951c43a4db843" dependencies = [ "jsonrpsee-core", "jsonrpsee-proc-macros", @@ -3664,9 +3675,9 @@ dependencies = [ [[package]] name = "jsonrpsee-core" -version = "0.24.5" +version = "0.24.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0e503369a76e195b65af35058add0e6900b794a4e9a9316900ddd3a87a80477" +checksum = "f2882f6f8acb9fdaec7cefc4fd607119a9bd709831df7d7672a1d3b644628280" dependencies = [ "async-trait", "bytes", @@ -3687,9 +3698,9 @@ dependencies = [ [[package]] name = "jsonrpsee-proc-macros" -version = "0.24.5" +version = "0.24.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc660a9389e2748e794a40673a4155d501f32db667757cdb80edeff0306b489b" +checksum = "c06c01ae0007548e73412c08e2285ffe5d723195bf268bce67b1b77c3bb2a14d" dependencies = [ "heck 0.5.0", "proc-macro-crate 3.2.0", @@ -3700,15 +3711,15 @@ dependencies = [ [[package]] name = "jsonrpsee-server" -version = "0.24.5" +version = "0.24.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af6e6c9b6d975edcb443565d648b605f3e85a04ec63aa6941811a8894cc9cded" +checksum = "82ad8ddc14be1d4290cd68046e7d1d37acd408efed6d3ca08aefcc3ad6da069c" dependencies = [ "futures-util", "http 1.1.0", "http-body 1.0.1", "http-body-util", - "hyper 1.4.1", + "hyper 1.5.0", "hyper-util", "jsonrpsee-core", "jsonrpsee-types", @@ -3727,9 +3738,9 @@ dependencies = [ [[package]] name = "jsonrpsee-types" -version = "0.24.5" +version = "0.24.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8fb16314327cbc94fdf7965ef7e4422509cd5597f76d137bd104eb34aeede67" +checksum = "a178c60086f24cc35bb82f57c651d0d25d99c4742b4d335de04e97fa1f08a8a1" dependencies = [ "http 1.1.0", "serde", @@ -3908,7 +3919,7 @@ dependencies = [ "libp2p-identity", "log", "multiaddr 0.18.2", - "multihash 0.19.1", + "multihash 0.19.2", "multistream-select", "once_cell", "parking_lot 0.12.3", @@ -3953,7 +3964,7 @@ dependencies = [ "libp2p-identity", "libp2p-swarm", "log", - "lru 0.12.4", + "lru 0.12.5", "quick-protobuf", "quick-protobuf-codec", "smallvec", @@ -3970,7 +3981,7 @@ dependencies = [ "bs58 0.5.1", "ed25519-dalek", "hkdf", - "multihash 0.19.1", + "multihash 0.19.2", "quick-protobuf", "rand", "sha2 0.10.8", @@ -4059,7 +4070,7 @@ dependencies = [ "libp2p-identity", "log", "multiaddr 0.18.2", - "multihash 0.19.1", + "multihash 0.19.2", "once_cell", "quick-protobuf", "rand", @@ -4491,11 +4502,11 @@ dependencies = [ [[package]] name = "lru" -version = "0.12.4" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37ee39891760e7d94734f6f63fedc29a2e4a152f836120753a72503f09fcf904" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" dependencies = [ - "hashbrown 0.14.5", + "hashbrown 0.15.0", ] [[package]] @@ -4827,7 +4838,7 @@ dependencies = [ "data-encoding", "libp2p-identity", "multibase", - "multihash 0.19.1", + "multihash 0.19.2", "percent-encoding", "serde", "static_assertions", @@ -4882,12 +4893,12 @@ dependencies = [ [[package]] name = "multihash" -version = "0.19.1" +version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "076d548d76a0e2a0d4ab471d0b1c36c577786dfc4471242035d97a12a735c492" +checksum = "cc41f430805af9d1cf4adae4ed2149c759b877b01d909a1f40256188d09345d2" dependencies = [ "core2", - "unsigned-varint 0.7.2", + "unsigned-varint 0.8.0", ] [[package]] @@ -4910,12 +4921,6 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" -[[package]] -name = "multimap" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03" - [[package]] name = "multistream-select" version = "0.13.0" @@ -5377,9 +5382,9 @@ checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "openssl" -version = "0.10.66" +version = "0.10.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" +checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" dependencies = [ "bitflags 2.6.0", "cfg-if", @@ -5409,18 +5414,18 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-src" -version = "300.3.2+3.3.2" +version = "300.4.0+3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a211a18d945ef7e648cc6e0058f4c548ee46aab922ea203e0d30e966ea23647b" +checksum = "a709e02f2b4aca747929cca5ed248880847c650233cf8b8cdc48f40aaf4898a6" dependencies = [ "cc", ] [[package]] name = "openssl-sys" -version = "0.9.103" +version = "0.9.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" +checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" dependencies = [ "cc", "libc", @@ -6527,7 +6532,7 @@ dependencies = [ "itertools 0.10.5", "lazy_static", "log", - "multimap 0.8.3", + "multimap", "petgraph", "prettyplease 0.1.25", "prost 0.11.9", @@ -6548,7 +6553,7 @@ dependencies = [ "heck 0.5.0", "itertools 0.12.1", "log", - "multimap 0.10.0", + "multimap", "once_cell", "petgraph", "prettyplease 0.2.22", @@ -7913,7 +7918,7 @@ dependencies = [ "litep2p", "log", "multiaddr 0.18.2", - "multihash 0.19.1", + "multihash 0.19.2", "rand", "thiserror", "zeroize", @@ -8025,7 +8030,7 @@ dependencies = [ "governor", "http 1.1.0", "http-body-util", - "hyper 1.4.1", + "hyper 1.5.0", "ip_network", "jsonrpsee", "log", @@ -9830,7 +9835,7 @@ version = "0.17.0" source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.16.0-rc1#a427d8fb677b62635dfb78a6e530facdd2c362ec" dependencies = [ "http-body-util", - "hyper 1.4.1", + "hyper 1.5.0", "hyper-util", "log", "prometheus", diff --git a/pallets/subtensor/src/coinbase/run_coinbase.rs b/pallets/subtensor/src/coinbase/run_coinbase.rs index db86c7c58..badb811fa 100644 --- a/pallets/subtensor/src/coinbase/run_coinbase.rs +++ b/pallets/subtensor/src/coinbase/run_coinbase.rs @@ -265,69 +265,65 @@ impl Pallet { // --- 1.0 Drain the hotkey emission. PendingdHotkeyEmission::::insert(hotkey, 0); - // --- 2 Retrieve the last time this hotkey's emissions were drained. - let last_emission_drain: u64 = LastHotkeyEmissionDrain::::get(hotkey); - - // --- 3 Update the block value to the current block number. + // --- 2 Update the block value to the current block number. LastHotkeyEmissionDrain::::insert(hotkey, block_number); - // --- 4 Retrieve the total stake for the hotkey from all nominations. + // --- 3 Retrieve the total stake for the hotkey from all nominations. let total_hotkey_stake: u64 = Self::get_total_stake_for_hotkey(hotkey); - // --- 5 Calculate the emission take for the hotkey. + // --- 4 Calculate the emission take for the hotkey. let take_proportion: I64F64 = I64F64::from_num(Delegates::::get(hotkey)) .saturating_div(I64F64::from_num(u16::MAX)); let hotkey_take: u64 = (take_proportion.saturating_mul(I64F64::from_num(emission))).to_num::(); - // --- 6 Compute the remaining emission after deducting the hotkey's take. + // --- 5 Compute the remaining emission after deducting the hotkey's take. let emission_minus_take: u64 = emission.saturating_sub(hotkey_take); - // --- 7 Calculate the remaining emission after the hotkey's take. + // --- 6 Calculate the remaining emission after the hotkey's take. let mut remainder: u64 = emission_minus_take; - // --- 8 Iterate over each nominator and get all viable stake. + // --- 7 Iterate over each nominator and get all viable stake. let mut total_viable_nominator_stake: u64 = total_hotkey_stake; - for (nominator, nominator_stake) in Stake::::iter_prefix(hotkey) { - if LastAddStakeIncrease::::get(hotkey, nominator) > last_emission_drain { - total_viable_nominator_stake = - total_viable_nominator_stake.saturating_sub(nominator_stake); - } + for (nominator, _) in Stake::::iter_prefix(hotkey) { + let nonviable_nomintaor_stake = Self::get_nonviable_stake(hotkey, &nominator); + + total_viable_nominator_stake = + total_viable_nominator_stake.saturating_sub(nonviable_nomintaor_stake); } - // --- 9 Iterate over each nominator. + // --- 8 Iterate over each nominator. if total_viable_nominator_stake != 0 { for (nominator, nominator_stake) in Stake::::iter_prefix(hotkey) { - // --- 10 Check if the stake was manually increased by the user since the last emission drain for this 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. - if LastAddStakeIncrease::::get(hotkey, nominator.clone()) > last_emission_drain { - continue; - } + let viable_nominator_stake = + nominator_stake.saturating_sub(Self::get_nonviable_stake(hotkey, &nominator)); - // --- 11 Calculate this nominator's share of the emission. - let nominator_emission: I64F64 = I64F64::from_num(emission_minus_take) - .saturating_mul(I64F64::from_num(nominator_stake)) + // --- 10 Calculate this nominator's share of the emission. + let nominator_emission: I64F64 = I64F64::from_num(viable_nominator_stake) .checked_div(I64F64::from_num(total_viable_nominator_stake)) - .unwrap_or(I64F64::from_num(0)); + .unwrap_or(I64F64::from_num(0)) + .saturating_mul(I64F64::from_num(emission_minus_take)); - // --- 12 Increase the stake for the nominator. + // --- 11 Increase the stake for the nominator. Self::increase_stake_on_coldkey_hotkey_account( &nominator, hotkey, nominator_emission.to_num::(), ); - // --- 13* Record event and Subtract the nominator's emission from the remainder. + // --- 12* Record event and Subtract the nominator's emission from the remainder. total_new_tao = total_new_tao.saturating_add(nominator_emission.to_num::()); remainder = remainder.saturating_sub(nominator_emission.to_num::()); } } - // --- 14 Finally, add the stake to the hotkey itself, including its take and the remaining emission. + // --- 13 Finally, add the stake to the hotkey itself, including its take and the remaining emission. let hotkey_new_tao: u64 = hotkey_take.saturating_add(remainder); Self::increase_stake_on_hotkey_account(hotkey, hotkey_new_tao); - // --- 15 Record new tao creation event and return the amount created. + // --- 14 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 9fb2adeb7..fa24ccc8d 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -259,6 +259,11 @@ pub mod pallet { 0 } #[pallet::type_value] + /// Default stake delta. + pub fn DefaultStakeDelta() -> i128 { + 0 + } + #[pallet::type_value] /// Default stakes per interval. pub fn DefaultStakesPerInterval() -> (u64, u64) { (0, 0) diff --git a/pallets/subtensor/src/staking/add_stake.rs b/pallets/subtensor/src/staking/add_stake.rs index c9cbd7e04..72d8374bc 100644 --- a/pallets/subtensor/src/staking/add_stake.rs +++ b/pallets/subtensor/src/staking/add_stake.rs @@ -70,8 +70,10 @@ impl Pallet { Error::::StakeRateLimitExceeded ); - // Set the last time the stake increased for nominator drain protection. - LastAddStakeIncrease::::insert(&hotkey, &coldkey, Self::get_current_block_as_u64()); + // Track this addition in the stake delta. + StakeDeltaSinceLastEmissionDrain::::mutate(&hotkey, &coldkey, |stake_delta| { + *stake_delta = stake_delta.saturating_add_unsigned(stake_to_be_added as u128); + }); // If coldkey is not owner of the hotkey, it's a nomination stake. if !Self::coldkey_owns_hotkey(&coldkey, &hotkey) { From 2488fd23d3336e8691f8cdbb5aa212573db8c217 Mon Sep 17 00:00:00 2001 From: Greg Zaitsev Date: Tue, 29 Oct 2024 12:11:45 -0400 Subject: [PATCH 04/29] Format --- pallets/subtensor/src/lib.rs | 4 ++-- pallets/subtensor/src/swap/swap_hotkey.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index fa24ccc8d..8f2910222 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -262,7 +262,7 @@ pub mod pallet { /// Default stake delta. pub fn DefaultStakeDelta() -> i128 { 0 - } + } #[pallet::type_value] /// Default stakes per interval. pub fn DefaultStakesPerInterval() -> (u64, u64) { @@ -806,7 +806,7 @@ pub mod pallet { i128, ValueQuery, DefaultStakeDelta, - >; + >; #[pallet::storage] /// DMAP ( parent, netuid ) --> Vec<(proportion,child)> pub type ChildKeys = StorageDoubleMap< diff --git a/pallets/subtensor/src/swap/swap_hotkey.rs b/pallets/subtensor/src/swap/swap_hotkey.rs index cf2ace997..2feff6289 100644 --- a/pallets/subtensor/src/swap/swap_hotkey.rs +++ b/pallets/subtensor/src/swap/swap_hotkey.rs @@ -214,7 +214,7 @@ impl Pallet { PendingdHotkeyEmission::::insert(new_hotkey, old_pending_hotkey_emission); weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); } - + // 10. Swap all subnet specific info. let all_netuids: Vec = Self::get_all_subnet_netuids(); for netuid in all_netuids { From 90fdbb1b0c527e724cfe8531c93b87ecaf7c1537 Mon Sep 17 00:00:00 2001 From: Greg Zaitsev Date: Mon, 4 Nov 2024 17:09:50 -0500 Subject: [PATCH 05/29] Remove manual_inspect from Cargo.toml clippy config --- Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 42b5c473b..ba65ac182 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,7 +43,6 @@ indexing-slicing = "deny" arithmetic-side-effects = "deny" type_complexity = "allow" unwrap-used = "deny" -# manual_inspect = "allow" [workspace.dependencies] cargo-husky = { version = "1", default-features = false } From 87d21f896578d3784b46c96a3754a4ae7f6e35ff Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Mon, 4 Nov 2024 17:43:18 -0500 Subject: [PATCH 06/29] try re-adding --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index ba65ac182..54de1b0a0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,6 +43,7 @@ indexing-slicing = "deny" arithmetic-side-effects = "deny" type_complexity = "allow" unwrap-used = "deny" +manual_inspect = "allow" [workspace.dependencies] cargo-husky = { version = "1", default-features = false } From 9b1f243e004023225260f272d512adb592f84bc7 Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Tue, 5 Nov 2024 13:58:35 -0500 Subject: [PATCH 07/29] bump CI From da6d0813a0d62baf6bf8da55eb9c83cc8a6e30d7 Mon Sep 17 00:00:00 2001 From: Liam Date: Wed, 6 Nov 2024 22:47:25 +0400 Subject: [PATCH 08/29] fix: host functions --- node/src/service.rs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/node/src/service.rs b/node/src/service.rs index cc0bf2862..613781268 100644 --- a/node/src/service.rs +++ b/node/src/service.rs @@ -17,8 +17,18 @@ use std::{sync::Arc, time::Duration}; /// imported and generated. const GRANDPA_JUSTIFICATION_PERIOD: u32 = 512; +/// Always enable runtime benchmark host functions, the genesis state +/// was built with them so we're stuck with them forever. +/// +/// They're just a noop, never actually get used if the runtime was not compiled with +/// `runtime-benchmarks`. +pub type HostFunctions = ( + sp_io::SubstrateHostFunctions, + frame_benchmarking::benchmarking::HostFunctions, +); + pub(crate) type FullClient = - sc_service::TFullClient>; + sc_service::TFullClient>; type FullBackend = sc_service::TFullBackend; type FullSelectChain = sc_consensus::LongestChain; @@ -55,7 +65,7 @@ pub fn new_partial( }) .transpose()?; - let executor = sc_service::new_wasm_executor::(&config.executor); + let executor = sc_service::new_wasm_executor::(&config.executor); let (client, backend, keystore_container, task_manager) = sc_service::new_full_parts::( From f5c34317ef4968c4cf1340b043d571d1dfc103ec Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Wed, 6 Nov 2024 22:30:11 -0500 Subject: [PATCH 09/29] Hotfix/skip root for emission accum (#933) * skip netuid 0 for pending emission accum * comment --- pallets/subtensor/src/coinbase/run_coinbase.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pallets/subtensor/src/coinbase/run_coinbase.rs b/pallets/subtensor/src/coinbase/run_coinbase.rs index badb811fa..0615ccc39 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? From 95411b2bb794ccae761f9155d033ccd89da91faf Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Wed, 6 Nov 2024 22:34:50 -0500 Subject: [PATCH 10/29] Hotfix/clear stake delta on drain (#934) * add comment also * chore: fmt * add back delta handling in helpers * track removals in stake delta * handle stake delta in ck swap func * add tests back * add tests for staking back * add back test for ck swap --- .../subtensor/src/coinbase/run_coinbase.rs | 9 +- pallets/subtensor/src/staking/helpers.rs | 6 + pallets/subtensor/src/staking/remove_stake.rs | 5 + pallets/subtensor/src/swap/swap_coldkey.rs | 21 +- pallets/subtensor/tests/coinbase.rs | 1296 ++++++++++++++++- pallets/subtensor/tests/staking.rs | 103 ++ pallets/subtensor/tests/swap_coldkey.rs | 38 + 7 files changed, 1470 insertions(+), 8 deletions(-) diff --git a/pallets/subtensor/src/coinbase/run_coinbase.rs b/pallets/subtensor/src/coinbase/run_coinbase.rs index 0615ccc39..aa3fa17d2 100644 --- a/pallets/subtensor/src/coinbase/run_coinbase.rs +++ b/pallets/subtensor/src/coinbase/run_coinbase.rs @@ -303,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)); @@ -331,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/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/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/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 + ); + }); +} From b3127042414b2ca3e1343d1159648182f078a568 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Wed, 6 Nov 2024 22:39:01 -0500 Subject: [PATCH 11/29] respect chk for set weights min stake filter (#935) * respect chk for set weights min stake filter * use for test func * respect chk in set weights call also * add tests back * fix weights min stake tests also --- pallets/subtensor/src/lib.rs | 20 +-- pallets/subtensor/src/subnets/uids.rs | 2 +- pallets/subtensor/src/subnets/weights.rs | 4 +- pallets/subtensor/tests/children.rs | 191 +++++++++++++++++++++++ pallets/subtensor/tests/weights.rs | 6 +- 5 files changed, 207 insertions(+), 16 deletions(-) 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/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/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/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); From 714718794ee0bda9e9cfd27156b4ed5c1f20e52f Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Wed, 6 Nov 2024 22:34:31 -0500 Subject: [PATCH 12/29] use chk for neuron info lite stake (#936) --- pallets/subtensor/src/rpc_info/neuron_info.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) 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(), From c2aba47a53ab98fba9500d46d7fd3f5489ef3dfc Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Wed, 6 Nov 2024 22:30:11 -0500 Subject: [PATCH 13/29] Hotfix/skip root for emission accum (#933) * skip netuid 0 for pending emission accum * comment --- pallets/subtensor/src/coinbase/run_coinbase.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pallets/subtensor/src/coinbase/run_coinbase.rs b/pallets/subtensor/src/coinbase/run_coinbase.rs index badb811fa..0615ccc39 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? From a308cd45c60b4676f27ea762249a1e73ec6739de Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Wed, 6 Nov 2024 22:34:50 -0500 Subject: [PATCH 14/29] Hotfix/clear stake delta on drain (#934) * add comment also * chore: fmt * add back delta handling in helpers * track removals in stake delta * handle stake delta in ck swap func * add tests back * add tests for staking back * add back test for ck swap --- .../subtensor/src/coinbase/run_coinbase.rs | 9 +- pallets/subtensor/src/staking/helpers.rs | 6 + pallets/subtensor/src/staking/remove_stake.rs | 5 + pallets/subtensor/src/swap/swap_coldkey.rs | 21 +- pallets/subtensor/tests/coinbase.rs | 1296 ++++++++++++++++- pallets/subtensor/tests/staking.rs | 103 ++ pallets/subtensor/tests/swap_coldkey.rs | 38 + 7 files changed, 1470 insertions(+), 8 deletions(-) diff --git a/pallets/subtensor/src/coinbase/run_coinbase.rs b/pallets/subtensor/src/coinbase/run_coinbase.rs index 0615ccc39..aa3fa17d2 100644 --- a/pallets/subtensor/src/coinbase/run_coinbase.rs +++ b/pallets/subtensor/src/coinbase/run_coinbase.rs @@ -303,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)); @@ -331,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/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/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/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 + ); + }); +} From e21ba0decc4d56aa8c2f996c9a92abe64e1348c1 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Wed, 6 Nov 2024 22:39:01 -0500 Subject: [PATCH 15/29] respect chk for set weights min stake filter (#935) * respect chk for set weights min stake filter * use for test func * respect chk in set weights call also * add tests back * fix weights min stake tests also --- pallets/subtensor/src/lib.rs | 20 +-- pallets/subtensor/src/subnets/uids.rs | 2 +- pallets/subtensor/src/subnets/weights.rs | 4 +- pallets/subtensor/tests/children.rs | 191 +++++++++++++++++++++++ pallets/subtensor/tests/weights.rs | 6 +- 5 files changed, 207 insertions(+), 16 deletions(-) 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/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/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/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); From e735f728de08fd950e300961c0e225e2a6e6871c Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Wed, 6 Nov 2024 22:34:31 -0500 Subject: [PATCH 16/29] use chk for neuron info lite stake (#936) --- pallets/subtensor/src/rpc_info/neuron_info.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) 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(), From fd7a996a5ac160d14670363625a07d08f37c2840 Mon Sep 17 00:00:00 2001 From: Greg Zaitsev Date: Thu, 7 Nov 2024 13:46:26 -0500 Subject: [PATCH 17/29] Bump spec version to 207 --- runtime/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 7d33bff03..cc81da7f4 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: 207, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, From fc972f79e493dc98537528d344af83b76ee813e2 Mon Sep 17 00:00:00 2001 From: Greg Zaitsev Date: Thu, 7 Nov 2024 13:49:45 -0500 Subject: [PATCH 18/29] Empty commit, re-trigger CI From 00b9b3c559d2880114f8c9b1b723ca92aed56efe Mon Sep 17 00:00:00 2001 From: Greg Zaitsev Date: Thu, 7 Nov 2024 14:15:52 -0500 Subject: [PATCH 19/29] Bump spec version to 207 --- runtime/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 3d1cbb582..fc22745a7 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -160,7 +160,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: 206, + spec_version: 207, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, From 480cf3241497493c99a7263a3a4fc1d1b16a3141 Mon Sep 17 00:00:00 2001 From: Greg Zaitsev Date: Fri, 8 Nov 2024 11:55:03 -0500 Subject: [PATCH 20/29] Empty commit to trigger CI From 2de219d8f4064786c39b5fb522189dc04899976a Mon Sep 17 00:00:00 2001 From: Liam Date: Wed, 6 Nov 2024 22:32:29 +0400 Subject: [PATCH 21/29] fix: host functions --- node/src/service.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/node/src/service.rs b/node/src/service.rs index fef86dee6..91ffc3d45 100644 --- a/node/src/service.rs +++ b/node/src/service.rs @@ -35,15 +35,15 @@ use node_subtensor_runtime::{ /// imported and generated. const GRANDPA_JUSTIFICATION_PERIOD: u32 = 512; -/// Only enable the benchmarking host functions when we actually want to benchmark. -#[cfg(feature = "runtime-benchmarks")] +/// Always enable runtime benchmark host functions, the genesis state +/// was built with them so we're stuck with them forever. +/// +/// They're just a noop, never actually get used if the runtime was not compiled with +/// `runtime-benchmarks`. pub type HostFunctions = ( sp_io::SubstrateHostFunctions, frame_benchmarking::benchmarking::HostFunctions, ); -/// Otherwise we use empty host functions for ext host functions. -#[cfg(not(feature = "runtime-benchmarks"))] -pub type HostFunctions = sp_io::SubstrateHostFunctions; pub type Backend = FullBackend; pub type Client = FullClient; From bbd5a1e5edabf106d6602c347024530f9ba7a153 Mon Sep 17 00:00:00 2001 From: Liam Date: Wed, 6 Nov 2024 22:47:25 +0400 Subject: [PATCH 22/29] fix: host functions --- node/src/service.rs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/node/src/service.rs b/node/src/service.rs index cc0bf2862..613781268 100644 --- a/node/src/service.rs +++ b/node/src/service.rs @@ -17,8 +17,18 @@ use std::{sync::Arc, time::Duration}; /// imported and generated. const GRANDPA_JUSTIFICATION_PERIOD: u32 = 512; +/// Always enable runtime benchmark host functions, the genesis state +/// was built with them so we're stuck with them forever. +/// +/// They're just a noop, never actually get used if the runtime was not compiled with +/// `runtime-benchmarks`. +pub type HostFunctions = ( + sp_io::SubstrateHostFunctions, + frame_benchmarking::benchmarking::HostFunctions, +); + pub(crate) type FullClient = - sc_service::TFullClient>; + sc_service::TFullClient>; type FullBackend = sc_service::TFullBackend; type FullSelectChain = sc_consensus::LongestChain; @@ -55,7 +65,7 @@ pub fn new_partial( }) .transpose()?; - let executor = sc_service::new_wasm_executor::(&config.executor); + let executor = sc_service::new_wasm_executor::(&config.executor); let (client, backend, keystore_container, task_manager) = sc_service::new_full_parts::( From 09311be43e695d4571312f7f9681cbc13e055da6 Mon Sep 17 00:00:00 2001 From: johnreedv Date: Tue, 12 Nov 2024 10:03:13 -0800 Subject: [PATCH 23/29] hotfix chainspec build --- scripts/build_all_chainspecs.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/build_all_chainspecs.sh b/scripts/build_all_chainspecs.sh index 7750a358b..03727b728 100755 --- a/scripts/build_all_chainspecs.sh +++ b/scripts/build_all_chainspecs.sh @@ -3,7 +3,7 @@ set -e echo "*** Building node..." -cargo build +cargo build -p node-subtensor echo "*** Building new chainspecs..." From 6ccd420f32843ae9bed19c84ebb27d519bb2005f Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Tue, 12 Nov 2024 13:13:05 -0500 Subject: [PATCH 24/29] bump CI From 64adce13271a08069e695ebf63bad04b5fab8bd1 Mon Sep 17 00:00:00 2001 From: Greg Zaitsev Date: Tue, 12 Nov 2024 16:55:05 -0500 Subject: [PATCH 25/29] Merge testnet into devnet in progress --- node/src/service.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/node/src/service.rs b/node/src/service.rs index 241f81123..8906f9a29 100644 --- a/node/src/service.rs +++ b/node/src/service.rs @@ -45,10 +45,6 @@ pub type HostFunctions = ( frame_benchmarking::benchmarking::HostFunctions, ); -pub(crate) type FullClient = - sc_service::TFullClient>; -type FullBackend = sc_service::TFullBackend; -type FullSelectChain = sc_consensus::LongestChain; pub type Backend = FullBackend; pub type Client = FullClient; From 21527a9fd2aefffaaa7aedd32c9a87a2f17c6275 Mon Sep 17 00:00:00 2001 From: Aliaksandr Tsurko Date: Wed, 13 Nov 2024 00:08:02 +0100 Subject: [PATCH 26/29] Fix compilation --- node/src/service.rs | 168 +++++++++++++++++++++----------------------- 1 file changed, 80 insertions(+), 88 deletions(-) diff --git a/node/src/service.rs b/node/src/service.rs index 8906f9a29..dc5683bb3 100644 --- a/node/src/service.rs +++ b/node/src/service.rs @@ -53,41 +53,41 @@ type GrandpaBlockImport = sc_consensus_grandpa::GrandpaBlockImport, B, C, FullSelectChain>; type GrandpaLinkHalf = sc_consensus_grandpa::LinkHalf>; -pub fn new_partial( +pub fn new_partial( config: &Configuration, eth_config: &EthConfiguration, build_import_queue: BIQ, ) -> Result< PartialComponents< - FullClient, - FullBackend, - FullSelectChain, - BasicQueue, - FullPool>, + Client, + FullBackend, + FullSelectChain, + BasicQueue, + FullPool, ( Option, - BoxBlockImport, - GrandpaLinkHalf>, - FrontierBackend>, - Arc>, + BoxBlockImport, + GrandpaLinkHalf, + FrontierBackend, + Arc>, ), >, ServiceError, > where - B: BlockT, - RA: ConstructRuntimeApi>, - RA: Send + Sync + 'static, - RA::RuntimeApi: RuntimeApiCollection, - HF: HostFunctionsT + 'static, + // B: BlockT, + // RA: ConstructRuntimeApi, + // RA: Send + Sync + 'static, + // RA::RuntimeApi: RuntimeApiCollection, + // HF: HostFunctionsT + 'static, BIQ: FnOnce( - Arc>, + Arc, &Configuration, &EthConfiguration, &TaskManager, Option, - GrandpaBlockImport>, - ) -> Result<(BasicQueue, BoxBlockImport), ServiceError>, + GrandpaBlockImport, + ) -> Result<(BasicQueue, BoxBlockImport), ServiceError>, { let telemetry = config .telemetry_endpoints @@ -100,12 +100,13 @@ where }) .transpose()?; - let executor = sc_service::new_wasm_executor::(&config.executor); - let (client, backend, keystore_container, task_manager) = sc_service::new_full_parts::( - config, - telemetry.as_ref().map(|(_, telemetry)| telemetry.handle()), - executor, - )?; + let executor = sc_service::new_wasm_executor(&config.executor); + let (client, backend, keystore_container, task_manager) = + sc_service::new_full_parts::( + config, + telemetry.as_ref().map(|(_, telemetry)| telemetry.handle()), + executor, + )?; let client = Arc::new(client); let telemetry = telemetry.map(|(worker, telemetry)| { @@ -124,7 +125,7 @@ where telemetry.as_ref().map(|x| x.handle()), )?; - let storage_override = Arc::new(StorageOverrideHandler::::new(client.clone())); + let storage_override = Arc::new(StorageOverrideHandler::<_, _, _>::new(client.clone())); let frontier_backend = match eth_config.frontier_backend_type { BackendType::KeyValue => FrontierBackend::KeyValue(Arc::new(fc_db::kv::Backend::open( Arc::clone(&client), @@ -190,21 +191,21 @@ where } /// Build the import queue for the template runtime (aura + grandpa). -pub fn build_aura_grandpa_import_queue( - client: Arc>, +pub fn build_aura_grandpa_import_queue( + client: Arc, config: &Configuration, eth_config: &EthConfiguration, task_manager: &TaskManager, telemetry: Option, - grandpa_block_import: GrandpaBlockImport>, -) -> Result<(BasicQueue, BoxBlockImport), ServiceError> + grandpa_block_import: GrandpaBlockImport, +) -> Result<(BasicQueue, BoxBlockImport), ServiceError> where - B: BlockT, - NumberFor: BlockNumberOps, - RA: ConstructRuntimeApi>, - RA: Send + Sync + 'static, - RA::RuntimeApi: RuntimeApiCollection, - HF: HostFunctionsT + 'static, + // B: BlockT, + NumberFor: BlockNumberOps, + // RA: ConstructRuntimeApi>, + // RA: Send + Sync + 'static, + // RA::RuntimeApi: RuntimeApiCollection, + // HF: HostFunctionsT + 'static, { let frontier_block_import = FrontierBlockImport::new(grandpa_block_import.clone(), client.clone()); @@ -241,20 +242,20 @@ where } /// Build the import queue for the template runtime (manual seal). -pub fn build_manual_seal_import_queue( - client: Arc>, +pub fn build_manual_seal_import_queue( + client: Arc, config: &Configuration, _eth_config: &EthConfiguration, task_manager: &TaskManager, _telemetry: Option, - _grandpa_block_import: GrandpaBlockImport>, -) -> Result<(BasicQueue, BoxBlockImport), ServiceError> -where - B: BlockT, - RA: ConstructRuntimeApi>, - RA: Send + Sync + 'static, - RA::RuntimeApi: RuntimeApiCollection, - HF: HostFunctionsT + 'static, + _grandpa_block_import: GrandpaBlockImport, +) -> Result<(BasicQueue, BoxBlockImport), ServiceError> +// where + // B: BlockT, + // RA: ConstructRuntimeApi>, + // RA: Send + Sync + 'static, + // RA::RuntimeApi: RuntimeApiCollection, + // HF: HostFunctionsT + 'static, { let frontier_block_import = FrontierBlockImport::new(client.clone(), client); Ok(( @@ -268,25 +269,25 @@ where } /// Builds a new service for a full client. -pub async fn new_full( +pub async fn new_full( mut config: Configuration, eth_config: EthConfiguration, sealing: Option, ) -> Result where - B: BlockT, - NumberFor: BlockNumberOps, - ::Header: Unpin, - RA: ConstructRuntimeApi>, - RA: Send + Sync + 'static, - RA::RuntimeApi: RuntimeApiCollection, - HF: HostFunctionsT + 'static, - NB: sc_network::NetworkBackend::Hash>, + // B: BlockT, + NumberFor: BlockNumberOps, + // ::Header: Unpin, + // RA: ConstructRuntimeApi>, + // RA: Send + Sync + 'static, + // RA::RuntimeApi: RuntimeApiCollection, + // HF: HostFunctionsT + 'static, + NB: sc_network::NetworkBackend::Hash>, { let build_import_queue = if sealing.is_some() { - build_manual_seal_import_queue:: + build_manual_seal_import_queue } else { - build_aura_grandpa_import_queue:: + build_aura_grandpa_import_queue }; let PartialComponents { @@ -332,7 +333,7 @@ where None } else { net_config.add_notification_protocol(grandpa_protocol_config); - let warp_sync: Arc> = + let warp_sync: Arc> = Arc::new(sc_consensus_grandpa::warp_proof::NetworkProvider::new( backend.clone(), grandpa_link.shared_authority_set().clone(), @@ -378,10 +379,11 @@ where let role = config.role; let force_authoring = config.force_authoring; - let backoff_authoring_blocks = Some(BackoffAuthoringOnFinalizedHeadLagging::> { - unfinalized_slack: 6u32.into(), - ..Default::default() - }); + let backoff_authoring_blocks = + Some(BackoffAuthoringOnFinalizedHeadLagging::> { + unfinalized_slack: 6u32.into(), + ..Default::default() + }); let name = config.network.node_name.clone(); let frontier_backend = Arc::new(frontier_backend); let enable_grandpa = !config.disable_grandpa && sealing.is_none(); @@ -395,7 +397,7 @@ where // The MappingSyncWorker sends through the channel on block import and the subscription emits a notification to the subscriber on receiving a message through this channel. // This way we avoid race conditions when using native substrate block import notification stream. let pubsub_notification_sinks: fc_mapping_sync::EthereumBlockNotificationSinks< - fc_mapping_sync::EthereumBlockNotification, + fc_mapping_sync::EthereumBlockNotification, > = Default::default(); let pubsub_notification_sinks = Arc::new(pubsub_notification_sinks); @@ -447,7 +449,7 @@ where client: client.clone(), pool: pool.clone(), graph: pool.pool().clone(), - converter: Some(TransactionConverter::::default()), + converter: Some(TransactionConverter::::default()), is_authority, enable_dev_signer, network: network.clone(), @@ -640,16 +642,10 @@ pub async fn build_full( ) -> Result { match config.network.network_backend { sc_network::config::NetworkBackendType::Libp2p => { - new_full::>( - config, eth_config, sealing, - ) - .await + new_full::>(config, eth_config, sealing).await } sc_network::config::NetworkBackendType::Litep2p => { - new_full::( - config, eth_config, sealing, - ) - .await + new_full::>(config, eth_config, sealing).await } } } @@ -675,35 +671,31 @@ pub fn new_chain_ops( task_manager, other, .. - } = new_partial::( - config, - eth_config, - build_aura_grandpa_import_queue, - )?; + } = new_partial(config, eth_config, build_aura_grandpa_import_queue)?; Ok((client, backend, import_queue, task_manager, other.3)) } #[allow(clippy::too_many_arguments)] -fn run_manual_seal_authorship( +fn run_manual_seal_authorship( eth_config: &EthConfiguration, sealing: Sealing, - client: Arc>, - transaction_pool: Arc>>, - select_chain: FullSelectChain, - block_import: BoxBlockImport, + client: Arc, + transaction_pool: Arc>, + select_chain: FullSelectChain, + block_import: BoxBlockImport, task_manager: &TaskManager, prometheus_registry: Option<&Registry>, telemetry: Option<&Telemetry>, commands_stream: mpsc::Receiver< - sc_consensus_manual_seal::rpc::EngineCommand<::Hash>, + sc_consensus_manual_seal::rpc::EngineCommand<::Hash>, >, ) -> Result<(), ServiceError> -where - B: BlockT, - RA: ConstructRuntimeApi>, - RA: Send + Sync + 'static, - RA::RuntimeApi: RuntimeApiCollection, - HF: HostFunctionsT + 'static, +// where + // B: BlockT, + // RA: ConstructRuntimeApi>, + // RA: Send + Sync + 'static, + // RA::RuntimeApi: RuntimeApiCollection, + // HF: HostFunctionsT + 'static, { let proposer_factory = sc_basic_authorship::ProposerFactory::new( task_manager.spawn_handle(), From 510c4227e918ee4fcc8c436c70191b3c7fe51323 Mon Sep 17 00:00:00 2001 From: Greg Zaitsev Date: Tue, 12 Nov 2024 18:30:03 -0500 Subject: [PATCH 27/29] Cleanup imports --- node/src/service.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/node/src/service.rs b/node/src/service.rs index dc5683bb3..77673bb9c 100644 --- a/node/src/service.rs +++ b/node/src/service.rs @@ -1,7 +1,7 @@ //! Service and ServiceFactory implementation. Specialized wrapper over substrate service. use crate::cli::Sealing; -use crate::client::{FullBackend, FullClient, RuntimeApiCollection}; +use crate::client::{FullBackend, FullClient}; use crate::ethereum::{ db_config_dir, new_frontier_partial, spawn_frontier_tasks, BackendType, EthConfiguration, FrontierBackend, FrontierBlockImport, FrontierPartialComponents, StorageOverride, @@ -12,15 +12,13 @@ use sc_client_api::{Backend as BackendT, BlockBackend}; use sc_consensus::{BasicQueue, BoxBlockImport}; use sc_consensus_grandpa::BlockNumberOps; use sc_consensus_slots::BackoffAuthoringOnFinalizedHeadLagging; -use sc_executor::HostFunctions as HostFunctionsT; use sc_network_sync::strategy::warp::{WarpSyncConfig, WarpSyncProvider}; use sc_service::{error::Error as ServiceError, Configuration, PartialComponents, TaskManager}; use sc_telemetry::{log, Telemetry, TelemetryHandle, TelemetryWorker}; use sc_transaction_pool::FullPool; use sc_transaction_pool_api::OffchainTransactionPoolFactory; -use sp_api::ConstructRuntimeApi; -use sp_consensus_aura::sr25519::{AuthorityId as AuraId, AuthorityPair as AuraPair}; -use sp_core::{H256, U256}; +use sp_consensus_aura::sr25519::AuthorityPair as AuraPair; +use sp_core::U256; use sp_runtime::traits::{Block as BlockT, NumberFor}; use std::{cell::RefCell, path::Path}; use std::{sync::Arc, time::Duration}; @@ -28,7 +26,7 @@ use substrate_prometheus_endpoint::Registry; // Runtime use node_subtensor_runtime::{ - opaque::Block, AccountId, Balance, Nonce, RuntimeApi, TransactionConverter, + opaque::Block, RuntimeApi, TransactionConverter, }; /// The minimum period of blocks on which justifications will be From 8163679c2fa6678b36279eee5e35060ee02b0831 Mon Sep 17 00:00:00 2001 From: Greg Zaitsev Date: Tue, 12 Nov 2024 18:44:34 -0500 Subject: [PATCH 28/29] Fix clippy --- node/src/service.rs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/node/src/service.rs b/node/src/service.rs index 77673bb9c..84a3a1255 100644 --- a/node/src/service.rs +++ b/node/src/service.rs @@ -25,9 +25,7 @@ use std::{sync::Arc, time::Duration}; use substrate_prometheus_endpoint::Registry; // Runtime -use node_subtensor_runtime::{ - opaque::Block, RuntimeApi, TransactionConverter, -}; +use node_subtensor_runtime::{opaque::Block, RuntimeApi, TransactionConverter}; /// The minimum period of blocks on which justifications will be /// imported and generated. @@ -314,9 +312,7 @@ where let metrics = NB::register_notification_metrics(maybe_registry); let grandpa_protocol_name = sc_consensus_grandpa::protocol_standard_name( - &client - .block_hash(0u32.into())? - .expect("Genesis block exists; qed"), + &client.block_hash(0u32)?.expect("Genesis block exists; qed"), &config.chain_spec, ); @@ -379,7 +375,7 @@ where let force_authoring = config.force_authoring; let backoff_authoring_blocks = Some(BackoffAuthoringOnFinalizedHeadLagging::> { - unfinalized_slack: 6u32.into(), + unfinalized_slack: 6u32, ..Default::default() }); let name = config.network.node_name.clone(); From 850c8e75d5c5c492304d7f642d53d2139892dbbe Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Wed, 13 Nov 2024 12:16:27 -0500 Subject: [PATCH 29/29] remove duplicate code --- pallets/subtensor/tests/swap_hotkey.rs | 88 -------------------------- 1 file changed, 88 deletions(-) diff --git a/pallets/subtensor/tests/swap_hotkey.rs b/pallets/subtensor/tests/swap_hotkey.rs index 3507164c8..59d114cf1 100644 --- a/pallets/subtensor/tests/swap_hotkey.rs +++ b/pallets/subtensor/tests/swap_hotkey.rs @@ -1334,91 +1334,3 @@ fn test_swap_child_hotkey_childkey_maps() { ); }) } - -// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey -- test_hotkey_swap_stake_delta --exact --nocapture -#[test] -fn test_hotkey_swap_stake_delta() { - new_test_ext(1).execute_with(|| { - let old_hotkey = U256::from(3); - let new_hotkey = U256::from(4); - let coldkey = U256::from(7); - - let coldkeys = [U256::from(1), U256::from(2), U256::from(5)]; - - let mut weight = Weight::zero(); - - // Set up initial state - // Add stake delta for each coldkey and the old_hotkey - for &coldkey in coldkeys.iter() { - StakeDeltaSinceLastEmissionDrain::::insert( - old_hotkey, - coldkey, - (123 + coldkey.saturated_into::()), - ); - - StakingHotkeys::::insert(coldkey, vec![old_hotkey]); - } - - // Add stake delta for one coldkey and the new_hotkey - StakeDeltaSinceLastEmissionDrain::::insert(new_hotkey, coldkeys[0], 456); - // Add corresponding StakingHotkeys - StakingHotkeys::::insert(coldkeys[0], vec![old_hotkey, new_hotkey]); - - // Perform the swap - SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight); - - // Ensure the stake delta is correctly transferred for each coldkey - // -- coldkey[0] maintains its stake delta from the new_hotkey and the old_hotkey - assert_eq!( - StakeDeltaSinceLastEmissionDrain::::get(new_hotkey, coldkeys[0]), - 123 + coldkeys[0].saturated_into::() + 456 - ); - // -- coldkey[1..] maintains its stake delta from the old_hotkey - for &coldkey in coldkeys[1..].iter() { - assert_eq!( - StakeDeltaSinceLastEmissionDrain::::get(new_hotkey, coldkey), - 123 + coldkey.saturated_into::() - ); - assert!(!StakeDeltaSinceLastEmissionDrain::::contains_key( - old_hotkey, coldkey - )); - } - }); -} - -// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey -- test_swap_hotkey_with_pending_emissions --exact --nocapture -#[test] -fn test_swap_hotkey_with_pending_emissions() { - new_test_ext(1).execute_with(|| { - let old_hotkey = U256::from(1); - let new_hotkey = U256::from(2); - let coldkey = U256::from(3); - let netuid = 0u16; - let mut weight = Weight::zero(); - - let pending_emission = 123_456_789u64; - - // Set up initial state - add_network(netuid, 0, 1); - - // Set up pending emissions - PendingdHotkeyEmission::::insert(old_hotkey, pending_emission); - // Verify the pending emissions are set - assert_eq!( - PendingdHotkeyEmission::::get(old_hotkey), - pending_emission - ); - // Verify the new hotkey does not have any pending emissions - assert!(!PendingdHotkeyEmission::::contains_key(new_hotkey)); - - // Perform the swap - SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight); - - // Verify the pending emissions are transferred - assert_eq!( - PendingdHotkeyEmission::::get(new_hotkey), - pending_emission - ); - assert!(!PendingdHotkeyEmission::::contains_key(old_hotkey)); - }); -}