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/Cargo.lock b/Cargo.lock index 698607e7c..ee0933379 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9193,13 +9193,10 @@ version = "0.1.0" dependencies = [ "node-subtensor", "node-subtensor-runtime", - "pallet-commitments", - "pallet-subtensor", "proc-macro2", "quote", "rayon", "subtensor-linting", - "subtensor-macros", "syn 2.0.71", "walkdir", ] diff --git a/Cargo.toml b/Cargo.toml index b8fe8c4d4..e3c2814ff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,10 +11,7 @@ repository = "https://github.com/opentensor/subtensor" [dependencies] node-subtensor = { path = "node", version = "4.0.0-dev" } -pallet-commitments = { path = "pallets/commitments", version = "4.0.0-dev" } -pallet-subtensor = { path = "pallets/subtensor", version = "4.0.0-dev" } node-subtensor-runtime = { path = "runtime", version = "4.0.0-dev" } -subtensor-macros = { path = "support/macros", version = "0.1.0" } [build-dependencies] subtensor-linting = { path = "support/linting", version = "0.1.0" } @@ -167,3 +164,8 @@ opt-level = 3 inherits = "release" lto = true codegen-units = 1 + +[features] +default = [] +try-runtime = ["node-subtensor/try-runtime", "node-subtensor-runtime/try-runtime"] +runtime-benchmarks = ["node-subtensor/runtime-benchmarks", "node-subtensor-runtime/runtime-benchmarks"] 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/admin-utils/src/lib.rs b/pallets/admin-utils/src/lib.rs index baf711b85..3e06b822e 100644 --- a/pallets/admin-utils/src/lib.rs +++ b/pallets/admin-utils/src/lib.rs @@ -4,6 +4,7 @@ pub use pallet::*; pub mod weights; pub use weights::WeightInfo; +use frame_system::pallet_prelude::BlockNumberFor; use sp_runtime::{traits::Member, RuntimeAppPublic}; mod benchmarking; @@ -1128,6 +1129,73 @@ pub mod pallet { Ok(()) } + + /// Sets the duration of the coldkey swap schedule. + /// + /// This extrinsic allows the root account to set the duration for the coldkey swap schedule. + /// The coldkey swap schedule determines how long it takes for a coldkey swap operation to complete. + /// + /// # Arguments + /// * `origin` - The origin of the call, which must be the root account. + /// * `duration` - The new duration for the coldkey swap schedule, in number of blocks. + /// + /// # Errors + /// * `BadOrigin` - If the caller is not the root account. + /// + /// # Weight + /// Weight is handled by the `#[pallet::weight]` attribute. + #[pallet::call_index(54)] + #[pallet::weight((0, DispatchClass::Operational, Pays::No))] + pub fn sudo_set_coldkey_swap_schedule_duration( + origin: OriginFor, + duration: BlockNumberFor, + ) -> DispatchResult { + // Ensure the call is made by the root account + ensure_root(origin)?; + + // Set the new duration of schedule coldkey swap + pallet_subtensor::Pallet::::set_coldkey_swap_schedule_duration(duration); + + // Log the change + log::trace!("ColdkeySwapScheduleDurationSet( duration: {:?} )", duration); + + Ok(()) + } + + /// Sets the duration of the dissolve network schedule. + /// + /// This extrinsic allows the root account to set the duration for the dissolve network schedule. + /// The dissolve network schedule determines how long it takes for a network dissolution operation to complete. + /// + /// # Arguments + /// * `origin` - The origin of the call, which must be the root account. + /// * `duration` - The new duration for the dissolve network schedule, in number of blocks. + /// + /// # Errors + /// * `BadOrigin` - If the caller is not the root account. + /// + /// # Weight + /// Weight is handled by the `#[pallet::weight]` attribute. + #[pallet::call_index(55)] + #[pallet::weight((0, DispatchClass::Operational, Pays::No))] + pub fn sudo_set_dissolve_network_schedule_duration( + origin: OriginFor, + duration: BlockNumberFor, + ) -> DispatchResult { + // Ensure the call is made by the root account + ensure_root(origin)?; + + // Set the duration of schedule dissolve network + pallet_subtensor::Pallet::::set_dissolve_network_schedule_duration(duration); + + // Log the change + log::trace!( + "DissolveNetworkScheduleDurationSet( duration: {:?} )", + duration + ); + + Ok(()) + } } } diff --git a/pallets/admin-utils/tests/tests.rs b/pallets/admin-utils/tests/tests.rs index af3bf66d7..8ab85f177 100644 --- a/pallets/admin-utils/tests/tests.rs +++ b/pallets/admin-utils/tests/tests.rs @@ -1,6 +1,6 @@ use frame_support::sp_runtime::DispatchError; use frame_support::{ - assert_err, assert_ok, + assert_err, assert_noop, assert_ok, dispatch::{DispatchClass, GetDispatchInfo, Pays}, }; use frame_system::Config; @@ -1361,3 +1361,77 @@ fn test_sudo_get_set_alpha() { )); }); } + +#[test] +fn test_sudo_set_coldkey_swap_schedule_duration() { + new_test_ext().execute_with(|| { + // Arrange + let root = RuntimeOrigin::root(); + let non_root = RuntimeOrigin::signed(U256::from(1)); + let new_duration = 100u32.into(); + + // Act & Assert: Non-root account should fail + assert_noop!( + AdminUtils::sudo_set_coldkey_swap_schedule_duration(non_root, new_duration), + DispatchError::BadOrigin + ); + + // Act: Root account should succeed + assert_ok!(AdminUtils::sudo_set_coldkey_swap_schedule_duration( + root.clone(), + new_duration + )); + + // Assert: Check if the duration was actually set + assert_eq!( + pallet_subtensor::ColdkeySwapScheduleDuration::::get(), + new_duration + ); + + // Act & Assert: Setting the same value again should succeed (idempotent operation) + assert_ok!(AdminUtils::sudo_set_coldkey_swap_schedule_duration( + root, + new_duration + )); + + // You might want to check for events here if your pallet emits them + System::assert_last_event(Event::ColdkeySwapScheduleDurationSet(new_duration).into()); + }); +} + +#[test] +fn test_sudo_set_dissolve_network_schedule_duration() { + new_test_ext().execute_with(|| { + // Arrange + let root = RuntimeOrigin::root(); + let non_root = RuntimeOrigin::signed(U256::from(1)); + let new_duration = 200u32.into(); + + // Act & Assert: Non-root account should fail + assert_noop!( + AdminUtils::sudo_set_dissolve_network_schedule_duration(non_root, new_duration), + DispatchError::BadOrigin + ); + + // Act: Root account should succeed + assert_ok!(AdminUtils::sudo_set_dissolve_network_schedule_duration( + root.clone(), + new_duration + )); + + // Assert: Check if the duration was actually set + assert_eq!( + pallet_subtensor::DissolveNetworkScheduleDuration::::get(), + new_duration + ); + + // Act & Assert: Setting the same value again should succeed (idempotent operation) + assert_ok!(AdminUtils::sudo_set_dissolve_network_schedule_duration( + root, + new_duration + )); + + // You might want to check for events here if your pallet emits them + System::assert_last_event(Event::DissolveNetworkScheduleDurationSet(new_duration).into()); + }); +} diff --git a/pallets/subtensor/rpc/src/lib.rs b/pallets/subtensor/rpc/src/lib.rs index 2f71e9c21..2445a5eda 100644 --- a/pallets/subtensor/rpc/src/lib.rs +++ b/pallets/subtensor/rpc/src/lib.rs @@ -46,6 +46,10 @@ pub trait SubtensorCustomApi { fn get_subnet_info(&self, netuid: u16, at: Option) -> RpcResult>; #[method(name = "subnetInfo_getSubnetsInfo")] fn get_subnets_info(&self, at: Option) -> RpcResult>; + #[method(name = "subnetInfo_getSubnetInfo_v2")] + fn get_subnet_info_v2(&self, netuid: u16, at: Option) -> RpcResult>; + #[method(name = "subnetInfo_getSubnetsInf_v2")] + fn get_subnets_info_v2(&self, at: Option) -> RpcResult>; #[method(name = "subnetInfo_getSubnetHyperparams")] fn get_subnet_hyperparams(&self, netuid: u16, at: Option) -> RpcResult>; @@ -215,6 +219,26 @@ where .map_err(|e| Error::RuntimeError(format!("Unable to get subnets info: {:?}", e)).into()) } + fn get_subnet_info_v2( + &self, + netuid: u16, + at: Option<::Hash>, + ) -> RpcResult> { + let api = self.client.runtime_api(); + let at = at.unwrap_or_else(|| self.client.info().best_hash); + + api.get_subnet_info_v2(at, netuid) + .map_err(|e| Error::RuntimeError(format!("Unable to get subnet info: {:?}", e)).into()) + } + + fn get_subnets_info_v2(&self, at: Option<::Hash>) -> RpcResult> { + let api = self.client.runtime_api(); + let at = at.unwrap_or_else(|| self.client.info().best_hash); + + api.get_subnets_info_v2(at) + .map_err(|e| Error::RuntimeError(format!("Unable to get subnets info: {:?}", e)).into()) + } + fn get_network_lock_cost(&self, at: Option<::Hash>) -> RpcResult { let api = self.client.runtime_api(); let at = at.unwrap_or_else(|| self.client.info().best_hash); diff --git a/pallets/subtensor/runtime-api/src/lib.rs b/pallets/subtensor/runtime-api/src/lib.rs index 9095ad54a..ca43384b8 100644 --- a/pallets/subtensor/runtime-api/src/lib.rs +++ b/pallets/subtensor/runtime-api/src/lib.rs @@ -21,6 +21,8 @@ sp_api::decl_runtime_apis! { pub trait SubnetInfoRuntimeApi { fn get_subnet_info(netuid: u16) -> Vec; fn get_subnets_info() -> Vec; + fn get_subnet_info_v2(netuid: u16) -> Vec; + fn get_subnets_info_v2() -> Vec; fn get_subnet_hyperparams(netuid: u16) -> Vec; } diff --git a/pallets/subtensor/src/benchmarks.rs b/pallets/subtensor/src/benchmarks.rs index e9d5f804c..4915bb3ac 100644 --- a/pallets/subtensor/src/benchmarks.rs +++ b/pallets/subtensor/src/benchmarks.rs @@ -312,7 +312,8 @@ benchmarks! { let amount_to_be_staked = 100_000_000_000_000u64; Subtensor::::add_balance_to_coldkey_account(&coldkey.clone(), amount_to_be_staked); assert_ok!(Subtensor::::register_network(RawOrigin::Signed(coldkey.clone()).into())); - }: dissolve_network(RawOrigin::Signed(coldkey), 1) + }: dissolve_network(RawOrigin::Root, coldkey.clone(), 1) + // swap_hotkey { // let seed: u32 = 1; @@ -444,7 +445,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 +463,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::Root, old_coldkey.clone(), new_coldkey.clone()) + } diff --git a/pallets/subtensor/src/coinbase/root.rs b/pallets/subtensor/src/coinbase/root.rs index 39f745b93..067d5855b 100644 --- a/pallets/subtensor/src/coinbase/root.rs +++ b/pallets/subtensor/src/coinbase/root.rs @@ -891,20 +891,27 @@ impl Pallet { .into()) } - /// Facilitates user registration of a new subnetwork. + /// Facilitates user registration of a new subnetwork with subnet identity. /// /// # Args: - /// * 'origin': ('T::RuntimeOrigin'): The calling origin. Must be signed. + /// * `origin` (`T::RuntimeOrigin`): The calling origin. Must be signed. + /// * `identity` (`Option`): Optional identity to be associated with the new subnetwork. /// - /// # Event: - /// * 'NetworkAdded': Emitted when a new network is successfully added. + /// # Events: + /// * `NetworkAdded(netuid, modality)`: Emitted when a new network is successfully added. + /// * `SubnetIdentitySet(netuid)`: Emitted when a custom identity is set for a new subnetwork. + /// * `NetworkRemoved(netuid)`: Emitted when an existing network is removed to make room for the new one. + /// * `SubnetIdentityRemoved(netuid)`: Emitted when the identity of a removed network is also deleted. /// /// # Raises: /// * 'TxRateLimitExceeded': If the rate limit for network registration is exceeded. /// * 'NotEnoughBalanceToStake': If there isn't enough balance to stake for network registration. /// * 'BalanceWithdrawalError': If an error occurs during balance withdrawal for network registration. /// - pub fn user_add_network(origin: T::RuntimeOrigin) -> dispatch::DispatchResult { + pub fn user_add_network( + origin: T::RuntimeOrigin, + identity: Option, + ) -> dispatch::DispatchResult { // --- 0. Ensure the caller is a signed user. let coldkey = ensure_signed(origin)?; @@ -948,6 +955,11 @@ impl Pallet { Self::remove_network(netuid_to_prune); log::debug!("remove_network: {:?}", netuid_to_prune,); Self::deposit_event(Event::NetworkRemoved(netuid_to_prune)); + + if SubnetIdentities::::take(netuid_to_prune).is_some() { + Self::deposit_event(Event::SubnetIdentityRemoved(netuid_to_prune)); + } + netuid_to_prune } }; @@ -961,13 +973,24 @@ impl Pallet { Self::init_new_network(netuid_to_register, 360); log::debug!("init_new_network: {:?}", netuid_to_register,); - // --- 7. Set netuid storage. + // --- 7. Add the identity if it exists + if let Some(identity_value) = identity { + ensure!( + Self::is_valid_subnet_identity(&identity_value), + Error::::InvalidIdentity + ); + + SubnetIdentities::::insert(netuid_to_register, identity_value); + Self::deposit_event(Event::SubnetIdentitySet(netuid_to_register)); + } + + // --- 8. Set netuid storage. let current_block_number: u64 = Self::get_current_block_as_u64(); NetworkLastRegistered::::set(current_block_number); NetworkRegisteredAt::::insert(netuid_to_register, current_block_number); SubnetOwner::::insert(netuid_to_register, coldkey); - // --- 8. Emit the NetworkAdded event. + // --- 9. Emit the NetworkAdded event. log::debug!( "NetworkAdded( netuid:{:?}, modality:{:?} )", netuid_to_register, @@ -975,7 +998,7 @@ impl Pallet { ); Self::deposit_event(Event::NetworkAdded(netuid_to_register, 0)); - // --- 9. Return success. + // --- 10. Return success. Ok(()) } @@ -992,30 +1015,32 @@ impl Pallet { /// * 'SubNetworkDoesNotExist': If the specified network does not exist. /// * 'NotSubnetOwner': If the caller does not own the specified subnet. /// - pub fn user_remove_network(origin: T::RuntimeOrigin, netuid: u16) -> dispatch::DispatchResult { - // --- 1. Ensure the function caller is a signed user. - let coldkey = ensure_signed(origin)?; - - // --- 2. Ensure this subnet exists. + pub fn user_remove_network(coldkey: T::AccountId, netuid: u16) -> dispatch::DispatchResult { + // --- 1. Ensure this subnet exists. ensure!( Self::if_subnet_exist(netuid), Error::::SubNetworkDoesNotExist ); - // --- 3. Ensure the caller owns this subnet. + // --- 2. Ensure the caller owns this subnet. ensure!( SubnetOwner::::get(netuid) == coldkey, Error::::NotSubnetOwner ); - // --- 4. Explicitly erase the network and all its parameters. + // --- 4. Remove the subnet identity if it exists. + if SubnetIdentities::::take(netuid).is_some() { + Self::deposit_event(Event::SubnetIdentityRemoved(netuid)); + } + + // --- 5. Explicitly erase the network and all its parameters. Self::remove_network(netuid); - // --- 5. Emit the NetworkRemoved event. + // --- 6. Emit the NetworkRemoved event. log::debug!("NetworkRemoved( netuid:{:?} )", netuid); Self::deposit_event(Event::NetworkRemoved(netuid)); - // --- 6. Return success. + // --- 7. Return success. Ok(()) } @@ -1101,8 +1126,8 @@ impl Pallet { /// Removes a network (identified by netuid) and all associated parameters. /// /// This function is responsible for cleaning up all the data associated with a network. - /// It ensures that all the storage values related to the network are removed, and any - /// reserved balance is returned to the network owner. + /// It ensures that all the storage values related to the network are removed, any + /// reserved balance is returned to the network owner, and the subnet identity is removed if it exists. /// /// # Args: /// * 'netuid': ('u16'): The unique identifier of the network to be removed. @@ -1110,11 +1135,10 @@ impl Pallet { /// # Note: /// This function does not emit any events, nor does it raise any errors. It silently /// returns if any internal checks fail. - /// pub fn remove_network(netuid: u16) { // --- 1. Return balance to subnet owner. - let owner_coldkey = SubnetOwner::::get(netuid); - let reserved_amount = Self::get_subnet_locked_balance(netuid); + let owner_coldkey: T::AccountId = SubnetOwner::::get(netuid); + let reserved_amount: u64 = Self::get_subnet_locked_balance(netuid); // --- 2. Remove network count. SubnetworkN::::remove(netuid); @@ -1125,13 +1149,13 @@ impl Pallet { // --- 4. Remove netuid from added networks. NetworksAdded::::remove(netuid); - // --- 6. Decrement the network counter. - TotalNetworks::::mutate(|n| *n = n.saturating_sub(1)); + // --- 5. Decrement the network counter. + TotalNetworks::::mutate(|n: &mut u16| *n = n.saturating_sub(1)); - // --- 7. Remove various network-related storages. + // --- 6. Remove various network-related storages. NetworkRegisteredAt::::remove(netuid); - // --- 8. Remove incentive mechanism memory. + // --- 7. Remove incentive mechanism memory. let _ = Uids::::clear_prefix(netuid, u32::MAX, None); let _ = Keys::::clear_prefix(netuid, u32::MAX, None); let _ = Bonds::::clear_prefix(netuid, u32::MAX, None); @@ -1146,7 +1170,7 @@ impl Pallet { ) { // Create a new vector to hold modified weights. - let mut modified_weights = weights_i.clone(); + let mut modified_weights: Vec<(u16, u16)> = weights_i.clone(); // Iterate over each weight entry to potentially update it. for (subnet_id, weight) in modified_weights.iter_mut() { if subnet_id == &netuid { @@ -1188,6 +1212,12 @@ impl Pallet { Self::add_balance_to_coldkey_account(&owner_coldkey, reserved_amount); Self::set_subnet_locked_balance(netuid, 0); SubnetOwner::::remove(netuid); + + // --- 13. Remove subnet identity if it exists. + if SubnetIdentities::::contains_key(netuid) { + SubnetIdentities::::remove(netuid); + Self::deposit_event(Event::SubnetIdentityRemoved(netuid)); + } } #[allow(clippy::arithmetic_side_effects)] diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 02fa4d7d6..2985736c8 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -148,10 +148,10 @@ pub mod pallet { pub ip_type: u8, } - /// Struct for Prometheus. + /// Struct for ChainIdentities. pub type ChainIdentityOf = ChainIdentity; - /// Data structure for Prometheus information. + /// Data structure for Chain Identities. #[crate::freeze_struct("bbfd00438dbe2b58")] #[derive(Encode, Decode, Default, TypeInfo, Clone, PartialEq, Eq, Debug)] pub struct ChainIdentity { @@ -169,6 +169,19 @@ pub mod pallet { pub additional: Vec, } + /// Struct for SubnetIdentities. + pub type SubnetIdentityOf = SubnetIdentity; + /// Data structure for Subnet Identities + #[crate::freeze_struct("f448dc3dad763108")] + #[derive(Encode, Decode, Default, TypeInfo, Clone, PartialEq, Eq, Debug)] + pub struct SubnetIdentity { + /// The name of the subnet + pub subnet_name: Vec, + /// The github repository associated with the chain identity + pub github_repo: Vec, + /// The subnet's contact + pub subnet_contact: Vec, + } /// ============================ /// ==== Staking + Accounts ==== /// ============================ @@ -1164,6 +1177,10 @@ pub mod pallet { pub type Identities = StorageMap<_, Blake2_128Concat, T::AccountId, ChainIdentityOf, OptionQuery>; + #[pallet::storage] // --- MAP ( netuid ) --> identity + pub type SubnetIdentities = + StorageMap<_, Blake2_128Concat, u16, SubnetIdentityOf, OptionQuery>; + /// ================================= /// ==== Axon / Promo Endpoints ===== /// ================================= diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index ce3f92879..a97e4494d 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -672,16 +672,17 @@ 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, new_coldkey: T::AccountId, ) -> DispatchResultWithPostInfo { // Ensure it's called with root privileges (scheduler has root privileges) - ensure_root(origin.clone())?; + ensure_root(origin)?; log::info!("swap_coldkey: {:?} -> {:?}", old_coldkey, new_coldkey); Self::do_swap_coldkey(&old_coldkey, &new_coldkey) @@ -901,7 +902,7 @@ mod dispatches { .saturating_add(T::DbWeight::get().reads(16)) .saturating_add(T::DbWeight::get().writes(30)), DispatchClass::Operational, Pays::No))] pub fn register_network(origin: OriginFor) -> DispatchResult { - Self::user_add_network(origin) + Self::user_add_network(origin, None) } /// Facility extrinsic for user to get taken from faucet @@ -930,8 +931,13 @@ mod dispatches { #[pallet::weight((Weight::from_parts(119_000_000, 0) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(31)), DispatchClass::Operational, Pays::No))] - pub fn dissolve_network(origin: OriginFor, netuid: u16) -> DispatchResult { - Self::user_remove_network(origin, netuid) + pub fn dissolve_network( + origin: OriginFor, + coldkey: T::AccountId, + netuid: u16, + ) -> DispatchResult { + ensure_root(origin)?; + Self::user_remove_network(coldkey, netuid) } /// Set a single child for a given hotkey on a specified network. @@ -1100,7 +1106,10 @@ mod dispatches { let duration: BlockNumberFor = DissolveNetworkScheduleDuration::::get(); let when: BlockNumberFor = current_block.saturating_add(duration); - let call = Call::::dissolve_network { netuid }; + let call = Call::::dissolve_network { + coldkey: who.clone(), + netuid, + }; let bound_call = T::Preimages::bound(LocalCallOf::::from(call.clone())) .map_err(|_| Error::::FailedToSchedule)?; @@ -1109,7 +1118,7 @@ mod dispatches { DispatchTime::At(when), None, 63, - frame_system::RawOrigin::Signed(who.clone()).into(), + frame_system::RawOrigin::Root.into(), bound_call, ) .map_err(|_| Error::::FailedToSchedule)?; @@ -1159,5 +1168,47 @@ mod dispatches { ) -> DispatchResult { Self::do_set_identity(origin, name, url, image, discord, description, additional) } + + /// ---- Set the identity information for a subnet. + /// # Args: + /// * `origin` - (::Origin): + /// - The signature of the calling coldkey, which must be the owner of the subnet. + /// + /// * `netuid` (u16): + /// - The unique network identifier of the subnet. + /// + /// * `subnet_name` (Vec): + /// - The name of the subnet. + /// + /// * `github_repo` (Vec): + /// - The GitHub repository associated with the subnet identity. + /// + /// * `subnet_contact` (Vec): + /// - The contact information for the subnet. + #[pallet::call_index(78)] + #[pallet::weight((Weight::from_parts(45_000_000, 0) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(1)), DispatchClass::Normal, Pays::Yes))] + pub fn set_subnet_identity( + origin: OriginFor, + netuid: u16, + subnet_name: Vec, + github_repo: Vec, + subnet_contact: Vec, + ) -> DispatchResult { + Self::do_set_subnet_identity(origin, netuid, subnet_name, github_repo, subnet_contact) + } + + /// User register a new subnetwork + #[pallet::call_index(79)] + #[pallet::weight((Weight::from_parts(157_000_000, 0) + .saturating_add(T::DbWeight::get().reads(16)) + .saturating_add(T::DbWeight::get().writes(30)), DispatchClass::Operational, Pays::No))] + pub fn register_network_with_identity( + origin: OriginFor, + identity: Option, + ) -> DispatchResult { + Self::user_add_network(origin, identity) + } } } diff --git a/pallets/subtensor/src/macros/events.rs b/pallets/subtensor/src/macros/events.rs index b5e34b842..ac6b69012 100644 --- a/pallets/subtensor/src/macros/events.rs +++ b/pallets/subtensor/src/macros/events.rs @@ -187,6 +187,10 @@ mod events { NetworkMaxStakeSet(u16, u64), /// The identity of a coldkey has been set ChainIdentitySet(T::AccountId), + /// The identity of a subnet has been set + SubnetIdentitySet(u16), + /// The identity of a subnet has been removed + SubnetIdentityRemoved(u16), /// A dissolve network extrinsic scheduled. DissolveNetworkScheduled { /// The account ID schedule the dissolve network extrisnic @@ -196,5 +200,9 @@ mod events { /// extrinsic execution block number execution_block: BlockNumberFor, }, + /// The duration of schedule coldkey swap has been set + ColdkeySwapScheduleDurationSet(BlockNumberFor), + /// The duration of dissolve network has been set + DissolveNetworkScheduleDurationSet(BlockNumberFor), } } diff --git a/pallets/subtensor/src/rpc_info/subnet_info.rs b/pallets/subtensor/src/rpc_info/subnet_info.rs index 4e9e756a0..9b22e0401 100644 --- a/pallets/subtensor/src/rpc_info/subnet_info.rs +++ b/pallets/subtensor/src/rpc_info/subnet_info.rs @@ -27,6 +27,30 @@ pub struct SubnetInfo { owner: T::AccountId, } +#[freeze_struct("65f931972fa13222")] +#[derive(Decode, Encode, PartialEq, Eq, Clone, Debug)] +pub struct SubnetInfov2 { + netuid: Compact, + rho: Compact, + kappa: Compact, + difficulty: Compact, + immunity_period: Compact, + max_allowed_validators: Compact, + min_allowed_weights: Compact, + max_weights_limit: Compact, + scaling_law_power: Compact, + subnetwork_n: Compact, + max_allowed_uids: Compact, + blocks_since_last_step: Compact, + tempo: Compact, + network_modality: Compact, + network_connect: Vec<[u16; 2]>, + emission_values: Compact, + burn: Compact, + owner: T::AccountId, + identity: Option, +} + #[freeze_struct("55b472510f10e76a")] #[derive(Decode, Encode, PartialEq, Eq, Clone, Debug)] pub struct SubnetHyperparams { @@ -80,7 +104,6 @@ impl Pallet { let network_modality = >::get(netuid); let emission_values = Self::get_emission_value(netuid); let burn: Compact = Self::get_burn_as_u64(netuid).into(); - // DEPRECATED let network_connect: Vec<[u16; 2]> = Vec::<[u16; 2]>::new(); // DEPRECATED for ( _netuid_, con_req) in < NetworkConnect as IterableStorageDoubleMap >::iter_prefix(netuid) { @@ -131,6 +154,77 @@ impl Pallet { subnets_info } + pub fn get_subnet_info_v2(netuid: u16) -> Option> { + if !Self::if_subnet_exist(netuid) { + return None; + } + + let rho = Self::get_rho(netuid); + let kappa = Self::get_kappa(netuid); + let difficulty: Compact = Self::get_difficulty_as_u64(netuid).into(); + let immunity_period = Self::get_immunity_period(netuid); + let max_allowed_validators = Self::get_max_allowed_validators(netuid); + let min_allowed_weights = Self::get_min_allowed_weights(netuid); + let max_weights_limit = Self::get_max_weight_limit(netuid); + let scaling_law_power = Self::get_scaling_law_power(netuid); + let subnetwork_n = Self::get_subnetwork_n(netuid); + let max_allowed_uids = Self::get_max_allowed_uids(netuid); + let blocks_since_last_step = Self::get_blocks_since_last_step(netuid); + let tempo = Self::get_tempo(netuid); + let network_modality = >::get(netuid); + let emission_values = Self::get_emission_value(netuid); + let burn: Compact = Self::get_burn_as_u64(netuid).into(); + let identity: Option = SubnetIdentities::::get(netuid); + + // DEPRECATED + let network_connect: Vec<[u16; 2]> = Vec::<[u16; 2]>::new(); + // DEPRECATED for ( _netuid_, con_req) in < NetworkConnect as IterableStorageDoubleMap >::iter_prefix(netuid) { + // network_connect.push([_netuid_, con_req]); + // } + + Some(SubnetInfov2 { + rho: rho.into(), + kappa: kappa.into(), + difficulty, + immunity_period: immunity_period.into(), + netuid: netuid.into(), + max_allowed_validators: max_allowed_validators.into(), + min_allowed_weights: min_allowed_weights.into(), + max_weights_limit: max_weights_limit.into(), + scaling_law_power: scaling_law_power.into(), + subnetwork_n: subnetwork_n.into(), + max_allowed_uids: max_allowed_uids.into(), + blocks_since_last_step: blocks_since_last_step.into(), + tempo: tempo.into(), + network_modality: network_modality.into(), + network_connect, + emission_values: emission_values.into(), + burn, + owner: Self::get_subnet_owner(netuid), + identity, + }) + } + pub fn get_subnets_info_v2() -> Vec>> { + let mut subnet_netuids = Vec::::new(); + let mut max_netuid: u16 = 0; + for (netuid, added) in as IterableStorageMap>::iter() { + if added { + subnet_netuids.push(netuid); + if netuid > max_netuid { + max_netuid = netuid; + } + } + } + + let mut subnets_info = Vec::>>::new(); + for netuid_ in 0..=max_netuid { + if subnet_netuids.contains(&netuid_) { + subnets_info.push(Self::get_subnet_info(netuid_)); + } + } + + subnets_info + } pub fn get_subnet_hyperparams(netuid: u16) -> Option { if !Self::if_subnet_exist(netuid) { return None; 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/src/utils/identity.rs b/pallets/subtensor/src/utils/identity.rs index 460bcb838..7babb04f4 100644 --- a/pallets/subtensor/src/utils/identity.rs +++ b/pallets/subtensor/src/utils/identity.rs @@ -75,6 +75,65 @@ impl Pallet { Ok(()) } + /// Sets the identity for a subnet. + /// + /// This function allows the owner of a subnet to set or update the identity information associated with the subnet. + /// It verifies that the caller is the owner of the specified subnet, validates the provided identity information, + /// and then stores it in the blockchain state. + /// + /// # Arguments + /// + /// * `origin` - The origin of the call, which should be a signed extrinsic. + /// * `netuid` - The unique identifier for the subnet. + /// * `subnet_name` - The name of the subnet to be associated with the identity. + /// * `github_repo` - The GitHub repository URL associated with the subnet identity. + /// * `subnet_contact` - Contact information for the subnet. + /// + /// # Returns + /// + /// Returns `Ok(())` if the subnet identity is successfully set, otherwise returns an error. + pub fn do_set_subnet_identity( + origin: T::RuntimeOrigin, + netuid: u16, + subnet_name: Vec, + github_repo: Vec, + subnet_contact: Vec, + ) -> dispatch::DispatchResult { + // Ensure the call is signed and get the signer's (coldkey) account + let coldkey = ensure_signed(origin)?; + + // Ensure that the coldkey owns the subnet + ensure!( + Self::get_subnet_owner(netuid) == coldkey, + Error::::NotSubnetOwner + ); + + // Create the identity struct with the provided information + let identity: SubnetIdentityOf = SubnetIdentityOf { + subnet_name, + github_repo, + subnet_contact, + }; + + // Validate the created identity + ensure!( + Self::is_valid_subnet_identity(&identity), + Error::::InvalidIdentity + ); + + // Store the validated identity in the blockchain state + SubnetIdentities::::insert(netuid, identity.clone()); + + // Log the identity set event + log::info!("SubnetIdentitySet( netuid:{:?} ) ", netuid); + + // Emit an event to notify that an identity has been set + Self::deposit_event(Event::SubnetIdentitySet(netuid)); + + // Return Ok to indicate successful execution + Ok(()) + } + /// Validates the given ChainIdentityOf struct. /// /// This function checks if the total length of all fields in the ChainIdentityOf struct @@ -106,4 +165,30 @@ impl Pallet { && identity.description.len() <= 1024 && identity.additional.len() <= 1024 } + + /// Validates the given SubnetIdentityOf struct. + /// + /// This function checks if the total length of all fields in the SubnetIdentityOf struct + /// is less than or equal to 2304 bytes, and if each individual field is also + /// within its respective maximum byte limit. + /// + /// # Arguments + /// + /// * `identity` - A reference to the SubnetIdentityOf struct to be validated. + /// + /// # Returns + /// + /// * `bool` - Returns true if the SubnetIdentity is valid, false otherwise. + pub fn is_valid_subnet_identity(identity: &SubnetIdentityOf) -> bool { + let total_length = identity + .subnet_name + .len() + .saturating_add(identity.github_repo.len()) + .saturating_add(identity.subnet_contact.len()); + + total_length <= 256 + 1024 + 1024 + && identity.subnet_name.len() <= 256 + && identity.github_repo.len() <= 1024 + && identity.subnet_contact.len() <= 1024 + } } diff --git a/pallets/subtensor/src/utils/misc.rs b/pallets/subtensor/src/utils/misc.rs index 0e1038b18..76546a1a2 100644 --- a/pallets/subtensor/src/utils/misc.rs +++ b/pallets/subtensor/src/utils/misc.rs @@ -1,6 +1,6 @@ use super::*; use crate::{ - system::{ensure_root, ensure_signed_or_root}, + system::{ensure_root, ensure_signed_or_root, pallet_prelude::BlockNumberFor}, Error, }; use sp_core::Get; @@ -750,4 +750,34 @@ impl Pallet { // Emit an event to notify listeners about the change Self::deposit_event(Event::NetworkMaxStakeSet(netuid, max_stake)); } + + /// Set the duration for coldkey swap + /// + /// # Arguments + /// + /// * `duration` - The blocks for coldkey swap execution. + /// + /// # Effects + /// + /// * Update the ColdkeySwapScheduleDuration storage. + /// * Emits a ColdkeySwapScheduleDurationSet evnet. + pub fn set_coldkey_swap_schedule_duration(duration: BlockNumberFor) { + ColdkeySwapScheduleDuration::::set(duration); + Self::deposit_event(Event::ColdkeySwapScheduleDurationSet(duration)); + } + + /// Set the duration for dissolve network + /// + /// # Arguments + /// + /// * `duration` - The blocks for dissolve network execution. + /// + /// # Effects + /// + /// * Update the DissolveNetworkScheduleDuration storage. + /// * Emits a DissolveNetworkScheduleDurationSet evnet. + pub fn set_dissolve_network_schedule_duration(duration: BlockNumberFor) { + DissolveNetworkScheduleDuration::::set(duration); + Self::deposit_event(Event::DissolveNetworkScheduleDurationSet(duration)); + } } diff --git a/pallets/subtensor/tests/migration.rs b/pallets/subtensor/tests/migration.rs index 2a1388237..6c40d7d78 100644 --- a/pallets/subtensor/tests/migration.rs +++ b/pallets/subtensor/tests/migration.rs @@ -170,7 +170,7 @@ fn test_total_issuance_global() { SubtensorModule::add_balance_to_coldkey_account(&owner, lockcost); // Add a balance of 20000 to the coldkey account. assert_eq!(SubtensorModule::get_total_issuance(), 0); // initial is zero. assert_ok!(SubtensorModule::register_network( - <::RuntimeOrigin>::signed(owner) + <::RuntimeOrigin>::signed(owner), )); SubtensorModule::set_max_allowed_uids(netuid, 1); // Set the maximum allowed unique identifiers for the network to 1. assert_eq!(SubtensorModule::get_total_issuance(), 0); // initial is zero. diff --git a/pallets/subtensor/tests/networks.rs b/pallets/subtensor/tests/networks.rs index b41ff985d..3d3644236 100644 --- a/pallets/subtensor/tests/networks.rs +++ b/pallets/subtensor/tests/networks.rs @@ -1,7 +1,7 @@ use crate::mock::*; use frame_support::assert_ok; use frame_system::Config; -use pallet_subtensor::{DissolveNetworkScheduleDuration, Event}; +use pallet_subtensor::{ColdkeySwapScheduleDuration, DissolveNetworkScheduleDuration, Event}; use sp_core::U256; mod mock; @@ -35,7 +35,7 @@ fn test_registration_ok() { )); assert_ok!(SubtensorModule::user_remove_network( - <::RuntimeOrigin>::signed(coldkey_account_id), + coldkey_account_id, netuid )); @@ -94,3 +94,191 @@ fn test_schedule_dissolve_network_execution() { assert!(!SubtensorModule::if_subnet_exist(netuid)); }) } + +#[test] +fn test_non_owner_schedule_dissolve_network_execution() { + new_test_ext(1).execute_with(|| { + let block_number: u64 = 0; + let netuid: u16 = 2; + let tempo: u16 = 13; + let hotkey_account_id: U256 = U256::from(1); + let coldkey_account_id = U256::from(0); // Neighbour of the beast, har har + let non_network_owner_account_id = U256::from(2); // + let (nonce, work): (u64, Vec) = SubtensorModule::create_work_for_block_number( + netuid, + block_number, + 129123813, + &hotkey_account_id, + ); + + //add network + add_network(netuid, tempo, 0); + + assert_ok!(SubtensorModule::register( + <::RuntimeOrigin>::signed(hotkey_account_id), + netuid, + block_number, + nonce, + work.clone(), + hotkey_account_id, + coldkey_account_id + )); + + assert!(SubtensorModule::if_subnet_exist(netuid)); + + assert_ok!(SubtensorModule::schedule_dissolve_network( + <::RuntimeOrigin>::signed(non_network_owner_account_id), + netuid + )); + + let current_block = System::block_number(); + let execution_block = current_block + DissolveNetworkScheduleDuration::::get(); + + System::assert_last_event( + Event::DissolveNetworkScheduled { + account: non_network_owner_account_id, + netuid, + execution_block, + } + .into(), + ); + + run_to_block(execution_block); + // network exists since the caller is no the network owner + assert!(SubtensorModule::if_subnet_exist(netuid)); + }) +} + +#[test] +fn test_new_owner_schedule_dissolve_network_execution() { + new_test_ext(1).execute_with(|| { + let block_number: u64 = 0; + let netuid: u16 = 2; + let tempo: u16 = 13; + let hotkey_account_id: U256 = U256::from(1); + let coldkey_account_id = U256::from(0); // Neighbour of the beast, har har + let new_network_owner_account_id = U256::from(2); // + let (nonce, work): (u64, Vec) = SubtensorModule::create_work_for_block_number( + netuid, + block_number, + 129123813, + &hotkey_account_id, + ); + + //add network + add_network(netuid, tempo, 0); + + assert_ok!(SubtensorModule::register( + <::RuntimeOrigin>::signed(hotkey_account_id), + netuid, + block_number, + nonce, + work.clone(), + hotkey_account_id, + coldkey_account_id + )); + + assert!(SubtensorModule::if_subnet_exist(netuid)); + + // the account is not network owner when schedule the call + assert_ok!(SubtensorModule::schedule_dissolve_network( + <::RuntimeOrigin>::signed(new_network_owner_account_id), + netuid + )); + + let current_block = System::block_number(); + let execution_block = current_block + DissolveNetworkScheduleDuration::::get(); + + System::assert_last_event( + Event::DissolveNetworkScheduled { + account: new_network_owner_account_id, + netuid, + execution_block, + } + .into(), + ); + run_to_block(current_block + 1); + // become network owner after call scheduled + pallet_subtensor::SubnetOwner::::insert(netuid, new_network_owner_account_id); + + run_to_block(execution_block); + // network exists since the caller is no the network owner + assert!(!SubtensorModule::if_subnet_exist(netuid)); + }) +} + +#[test] +fn test_schedule_dissolve_network_execution_with_coldkey_swap() { + new_test_ext(1).execute_with(|| { + let block_number: u64 = 0; + let netuid: u16 = 2; + let tempo: u16 = 13; + let hotkey_account_id: U256 = U256::from(1); + let coldkey_account_id = U256::from(0); // Neighbour of the beast, har har + let new_network_owner_account_id = U256::from(2); // + + SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id, 1000000000000000); + + let (nonce, work): (u64, Vec) = SubtensorModule::create_work_for_block_number( + netuid, + block_number, + 129123813, + &hotkey_account_id, + ); + + //add network + add_network(netuid, tempo, 0); + + assert_ok!(SubtensorModule::register( + <::RuntimeOrigin>::signed(hotkey_account_id), + netuid, + block_number, + nonce, + work.clone(), + hotkey_account_id, + coldkey_account_id + )); + + assert!(SubtensorModule::if_subnet_exist(netuid)); + + // the account is not network owner when schedule the call + assert_ok!(SubtensorModule::schedule_swap_coldkey( + <::RuntimeOrigin>::signed(coldkey_account_id), + new_network_owner_account_id + )); + + let current_block = System::block_number(); + let execution_block = current_block + ColdkeySwapScheduleDuration::::get(); + + run_to_block(execution_block - 1); + + // the account is not network owner when schedule the call + assert_ok!(SubtensorModule::schedule_dissolve_network( + <::RuntimeOrigin>::signed(new_network_owner_account_id), + netuid + )); + + System::assert_last_event( + Event::DissolveNetworkScheduled { + account: new_network_owner_account_id, + netuid, + execution_block: DissolveNetworkScheduleDuration::::get() + execution_block + - 1, + } + .into(), + ); + + run_to_block(execution_block); + assert_eq!( + pallet_subtensor::SubnetOwner::::get(netuid), + new_network_owner_account_id + ); + + let current_block = System::block_number(); + let execution_block = current_block + DissolveNetworkScheduleDuration::::get(); + + run_to_block(execution_block); + // network exists since the caller is no the network owner + assert!(!SubtensorModule::if_subnet_exist(netuid)); + }) +} diff --git a/pallets/subtensor/tests/root.rs b/pallets/subtensor/tests/root.rs index 84df71d83..caf1e5935 100644 --- a/pallets/subtensor/tests/root.rs +++ b/pallets/subtensor/tests/root.rs @@ -4,8 +4,9 @@ use crate::mock::*; use frame_support::{assert_err, assert_ok}; use frame_system::Config; use frame_system::{EventRecord, Phase}; -use pallet_subtensor::migrations; use pallet_subtensor::Error; +use pallet_subtensor::{migrations, SubnetIdentity}; +use pallet_subtensor::{SubnetIdentities, SubnetIdentityOf}; use sp_core::{Get, H256, U256}; mod mock; @@ -235,7 +236,7 @@ fn test_root_set_weights() { for netuid in 1..n { log::debug!("Adding network with netuid: {}", netuid); assert_ok!(SubtensorModule::register_network( - <::RuntimeOrigin>::signed(U256::from(netuid + 456)) + <::RuntimeOrigin>::signed(U256::from(netuid + 456)), )); } @@ -380,7 +381,7 @@ fn test_root_set_weights_out_of_order_netuids() { if netuid % 2 == 0 { assert_ok!(SubtensorModule::register_network( - <::RuntimeOrigin>::signed(U256::from(netuid)) + <::RuntimeOrigin>::signed(U256::from(netuid)), )); } else { add_network(netuid as u16 * 10, 1000, 0) @@ -471,14 +472,14 @@ fn test_root_subnet_creation_deletion() { SubtensorModule::add_balance_to_coldkey_account(&owner, 1_000_000_000_000_000); // last_lock: 100000000000, min_lock: 100000000000, last_lock_block: 0, lock_reduction_interval: 2, current_block: 0, mult: 1 lock_cost: 100000000000 assert_ok!(SubtensorModule::register_network( - <::RuntimeOrigin>::signed(owner) + <::RuntimeOrigin>::signed(owner), )); // last_lock: 100000000000, min_lock: 100000000000, last_lock_block: 0, lock_reduction_interval: 2, current_block: 0, mult: 1 lock_cost: 100000000000 assert_eq!(SubtensorModule::get_network_lock_cost(), 100_000_000_000); step_block(1); // last_lock: 100000000000, min_lock: 100000000000, last_lock_block: 0, lock_reduction_interval: 2, current_block: 1, mult: 1 lock_cost: 100000000000 assert_ok!(SubtensorModule::register_network( - <::RuntimeOrigin>::signed(owner) + <::RuntimeOrigin>::signed(owner), )); // last_lock: 100000000000, min_lock: 100000000000, last_lock_block: 1, lock_reduction_interval: 2, current_block: 1, mult: 2 lock_cost: 200000000000 assert_eq!(SubtensorModule::get_network_lock_cost(), 200_000_000_000); // Doubles from previous subnet creation @@ -492,38 +493,38 @@ fn test_root_subnet_creation_deletion() { // last_lock: 100000000000, min_lock: 100000000000, last_lock_block: 1, lock_reduction_interval: 2, current_block: 4, mult: 2 lock_cost: 100000000000 assert_eq!(SubtensorModule::get_network_lock_cost(), 100_000_000_000); // Reaches min value assert_ok!(SubtensorModule::register_network( - <::RuntimeOrigin>::signed(owner) + <::RuntimeOrigin>::signed(owner), )); // last_lock: 100000000000, min_lock: 100000000000, last_lock_block: 4, lock_reduction_interval: 2, current_block: 4, mult: 2 lock_cost: 200000000000 assert_eq!(SubtensorModule::get_network_lock_cost(), 200_000_000_000); // Doubles from previous subnet creation step_block(1); // last_lock: 100000000000, min_lock: 100000000000, last_lock_block: 4, lock_reduction_interval: 2, current_block: 5, mult: 2 lock_cost: 150000000000 assert_ok!(SubtensorModule::register_network( - <::RuntimeOrigin>::signed(owner) + <::RuntimeOrigin>::signed(owner), )); // last_lock: 150000000000, min_lock: 100000000000, last_lock_block: 5, lock_reduction_interval: 2, current_block: 5, mult: 2 lock_cost: 300000000000 assert_eq!(SubtensorModule::get_network_lock_cost(), 300_000_000_000); // Doubles from previous subnet creation step_block(1); // last_lock: 150000000000, min_lock: 100000000000, last_lock_block: 5, lock_reduction_interval: 2, current_block: 6, mult: 2 lock_cost: 225000000000 assert_ok!(SubtensorModule::register_network( - <::RuntimeOrigin>::signed(owner) + <::RuntimeOrigin>::signed(owner), )); // last_lock: 225000000000, min_lock: 100000000000, last_lock_block: 6, lock_reduction_interval: 2, current_block: 6, mult: 2 lock_cost: 450000000000 assert_eq!(SubtensorModule::get_network_lock_cost(), 450_000_000_000); // Increasing step_block(1); // last_lock: 225000000000, min_lock: 100000000000, last_lock_block: 6, lock_reduction_interval: 2, current_block: 7, mult: 2 lock_cost: 337500000000 assert_ok!(SubtensorModule::register_network( - <::RuntimeOrigin>::signed(owner) + <::RuntimeOrigin>::signed(owner), )); // last_lock: 337500000000, min_lock: 100000000000, last_lock_block: 7, lock_reduction_interval: 2, current_block: 7, mult: 2 lock_cost: 675000000000 assert_eq!(SubtensorModule::get_network_lock_cost(), 675_000_000_000); // Increasing. assert_ok!(SubtensorModule::register_network( - <::RuntimeOrigin>::signed(owner) + <::RuntimeOrigin>::signed(owner), )); // last_lock: 337500000000, min_lock: 100000000000, last_lock_block: 7, lock_reduction_interval: 2, current_block: 7, mult: 2 lock_cost: 675000000000 assert_eq!(SubtensorModule::get_network_lock_cost(), 1_350_000_000_000); // Double increasing. assert_ok!(SubtensorModule::register_network( - <::RuntimeOrigin>::signed(owner) + <::RuntimeOrigin>::signed(owner), )); assert_eq!(SubtensorModule::get_network_lock_cost(), 2_700_000_000_000); // Double increasing again. @@ -572,7 +573,7 @@ fn test_network_pruning() { 1_000 )); assert_ok!(SubtensorModule::register_network( - <::RuntimeOrigin>::signed(cold) + <::RuntimeOrigin>::signed(cold), )); log::debug!("Adding network with netuid: {}", (i as u16) + 1); assert!(SubtensorModule::if_subnet_exist((i as u16) + 1)); @@ -645,17 +646,17 @@ fn test_network_prune_results() { SubtensorModule::add_balance_to_coldkey_account(&owner, 1_000_000_000_000_000); assert_ok!(SubtensorModule::register_network( - <::RuntimeOrigin>::signed(owner) + <::RuntimeOrigin>::signed(owner), )); step_block(3); assert_ok!(SubtensorModule::register_network( - <::RuntimeOrigin>::signed(owner) + <::RuntimeOrigin>::signed(owner), )); step_block(3); assert_ok!(SubtensorModule::register_network( - <::RuntimeOrigin>::signed(owner) + <::RuntimeOrigin>::signed(owner), )); step_block(3); @@ -699,7 +700,7 @@ fn test_weights_after_network_pruning() { // Register a network assert_ok!(SubtensorModule::register_network( - <::RuntimeOrigin>::signed(cold) + <::RuntimeOrigin>::signed(cold), )); log::debug!("Adding network with netuid: {}", (i as u16) + 1); @@ -759,7 +760,7 @@ fn test_weights_after_network_pruning() { assert_eq!(latest_weights[0][1], 21845); assert_ok!(SubtensorModule::register_network( - <::RuntimeOrigin>::signed(cold) + <::RuntimeOrigin>::signed(cold), )); // Subnet should not exist, as it would replace a previous subnet. @@ -914,7 +915,8 @@ fn test_dissolve_network_ok() { assert!(SubtensorModule::if_subnet_exist(netuid)); assert_ok!(SubtensorModule::dissolve_network( - RuntimeOrigin::signed(owner_coldkey), + RuntimeOrigin::root(), + owner_coldkey, netuid )); assert!(!SubtensorModule::if_subnet_exist(netuid)) @@ -937,7 +939,8 @@ fn test_dissolve_network_refund_coldkey_ok() { assert!(SubtensorModule::if_subnet_exist(netuid)); assert_ok!(SubtensorModule::dissolve_network( - RuntimeOrigin::signed(owner_coldkey), + RuntimeOrigin::root(), + owner_coldkey, netuid )); assert!(!SubtensorModule::if_subnet_exist(netuid)); @@ -961,7 +964,7 @@ fn test_dissolve_network_not_owner_err() { register_ok_neuron(netuid, hotkey, owner_coldkey, 3); assert_err!( - SubtensorModule::dissolve_network(RuntimeOrigin::signed(random_coldkey), netuid), + SubtensorModule::dissolve_network(RuntimeOrigin::root(), random_coldkey, netuid), Error::::NotSubnetOwner ); }); @@ -974,8 +977,78 @@ fn test_dissolve_network_does_not_exist_err() { let coldkey = U256::from(2); assert_err!( - SubtensorModule::dissolve_network(RuntimeOrigin::signed(coldkey), netuid), + SubtensorModule::dissolve_network(RuntimeOrigin::root(), coldkey, netuid), Error::::SubNetworkDoesNotExist ); }); } + +#[test] +fn test_user_add_network_with_identity_fields_ok() { + new_test_ext(1).execute_with(|| { + let coldkey_1 = U256::from(1); + let coldkey_2 = U256::from(2); + let balance_1 = SubtensorModule::get_network_lock_cost() + 10_000; + + let subnet_name_1: Vec = b"GenericSubnet1".to_vec(); + let github_repo_1: Vec = b"GenericSubnet1.com".to_vec(); + let subnet_contact_1: Vec = b"https://www.GenericSubnet1.co".to_vec(); + + let identity_value_1: SubnetIdentity = SubnetIdentityOf { + subnet_name: subnet_name_1.clone(), + github_repo: github_repo_1.clone(), + subnet_contact: subnet_contact_1.clone(), + }; + + let subnet_name_2: Vec = b"DistinctSubnet2".to_vec(); + let github_repo_2: Vec = b"https://github.com/DistinctRepo2".to_vec(); + let subnet_contact_2: Vec = b"https://contact2.example.com".to_vec(); + + let identity_value_2: SubnetIdentity = SubnetIdentityOf { + subnet_name: subnet_name_2.clone(), + github_repo: github_repo_2.clone(), + subnet_contact: subnet_contact_2.clone(), + }; + + SubtensorModule::add_balance_to_coldkey_account(&coldkey_1, balance_1); + + assert_ok!(SubtensorModule::user_add_network( + RuntimeOrigin::signed(coldkey_1), + Some(identity_value_1.clone()) + )); + + let balance_2 = SubtensorModule::get_network_lock_cost() + 10_000; + SubtensorModule::add_balance_to_coldkey_account(&coldkey_2, balance_2); + + assert_ok!(SubtensorModule::user_add_network( + RuntimeOrigin::signed(coldkey_2), + Some(identity_value_2.clone()) + )); + + let stored_identity_1: SubnetIdentity = SubnetIdentities::::get(1).unwrap(); + assert_eq!(stored_identity_1.subnet_name, subnet_name_1); + assert_eq!(stored_identity_1.github_repo, github_repo_1); + assert_eq!(stored_identity_1.subnet_contact, subnet_contact_1); + + let stored_identity_2: SubnetIdentity = SubnetIdentities::::get(2).unwrap(); + assert_eq!(stored_identity_2.subnet_name, subnet_name_2); + assert_eq!(stored_identity_2.github_repo, github_repo_2); + assert_eq!(stored_identity_2.subnet_contact, subnet_contact_2); + + // Now remove the first network. + assert_ok!(SubtensorModule::user_remove_network(coldkey_1, 1)); + + // Verify that the first network and identity have been removed. + assert!(SubnetIdentities::::get(1).is_none()); + + // Ensure the second network and identity are still intact. + let stored_identity_2_after_removal: SubnetIdentity = + SubnetIdentities::::get(2).unwrap(); + assert_eq!(stored_identity_2_after_removal.subnet_name, subnet_name_2); + assert_eq!(stored_identity_2_after_removal.github_repo, github_repo_2); + assert_eq!( + stored_identity_2_after_removal.subnet_contact, + subnet_contact_2 + ); + }); +} diff --git a/pallets/subtensor/tests/serving.rs b/pallets/subtensor/tests/serving.rs index b0eada8e6..49a963951 100644 --- a/pallets/subtensor/tests/serving.rs +++ b/pallets/subtensor/tests/serving.rs @@ -827,3 +827,190 @@ fn test_migrate_set_hotkey_identities() { ); }); } + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test serving -- test_do_set_subnet_identity --exact --nocapture +#[test] +fn test_do_set_subnet_identity() { + new_test_ext(1).execute_with(|| { + let coldkey = U256::from(1); + let hotkey = U256::from(2); + let netuid = 1; + + // Register a hotkey for the coldkey + add_network(netuid, 13, 0); + register_ok_neuron(netuid, hotkey, coldkey, 0); + + // Set coldkey as the owner of the subnet + SubnetOwner::::insert(netuid, coldkey); + + // Prepare subnet identity data + let subnet_name = b"Test Subnet".to_vec(); + let github_repo = b"https://github.com/test/subnet".to_vec(); + let subnet_contact = b"contact@testsubnet.com".to_vec(); + + // Set subnet identity + assert_ok!(SubtensorModule::do_set_subnet_identity( + <::RuntimeOrigin>::signed(coldkey), + netuid, + subnet_name.clone(), + github_repo.clone(), + subnet_contact.clone() + )); + + // Check if subnet identity is set correctly + let stored_identity = + SubnetIdentities::::get(netuid).expect("Subnet identity should be set"); + assert_eq!(stored_identity.subnet_name, subnet_name); + assert_eq!(stored_identity.github_repo, github_repo); + assert_eq!(stored_identity.subnet_contact, subnet_contact); + + // Test setting subnet identity by non-owner + let non_owner_coldkey = U256::from(2); + assert_noop!( + SubtensorModule::do_set_subnet_identity( + <::RuntimeOrigin>::signed(non_owner_coldkey), + netuid, + subnet_name.clone(), + github_repo.clone(), + subnet_contact.clone() + ), + Error::::NotSubnetOwner + ); + + // Test updating an existing subnet identity + let new_subnet_name = b"Updated Subnet".to_vec(); + let new_github_repo = b"https://github.com/test/subnet-updated".to_vec(); + assert_ok!(SubtensorModule::do_set_subnet_identity( + <::RuntimeOrigin>::signed(coldkey), + netuid, + new_subnet_name.clone(), + new_github_repo.clone(), + subnet_contact.clone() + )); + + let updated_identity = + SubnetIdentities::::get(netuid).expect("Updated subnet identity should be set"); + assert_eq!(updated_identity.subnet_name, new_subnet_name); + assert_eq!(updated_identity.github_repo, new_github_repo); + + // Test setting subnet identity with invalid data (exceeding 1024 bytes total) + let long_data = vec![0; 1025]; + assert_noop!( + SubtensorModule::do_set_subnet_identity( + <::RuntimeOrigin>::signed(coldkey), + netuid, + long_data.clone(), + long_data.clone(), + long_data.clone() + ), + Error::::InvalidIdentity + ); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test serving -- test_is_valid_subnet_identity --exact --nocapture +#[test] +fn test_is_valid_subnet_identity() { + new_test_ext(1).execute_with(|| { + // Test valid subnet identity + let valid_identity = SubnetIdentity { + subnet_name: vec![0; 256], + github_repo: vec![0; 1024], + subnet_contact: vec![0; 1024], + }; + assert!(SubtensorModule::is_valid_subnet_identity(&valid_identity)); + + // Test subnet identity with total length exactly at the maximum + let max_length_identity = SubnetIdentity { + subnet_name: vec![0; 256], + github_repo: vec![0; 1024], + subnet_contact: vec![0; 1024], + }; + assert!(SubtensorModule::is_valid_subnet_identity( + &max_length_identity + )); + + // Test subnet identity with total length exceeding the maximum + let invalid_length_identity = SubnetIdentity { + subnet_name: vec![0; 257], + github_repo: vec![0; 1024], + subnet_contact: vec![0; 1024], + }; + assert!(!SubtensorModule::is_valid_subnet_identity( + &invalid_length_identity + )); + + // Test subnet identity with one field exceeding its maximum + let invalid_field_identity = SubnetIdentity { + subnet_name: vec![0; 257], + github_repo: vec![0; 1024], + subnet_contact: vec![0; 1024], + }; + assert!(!SubtensorModule::is_valid_subnet_identity( + &invalid_field_identity + )); + + // Test subnet identity with empty fields + let empty_identity = SubnetIdentity { + subnet_name: vec![], + github_repo: vec![], + subnet_contact: vec![], + }; + assert!(SubtensorModule::is_valid_subnet_identity(&empty_identity)); + + // Test subnet identity with some empty and some filled fields + let mixed_identity = SubnetIdentity { + subnet_name: b"Test Subnet".to_vec(), + github_repo: vec![], + subnet_contact: b"contact@testsubnet.com".to_vec(), + }; + assert!(SubtensorModule::is_valid_subnet_identity(&mixed_identity)); + }); +} + +#[test] +fn test_set_identity_for_non_existent_subnet() { + new_test_ext(1).execute_with(|| { + let coldkey = U256::from(1); + let netuid = 999; // Non-existent subnet ID + + // Subnet identity data + let subnet_name = b"Non-existent Subnet".to_vec(); + let github_repo = b"https://github.com/test/nonexistent".to_vec(); + let subnet_contact = b"contact@nonexistent.com".to_vec(); + + // Attempt to set identity for a non-existent subnet + assert_noop!( + SubtensorModule::do_set_subnet_identity( + <::RuntimeOrigin>::signed(coldkey), + netuid, + subnet_name.clone(), + github_repo.clone(), + subnet_contact.clone() + ), + Error::::NotSubnetOwner // Since there's no owner, it should fail + ); + }); +} + +#[test] +fn test_set_subnet_identity_dispatch_info_ok() { + new_test_ext(1).execute_with(|| { + let netuid: u16 = 1; + let subnet_name: Vec = b"JesusSubnet".to_vec(); + let github_repo: Vec = b"bible.com".to_vec(); + let subnet_contact: Vec = b"https://www.vatican.va".to_vec(); + + let call: RuntimeCall = RuntimeCall::SubtensorModule(SubtensorCall::set_subnet_identity { + netuid, + subnet_name, + github_repo, + subnet_contact, + }); + + let dispatch_info: DispatchInfo = call.get_dispatch_info(); + + assert_eq!(dispatch_info.class, DispatchClass::Normal); + assert_eq!(dispatch_info.pays_fee, Pays::Yes); + }); +} 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..422954476 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, @@ -1426,6 +1426,21 @@ impl_runtime_apis! { result.encode() } + fn get_subnet_info_v2(netuid: u16) -> Vec { + let _result = SubtensorModule::get_subnet_info_v2(netuid); + if _result.is_some() { + let result = _result.expect("Could not get SubnetInfo"); + result.encode() + } else { + vec![] + } + } + + fn get_subnets_info_v2() -> Vec { + let result = SubtensorModule::get_subnets_info_v2(); + result.encode() + } + fn get_subnet_hyperparams(netuid: u16) -> Vec { let _result = SubtensorModule::get_subnet_hyperparams(netuid); if _result.is_some() {