diff --git a/pallets/admin-utils/tests/tests.rs b/pallets/admin-utils/tests/tests.rs index 8ab85f177..a1395a056 100644 --- a/pallets/admin-utils/tests/tests.rs +++ b/pallets/admin-utils/tests/tests.rs @@ -1243,7 +1243,7 @@ fn test_sudo_get_set_alpha() { DispatchError::BadOrigin ); - assert_ok!(SubtensorModule::register_network(signer.clone())); + assert_ok!(SubtensorModule::register_network(signer.clone(), None)); assert_ok!(AdminUtils::sudo_set_alpha_values( signer.clone(), diff --git a/pallets/subtensor/src/benchmarks.rs b/pallets/subtensor/src/benchmarks.rs index d33e968e3..4af039ad6 100644 --- a/pallets/subtensor/src/benchmarks.rs +++ b/pallets/subtensor/src/benchmarks.rs @@ -299,7 +299,7 @@ benchmarks! { let amount: u64 = 1; let amount_to_be_staked = 100_000_000_000_000u64; Subtensor::::add_balance_to_coldkey_account(&coldkey.clone(), amount_to_be_staked); - }: register_network(RawOrigin::Signed(coldkey)) + }: register_network(RawOrigin::Signed(coldkey), None) benchmark_dissolve_network { let seed : u32 = 1; @@ -311,8 +311,9 @@ benchmarks! { let amount: u64 = 1; 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::Root, coldkey, 1) + assert_ok!(Subtensor::::register_network(RawOrigin::Signed(coldkey.clone()).into(), None)); + }: dissolve_network(RawOrigin::Signed(coldkey), 1) + // swap_hotkey { // let seed: u32 = 1; diff --git a/pallets/subtensor/src/coinbase/root.rs b/pallets/subtensor/src/coinbase/root.rs index 14b1beb7f..c1c9b3ad9 100644 --- a/pallets/subtensor/src/coinbase/root.rs +++ b/pallets/subtensor/src/coinbase/root.rs @@ -894,17 +894,24 @@ impl Pallet { /// Facilitates user registration of a new subnetwork. /// /// # 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(()) } @@ -1005,14 +1028,19 @@ impl Pallet { Error::::NotSubnetOwner ); - // --- 2. 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); - // --- 3. Emit the NetworkRemoved event. + // --- 6. Emit the NetworkRemoved event. log::debug!("NetworkRemoved( netuid:{:?} )", netuid); Self::deposit_event(Event::NetworkRemoved(netuid)); - // --- 5. Return success. + // --- 7. Return success. Ok(()) } 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 d86bf4df8..b1446b69d 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -901,8 +901,11 @@ mod dispatches { #[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(origin: OriginFor) -> DispatchResult { - Self::user_add_network(origin) + pub fn register_network( + origin: OriginFor, + identity: Option, + ) -> DispatchResult { + Self::user_add_network(origin, identity) } /// Facility extrinsic for user to get taken from faucet @@ -1168,5 +1171,35 @@ 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) + } } } diff --git a/pallets/subtensor/src/macros/events.rs b/pallets/subtensor/src/macros/events.rs index 9d8530504..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 diff --git a/pallets/subtensor/src/rpc_info/subnet_info.rs b/pallets/subtensor/src/rpc_info/subnet_info.rs index 4e9e756a0..6e8b4bdc5 100644 --- a/pallets/subtensor/src/rpc_info/subnet_info.rs +++ b/pallets/subtensor/src/rpc_info/subnet_info.rs @@ -4,7 +4,7 @@ use frame_support::storage::IterableStorageMap; extern crate alloc; use codec::Compact; -#[freeze_struct("fe79d58173da662a")] +#[freeze_struct("ccca539640c3f631")] #[derive(Decode, Encode, PartialEq, Eq, Clone, Debug)] pub struct SubnetInfo { netuid: Compact, @@ -25,6 +25,7 @@ pub struct SubnetInfo { emission_values: Compact, burn: Compact, owner: T::AccountId, + identity: Option, } #[freeze_struct("55b472510f10e76a")] @@ -80,6 +81,7 @@ 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(); + let identity: Option = SubnetIdentities::::get(netuid); // DEPRECATED let network_connect: Vec<[u16; 2]> = Vec::<[u16; 2]>::new(); @@ -106,6 +108,7 @@ impl Pallet { emission_values: emission_values.into(), burn, owner: Self::get_subnet_owner(netuid), + identity, }) } 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/tests/epoch.rs b/pallets/subtensor/tests/epoch.rs index 9c4bf87cc..30cc1d304 100644 --- a/pallets/subtensor/tests/epoch.rs +++ b/pallets/subtensor/tests/epoch.rs @@ -1501,7 +1501,7 @@ fn test_set_alpha_disabled() { assert_ok!(SubtensorModule::root_register(signer.clone(), hotkey,)); assert_ok!(SubtensorModule::add_stake(signer.clone(), hotkey, 1000)); // Only owner can set alpha values - assert_ok!(SubtensorModule::register_network(signer.clone())); + assert_ok!(SubtensorModule::register_network(signer.clone(), None)); // Explicitly set to false SubtensorModule::set_liquid_alpha_enabled(netuid, false); @@ -2584,7 +2584,7 @@ fn test_get_set_alpha() { DispatchError::BadOrigin ); - assert_ok!(SubtensorModule::register_network(signer.clone())); + assert_ok!(SubtensorModule::register_network(signer.clone(), None)); assert_ok!(SubtensorModule::do_set_alpha_values( signer.clone(), diff --git a/pallets/subtensor/tests/migration.rs b/pallets/subtensor/tests/migration.rs index 2a1388237..95479d7d7 100644 --- a/pallets/subtensor/tests/migration.rs +++ b/pallets/subtensor/tests/migration.rs @@ -170,7 +170,8 @@ 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), + None )); 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/root.rs b/pallets/subtensor/tests/root.rs index 9b0c76965..0c621739b 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,8 @@ 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)), + None )); } @@ -380,7 +382,8 @@ 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)), + None )); } else { add_network(netuid as u16 * 10, 1000, 0) @@ -471,14 +474,16 @@ 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), + None )); // 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), + None )); // 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 +497,44 @@ 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), + None )); // 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), + None )); // 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), + None )); // 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), + None )); // 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), + None )); // 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), + None )); assert_eq!(SubtensorModule::get_network_lock_cost(), 2_700_000_000_000); // Double increasing again. @@ -572,7 +583,8 @@ fn test_network_pruning() { 1_000 )); assert_ok!(SubtensorModule::register_network( - <::RuntimeOrigin>::signed(cold) + <::RuntimeOrigin>::signed(cold), + None )); log::debug!("Adding network with netuid: {}", (i as u16) + 1); assert!(SubtensorModule::if_subnet_exist((i as u16) + 1)); @@ -645,17 +657,20 @@ 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), + None )); step_block(3); assert_ok!(SubtensorModule::register_network( - <::RuntimeOrigin>::signed(owner) + <::RuntimeOrigin>::signed(owner), + None )); step_block(3); assert_ok!(SubtensorModule::register_network( - <::RuntimeOrigin>::signed(owner) + <::RuntimeOrigin>::signed(owner), + None )); step_block(3); @@ -699,7 +714,8 @@ fn test_weights_after_network_pruning() { // Register a network assert_ok!(SubtensorModule::register_network( - <::RuntimeOrigin>::signed(cold) + <::RuntimeOrigin>::signed(cold), + None )); log::debug!("Adding network with netuid: {}", (i as u16) + 1); @@ -759,7 +775,8 @@ fn test_weights_after_network_pruning() { assert_eq!(latest_weights[0][1], 21845); assert_ok!(SubtensorModule::register_network( - <::RuntimeOrigin>::signed(cold) + <::RuntimeOrigin>::signed(cold), + None )); // Subnet should not exist, as it would replace a previous subnet. @@ -981,3 +998,76 @@ fn test_dissolve_network_does_not_exist_err() { ); }); } + +#[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( + RuntimeOrigin::signed(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); + }); +}