diff --git a/.gitignore b/.gitignore index f394de80c..5921b6b93 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,8 @@ **/*.lock +*.ipynb + # Generated by code coverage *.profraw *.profdata diff --git a/docs/delegate-info.json b/docs/delegate-info.json index bda41b48e..544c36e53 100644 --- a/docs/delegate-info.json +++ b/docs/delegate-info.json @@ -390,5 +390,19 @@ "url": "https://cortex.foundation/", "description": "Cortex Foundation is committed to advancing the integration of decentralized AI. Our validator is designed for transparency, reliability, and community engagement.", "signature": "7a6274ff6b0f7ddca97e37ef4a9b90781012ff3cf7baa3159f6feaafc43c557975aad324ea608d6b8abeb21f8f3ca2595e54b81a7564574d0242b803d969618a" + }, + { + "address":"5F27Eqz2PhyMtGMEce898x31DokNqRVxkm5AhDDe6rDGNvoY", + "name": "Love", + "url": "https://love.cosimo.fund", + "description": "Love validator exists to accelerate open source AI and be good stewards of the Bittensorr network", + "signature": "c221a3de3be031c149a7be912b3b75e0355605f041dc975153302b23b4d93e45e9cc7453532491e92076ccd333a4c1f95f4a2229aae8f4fcfb88e5dec3f14c87" + }, + { + "address": "5Hb63SvXBXqZ8zw6mwW1A39fHdqUrJvohXgepyhp2jgWedSB", + "name": "TAO Miner's Union", + "url": "https://minersunion.ai", + "description": "The first Bittensor validator that empowers you to choose which subnets to incentivize. Committed to transparency and integrity, we ensure fair and honest validation processes that contribute to the growth and strength of the network.", + "signature": "e8c68bc766a06f36c633e1f68d5aca4c4090a26e394372f64d5b00cc13621f361ec9df85fc9f0d247dbc1fe452bd53ffc0224dee2bc85c9d82cb250e4ac10984" } ] \ No newline at end of file diff --git a/pallets/subtensor/src/benchmarks.rs b/pallets/subtensor/src/benchmarks.rs index e9d5f804c..153d84b89 100644 --- a/pallets/subtensor/src/benchmarks.rs +++ b/pallets/subtensor/src/benchmarks.rs @@ -444,7 +444,7 @@ reveal_weights { let new_rate_limit: u64 = 100; }: sudo_set_tx_childkey_take_rate_limit(RawOrigin::Root, new_rate_limit) -benchmark_set_childkey_take { + benchmark_set_childkey_take { // Setup let netuid: u16 = 1; let tempo: u16 = 1; @@ -462,4 +462,62 @@ benchmark_set_childkey_take { Subtensor::::add_balance_to_coldkey_account(&coldkey, amount_to_be_staked); assert_ok!(Subtensor::::do_burned_registration(RawOrigin::Signed(coldkey.clone()).into(), netuid, hotkey.clone())); }: set_childkey_take(RawOrigin::Signed(coldkey), hotkey, netuid, take) + + swap_coldkey { + // Set up initial state + let old_coldkey: T::AccountId = account("old_coldkey", 0, 0); + let new_coldkey: T::AccountId = account("new_coldkey", 0, 0); + let hotkey1: T::AccountId = account("hotkey1", 0, 0); + let netuid = 1u16; + let stake_amount1 = 1000u64; + let stake_amount2 = 2000u64; + let swap_cost = Subtensor::::get_key_swap_cost(); + let free_balance_old = 12345u64 + swap_cost; + let tempo: u16 = 1; + + // Setup initial state + Subtensor::::init_new_network(netuid, tempo); + Subtensor::::set_network_registration_allowed(netuid, true); + Subtensor::::set_network_pow_registration_allowed(netuid, true); + + let block_number: u64 = Subtensor::::get_current_block_as_u64(); + let (nonce, work): (u64, Vec) = Subtensor::::create_work_for_block_number( + netuid, + block_number, + 3, + &hotkey1, + ); + + let _ = Subtensor::::register( + ::RuntimeOrigin::from(RawOrigin::Signed(old_coldkey.clone())), + netuid, + block_number, + nonce, + work.clone(), + hotkey1.clone(), + old_coldkey.clone(), + ); + + // Add balance to old coldkey + Subtensor::::add_balance_to_coldkey_account( + &old_coldkey, + stake_amount1 + stake_amount2 + free_balance_old, + ); + + // Insert an Identity + let name: Vec = b"The fourth Coolest Identity".to_vec(); + let identity: ChainIdentity = ChainIdentity { + name: name.clone(), + url: vec![], + image: vec![], + discord: vec![], + description: vec![], + additional: vec![], + }; + + Identities::::insert(&old_coldkey, identity); + + // Benchmark setup complete, now execute the extrinsic +}: swap_coldkey(RawOrigin::Signed(old_coldkey.clone()), old_coldkey.clone(), new_coldkey.clone()) + } diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index ce3f92879..6988d863a 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -672,9 +672,10 @@ mod dispatches { /// /// Weight is calculated based on the number of database reads and writes. #[pallet::call_index(71)] - #[pallet::weight((Weight::from_parts(1_940_000_000, 0) - .saturating_add(T::DbWeight::get().reads(272)) - .saturating_add(T::DbWeight::get().writes(527)), DispatchClass::Operational, Pays::No))] + #[pallet::weight((Weight::from_parts(127_713_000, 0) + .saturating_add(Weight::from_parts(0, 11645)) + .saturating_add(T::DbWeight::get().reads(18)) + .saturating_add(T::DbWeight::get().writes(12)), DispatchClass::Operational, Pays::No))] pub fn swap_coldkey( origin: OriginFor, old_coldkey: T::AccountId, diff --git a/pallets/subtensor/src/swap/swap_coldkey.rs b/pallets/subtensor/src/swap/swap_coldkey.rs index 498549e5c..bcbd2a330 100644 --- a/pallets/subtensor/src/swap/swap_coldkey.rs +++ b/pallets/subtensor/src/swap/swap_coldkey.rs @@ -49,37 +49,42 @@ impl Pallet { ); weight = weight.saturating_add(T::DbWeight::get().reads(1)); - // 5. Calculate the swap cost and ensure sufficient balance + // 5. Swap the identity if the old coldkey has one + if let Some(identity) = Identities::::take(old_coldkey) { + Identities::::insert(new_coldkey, identity); + } + + // 6. Calculate the swap cost and ensure sufficient balance let swap_cost = Self::get_key_swap_cost(); ensure!( Self::can_remove_balance_from_coldkey_account(old_coldkey, swap_cost), Error::::NotEnoughBalanceToPaySwapColdKey ); - // 6. Remove and burn the swap cost from the old coldkey's account + // 7. Remove and burn the swap cost from the old coldkey's account let actual_burn_amount = Self::remove_balance_from_coldkey_account(old_coldkey, swap_cost)?; Self::burn_tokens(actual_burn_amount); - // 7. Update the weight for the balance operations + // 8. Update the weight for the balance operations weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); - // 8. Perform the actual coldkey swap + // 9. Perform the actual coldkey swap let _ = Self::perform_swap_coldkey(old_coldkey, new_coldkey, &mut weight); - // 9. Update the last transaction block for the new coldkey + // 10. Update the last transaction block for the new coldkey Self::set_last_tx_block(new_coldkey, Self::get_current_block_as_u64()); weight.saturating_accrue(T::DbWeight::get().writes(1)); - // 10. Remove the coldkey swap scheduled record + // 11. Remove the coldkey swap scheduled record ColdkeySwapScheduled::::remove(old_coldkey); - // 11. Emit the ColdkeySwapped event + // 12. Emit the ColdkeySwapped event Self::deposit_event(Event::ColdkeySwapped { old_coldkey: old_coldkey.clone(), new_coldkey: new_coldkey.clone(), }); - // 11. Return the result with the updated weight + // 12. Return the result with the updated weight Ok(Some(weight).into()) } diff --git a/pallets/subtensor/tests/swap_coldkey.rs b/pallets/subtensor/tests/swap_coldkey.rs index ec030ebee..0fe601cab 100644 --- a/pallets/subtensor/tests/swap_coldkey.rs +++ b/pallets/subtensor/tests/swap_coldkey.rs @@ -529,6 +529,22 @@ fn test_do_swap_coldkey_success() { stake_amount2 )); + // Insert an Identity + let name: Vec = b"The fourth Coolest Identity".to_vec(); + let identity: ChainIdentity = ChainIdentity { + name: name.clone(), + url: vec![], + image: vec![], + discord: vec![], + description: vec![], + additional: vec![], + }; + + Identities::::insert(old_coldkey, identity.clone()); + + assert!(Identities::::get(old_coldkey).is_some()); + assert!(Identities::::get(new_coldkey).is_none()); + // Log state after adding stake log::info!( "Total stake after adding: {}", @@ -601,6 +617,14 @@ fn test_do_swap_coldkey_success() { "Total stake changed unexpectedly" ); + // Verify identities were swapped + assert!(Identities::::get(old_coldkey).is_none()); + assert!(Identities::::get(new_coldkey).is_some()); + assert_eq!( + Identities::::get(new_coldkey).expect("Expected an Identity"), + identity + ); + // Verify event emission System::assert_last_event( Event::ColdkeySwapped { @@ -1480,3 +1504,127 @@ fn test_schedule_swap_coldkey_with_pending_swap() { ); }); } + +#[test] +fn test_coldkey_swap_delegate_identity_updated() { + new_test_ext(1).execute_with(|| { + let old_coldkey = U256::from(1); + let new_coldkey = U256::from(2); + + let netuid = 1; + let burn_cost = 10; + let tempo = 1; + + SubtensorModule::set_burn(netuid, burn_cost); + add_network(netuid, tempo, 0); + + SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, 100_000_000_000); + + assert_ok!(SubtensorModule::burned_register( + <::RuntimeOrigin>::signed(old_coldkey), + netuid, + old_coldkey + )); + + let name: Vec = b"The Third Coolest Identity".to_vec(); + let identity: ChainIdentity = ChainIdentity { + name: name.clone(), + url: vec![], + image: vec![], + discord: vec![], + description: vec![], + additional: vec![], + }; + + Identities::::insert(old_coldkey, identity.clone()); + + assert!(Identities::::get(old_coldkey).is_some()); + assert!(Identities::::get(new_coldkey).is_none()); + + assert_ok!(SubtensorModule::do_swap_coldkey(&old_coldkey, &new_coldkey)); + + assert!(Identities::::get(old_coldkey).is_none()); + assert!(Identities::::get(new_coldkey).is_some()); + assert_eq!( + Identities::::get(new_coldkey).expect("Expected an Identity"), + identity + ); + }); +} + +#[test] +fn test_coldkey_swap_no_identity_no_changes() { + new_test_ext(1).execute_with(|| { + let old_coldkey = U256::from(1); + let new_coldkey = U256::from(2); + + let netuid = 1; + let burn_cost = 10; + let tempo = 1; + + SubtensorModule::set_burn(netuid, burn_cost); + add_network(netuid, tempo, 0); + + SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, 100_000_000_000); + + assert_ok!(SubtensorModule::burned_register( + <::RuntimeOrigin>::signed(old_coldkey), + netuid, + old_coldkey + )); + + // Ensure the old coldkey does not have an identity before the swap + assert!(Identities::::get(old_coldkey).is_none()); + + // Perform the coldkey swap + assert_ok!(SubtensorModule::do_swap_coldkey(&old_coldkey, &new_coldkey)); + + // Ensure no identities have been changed + assert!(Identities::::get(old_coldkey).is_none()); + assert!(Identities::::get(new_coldkey).is_none()); + }); +} + +#[test] +fn test_coldkey_swap_no_identity_no_changes_newcoldkey_exists() { + new_test_ext(1).execute_with(|| { + let old_coldkey = U256::from(3); + let new_coldkey = U256::from(4); + + let netuid = 1; + let burn_cost = 10; + let tempo = 1; + + SubtensorModule::set_burn(netuid, burn_cost); + add_network(netuid, tempo, 0); + SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, 100_000_000_000); + + assert_ok!(SubtensorModule::burned_register( + <::RuntimeOrigin>::signed(old_coldkey), + netuid, + old_coldkey + )); + + let name: Vec = b"The Coolest Identity".to_vec(); + let identity: ChainIdentity = ChainIdentity { + name: name.clone(), + url: vec![], + image: vec![], + discord: vec![], + description: vec![], + additional: vec![], + }; + + Identities::::insert(new_coldkey, identity.clone()); + // Ensure the new coldkey does have an identity before the swap + assert!(Identities::::get(new_coldkey).is_some()); + assert!(Identities::::get(old_coldkey).is_none()); + + // Perform the coldkey swap + assert_ok!(SubtensorModule::do_swap_coldkey(&old_coldkey, &new_coldkey)); + + // Ensure no identities have been changed + assert!(Identities::::get(old_coldkey).is_none()); + assert!(Identities::::get(new_coldkey).is_some()); + }); +} diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index e6c0e9d32..9ad0624d0 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -142,7 +142,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: 193, + spec_version: 194, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1,