use core::marker::PhantomData; use frame_support::dispatch::{DispatchInfo, GetDispatchInfo, PostDispatchInfo}; use frame_support::traits::{ConstU32, IsSubType}; use frame_system::RawOrigin; use pallet_evm::{AddressMapping, PrecompileHandle}; use precompile_utils::{ EvmResult, prelude::{ Address, BoundedBytes, BoundedString, BoundedVec as SolidityBoundedVec, UnboundedBytes, revert, }, }; use sp_core::{H256, ecdsa::Signature}; use sp_runtime::traits::{AsSystemOriginSigner, Dispatchable}; use sp_std::vec::Vec; use subtensor_runtime_common::{MechId, NetUid, NetUidStorageIndex}; use crate::{PrecompileExt, PrecompileHandleExt}; /// Neuron precompile for smart-contract (EVM) access to neuron management operations. /// /// Each method maps to a `pallet-subtensor` dispatchable and is dispatched as a /// signed runtime call on behalf of the EVM caller (its mapped Substrate account), /// so the caller pays the underlying extrinsic weight and is subject to the same /// authorization rules (e.g. the caller coldkey must own the addressed hotkey). /// All methods are marked `payable` so calls carrying EVM value do not revert, /// but none of these methods consume the attached value. pub struct NeuronPrecompile(PhantomData); impl PrecompileExt for NeuronPrecompile where R: frame_system::Config + pallet_balances::Config + pallet_evm::Config + pallet_subtensor::Config + pallet_shield::Config + pallet_subtensor_proxy::Config + Send + Sync + scale_info::TypeInfo, R::AccountId: From<[u8; 32]> + Into<[u8; 32]>, ::RuntimeOrigin: AsSystemOriginSigner + Clone, ::RuntimeCall: From> + GetDispatchInfo + Dispatchable + IsSubType> + IsSubType> + IsSubType> + IsSubType>, ::AddressMapping: AddressMapping, { const INDEX: u64 = 2052; } #[precompile_utils::precompile] impl NeuronPrecompile where R: frame_system::Config + pallet_balances::Config + pallet_evm::Config + pallet_subtensor::Config + pallet_shield::Config + pallet_subtensor_proxy::Config + Send + Sync + scale_info::TypeInfo, R::AccountId: From<[u8; 32]> + Into<[u8; 32]>, ::RuntimeOrigin: AsSystemOriginSigner + Clone, ::RuntimeCall: From> + GetDispatchInfo + Dispatchable + IsSubType> + IsSubType> + IsSubType> + IsSubType>, ::AddressMapping: AddressMapping, { /// Set inter-neuron weights for the calling neuron on a subnet. /// /// Dispatches `set_weights`. This direct path is only honored when commit-reveal /// weights are **disabled** for the subnet; when commit-reveal is enabled the /// weights must instead be committed and revealed via `commitWeights` / /// `revealWeights`. /// /// # Arguments /// * `netuid` - The subnet identifier (uint16) /// * `dests` - Destination UIDs the weights apply to (uint16[]) /// * `weights` - Weight values, one per destination UID (uint16[]) /// * `version_key` - Weights version key; rejected if it is lower than the /// subnet's configured weights version key (uint64) /// /// # Returns /// * `()` on success, or an EVM error reverts the call #[precompile::public("setWeights(uint16,uint16[],uint16[],uint64)")] #[precompile::payable] pub fn set_weights( handle: &mut impl PrecompileHandle, netuid: u16, dests: Vec, weights: Vec, version_key: u64, ) -> EvmResult<()> { let call = pallet_subtensor::Call::::set_weights { netuid: netuid.into(), dests, weights, version_key, }; handle.try_dispatch_runtime_call::( call, RawOrigin::Signed(handle.caller_account_id::()), ) } /// Commit a hash of intended weights for the commit-reveal-v2 flow. /// /// Dispatches `commit_weights`. Stores a commitment for the caller's neuron on /// the subnet so the weights can later be revealed during the correct reveal /// epoch. Requires commit-reveal weights to be enabled for the subnet and the /// caller to meet the subnet's stake threshold. /// /// # Arguments /// * `netuid` - The subnet identifier (uint16) /// * `commit_hash` - Hash of `(hotkey, netuid, uids, values, salt, version_key)` /// committing to the weights that will be revealed (bytes32) /// /// # Returns /// * `()` on success, or an EVM error reverts the call #[precompile::public("commitWeights(uint16,bytes32)")] #[precompile::payable] pub fn commit_weights( handle: &mut impl PrecompileHandle, netuid: u16, commit_hash: H256, ) -> EvmResult<()> { let call = pallet_subtensor::Call::::commit_weights { netuid: netuid.into(), commit_hash, }; handle.try_dispatch_runtime_call::( call, RawOrigin::Signed(handle.caller_account_id::()), ) } /// Reveal previously committed weights and set them for the calling neuron. /// /// Dispatches `reveal_weights`. Verifies the reveal matches a prior /// `commitWeights` commitment for the current reveal epoch, then sets the /// weights and consumes the commitment. The revealed tuple must hash (under the /// same scheme used to build the commit) to the stored commit hash. /// /// # Arguments /// * `netuid` - The subnet identifier (uint16) /// * `uids` - Destination UIDs the weights apply to (uint16[]) /// * `values` - Weight values, one per destination UID (uint16[]) /// * `salt` - Salts, one per destination UID, binding the commit (uint16[]) /// * `version_key` - Neuron version key, must match the committed value (uint64) /// /// # Returns /// * `()` on success, or an EVM error reverts the call #[precompile::public("revealWeights(uint16,uint16[],uint16[],uint16[],uint64)")] #[precompile::payable] pub fn reveal_weights( handle: &mut impl PrecompileHandle, netuid: u16, uids: Vec, values: Vec, salt: Vec, version_key: u64, ) -> EvmResult<()> { let call = pallet_subtensor::Call::::reveal_weights { netuid: netuid.into(), uids, values, salt, version_key, }; handle.try_dispatch_runtime_call::( call, RawOrigin::Signed(handle.caller_account_id::()), ) } /// Register a hotkey on a subnet by burning TAO from the caller coldkey. /// /// Dispatches `burned_register`. The EVM caller is used as the owning coldkey; /// `hotkey` is the neuron hotkey to register. The subnet's current registration /// burn is charged to the caller; on success the hotkey is assigned a UID on /// the subnet (pruning the lowest-scoring neuron if the subnet is full) and the /// coldkey becomes its owner. /// /// # Arguments /// * `netuid` - The subnet identifier (uint16) /// * `hotkey` - The hotkey account ID to register (bytes32) /// /// # Returns /// * `()` on success, or an EVM error reverts the call (e.g. insufficient /// balance to cover the burn, registration disabled, or no UID available) #[precompile::public("burnedRegister(uint16,bytes32)")] #[precompile::payable] fn burned_register( handle: &mut impl PrecompileHandle, netuid: u16, hotkey: H256, ) -> EvmResult<()> { let coldkey = handle.caller_account_id::(); let hotkey = R::AccountId::from(hotkey.0); let call = pallet_subtensor::Call::::burned_register { netuid: netuid.into(), hotkey, }; handle.try_dispatch_runtime_call::(call, RawOrigin::Signed(coldkey)) } /// Register a hotkey on a subnet with a maximum acceptable burn price. /// /// Dispatches `register_limit`. Like `burnedRegister`, but the registration only /// proceeds if the subnet's current burn is less than or equal to `limit_price`, /// so a surging burn cannot over-charge the caller. The EVM caller is the owning /// coldkey; `hotkey` is the neuron hotkey to register. /// /// # Arguments /// * `netuid` - The subnet identifier (uint16) /// * `hotkey` - The hotkey account ID to register (bytes32) /// * `limit_price` - Maximum burn, in RAO, the caller is willing to pay (uint64) /// /// # Returns /// * `()` on success, or an EVM error reverts the call (e.g. current burn /// exceeds `limit_price`, insufficient balance, registration disabled) #[precompile::public("registerLimit(uint16,bytes32,uint64)")] #[precompile::payable] fn register_limit( handle: &mut impl PrecompileHandle, netuid: u16, hotkey: H256, limit_price: u64, ) -> EvmResult<()> { let coldkey = handle.caller_account_id::(); let hotkey = R::AccountId::from(hotkey.0); let call = pallet_subtensor::Call::::register_limit { netuid: netuid.into(), hotkey, limit_price, }; handle.try_dispatch_runtime_call::(call, RawOrigin::Signed(coldkey)) } /// Publish the calling neuron's Axon endpoint metadata for a subnet. /// /// Dispatches `serve_axon`. Stores the network location of the neuron's Axon /// (its query/forward RPC server) so validators and other neurons on the subnet /// can discover and reach it. /// /// # Arguments /// * `netuid` - The subnet identifier (uint16) /// * `version` - Axon protocol version (uint32) /// * `ip` - IPv4/IPv6 address as a packed integer (uint128) /// * `port` - TCP port (uint16) /// * `ip_type` - Address family: 4 for IPv4, 6 for IPv6 (uint8) /// * `protocol` - Transport protocol (uint8) /// * `placeholder1` - Reserved field (uint8) /// * `placeholder2` - Reserved field (uint8) /// /// # Returns /// * `()` on success, or an EVM error reverts the call #[precompile::public("serveAxon(uint16,uint32,uint128,uint16,uint8,uint8,uint8,uint8)")] #[precompile::payable] #[allow(clippy::too_many_arguments)] fn serve_axon( handle: &mut impl PrecompileHandle, netuid: u16, version: u32, ip: u128, port: u16, ip_type: u8, protocol: u8, placeholder1: u8, placeholder2: u8, ) -> EvmResult<()> { let call = pallet_subtensor::Call::::serve_axon { netuid: netuid.into(), version, ip, port, ip_type, protocol, placeholder1, placeholder2, }; handle.try_dispatch_runtime_call::( call, RawOrigin::Signed(handle.caller_account_id::()), ) } /// Publish the calling neuron's Axon endpoint metadata together with a TLS certificate. /// /// Dispatches `serve_axon_tls`. Like `serveAxon`, and additionally stores a TLS /// certificate so the Axon can be reached over a mutually-authenticated TLS /// connection. /// /// # Arguments /// * `netuid` - The subnet identifier (uint16) /// * `version` - Axon protocol version (uint32) /// * `ip` - IPv4/IPv6 address as a packed integer (uint128) /// * `port` - TCP port (uint16) /// * `ip_type` - Address family: 4 for IPv4, 6 for IPv6 (uint8) /// * `protocol` - Transport protocol (uint8) /// * `placeholder1` - Reserved field (uint8) /// * `placeholder2` - Reserved field (uint8) /// * `certificate` - TLS certificate bytes (bytes) /// /// # Returns /// * `()` on success, or an EVM error reverts the call #[precompile::public( "serveAxonTls(uint16,uint32,uint128,uint16,uint8,uint8,uint8,uint8,bytes)" )] #[precompile::payable] #[allow(clippy::too_many_arguments)] fn serve_axon_tls( handle: &mut impl PrecompileHandle, netuid: u16, version: u32, ip: u128, port: u16, ip_type: u8, protocol: u8, placeholder1: u8, placeholder2: u8, certificate: UnboundedBytes, ) -> EvmResult<()> { let call = pallet_subtensor::Call::::serve_axon_tls { netuid: netuid.into(), version, ip, port, ip_type, protocol, placeholder1, placeholder2, certificate: certificate.into(), }; handle.try_dispatch_runtime_call::( call, RawOrigin::Signed(handle.caller_account_id::()), ) } /// Publish the calling neuron's Prometheus metrics endpoint metadata for a subnet. /// /// Dispatches `serve_prometheus`. Stores the network location of the neuron's /// Prometheus metrics server so its operational metrics can be scraped. /// /// # Arguments /// * `netuid` - The subnet identifier (uint16) /// * `version` - Prometheus endpoint version (uint32) /// * `ip` - IPv4/IPv6 address as a packed integer (uint128) /// * `port` - TCP port (uint16) /// * `ip_type` - Address family: 4 for IPv4, 6 for IPv6 (uint8) /// /// # Returns /// * `()` on success, or an EVM error reverts the call #[precompile::public("servePrometheus(uint16,uint32,uint128,uint16,uint8)")] #[precompile::payable] #[allow(clippy::too_many_arguments)] fn serve_prometheus( handle: &mut impl PrecompileHandle, netuid: u16, version: u32, ip: u128, port: u16, ip_type: u8, ) -> EvmResult<()> { let call = pallet_subtensor::Call::::serve_prometheus { netuid: netuid.into(), version, ip, port, ip_type, }; handle.try_dispatch_runtime_call::( call, RawOrigin::Signed(handle.caller_account_id::()), ) } #[precompile::public("setMechanismWeights(uint16,uint8,uint16[],uint16[],uint64)")] fn set_mechanism_weights( handle: &mut impl PrecompileHandle, netuid: u16, mecid: u8, dests: SolidityBoundedVec>, weights: SolidityBoundedVec>, version_key: u64, ) -> EvmResult<()> { dispatch_neuron( handle, pallet_subtensor::Call::::set_mechanism_weights { netuid: netuid.into(), mecid: MechId::from(mecid), dests: dests.into(), weights: weights.into(), version_key, }, ) } #[precompile::public("commitMechanismWeights(uint16,uint8,bytes32)")] fn commit_mechanism_weights( handle: &mut impl PrecompileHandle, netuid: u16, mecid: u8, commit_hash: H256, ) -> EvmResult<()> { dispatch_neuron( handle, pallet_subtensor::Call::::commit_mechanism_weights { netuid: netuid.into(), mecid: mecid.into(), commit_hash, }, ) } #[precompile::public("revealMechanismWeights(uint16,uint8,uint16[],uint16[],uint16[],uint64)")] fn reveal_mechanism_weights( handle: &mut impl PrecompileHandle, netuid: u16, mecid: u8, uids: SolidityBoundedVec>, values: SolidityBoundedVec>, salt: SolidityBoundedVec>, version_key: u64, ) -> EvmResult<()> { dispatch_neuron( handle, pallet_subtensor::Call::::reveal_mechanism_weights { netuid: netuid.into(), mecid: mecid.into(), uids: uids.into(), values: values.into(), salt: salt.into(), version_key, }, ) } #[precompile::public("commitCrv3MechanismWeights(uint16,uint8,bytes,uint64)")] fn commit_crv3_mechanism_weights( handle: &mut impl PrecompileHandle, netuid: u16, mecid: u8, commit: BoundedBytes>, reveal_round: u64, ) -> EvmResult<()> { let commit = frame_support::BoundedVec::>::try_from(Vec::::from(commit)) .map_err(|_| revert("commit exceeds runtime bound"))?; dispatch_neuron( handle, pallet_subtensor::Call::::commit_crv3_mechanism_weights { netuid: netuid.into(), mecid: mecid.into(), commit, reveal_round, }, ) } #[precompile::public("commitTimelockedWeights(uint16,bytes,uint64,uint16)")] fn commit_timelocked_weights( handle: &mut impl PrecompileHandle, netuid: u16, commit: BoundedBytes>, reveal_round: u64, commit_reveal_version: u16, ) -> EvmResult<()> { let commit = frame_support::BoundedVec::>::try_from(Vec::::from(commit)) .map_err(|_| revert("commit exceeds runtime bound"))?; dispatch_neuron( handle, pallet_subtensor::Call::::commit_timelocked_weights { netuid: netuid.into(), commit, reveal_round, commit_reveal_version, }, ) } #[precompile::public("commitTimelockedMechanismWeights(uint16,uint8,bytes,uint64,uint16)")] fn commit_timelocked_mechanism_weights( handle: &mut impl PrecompileHandle, netuid: u16, mecid: u8, commit: BoundedBytes>, reveal_round: u64, commit_reveal_version: u16, ) -> EvmResult<()> { let commit = frame_support::BoundedVec::>::try_from(Vec::::from(commit)) .map_err(|_| revert("commit exceeds runtime bound"))?; dispatch_neuron( handle, pallet_subtensor::Call::::commit_timelocked_mechanism_weights { netuid: netuid.into(), mecid: mecid.into(), commit, reveal_round, commit_reveal_version, }, ) } #[precompile::public("batchSetWeights(uint16[],uint16[][],uint16[][],uint64[])")] fn batch_set_weights( handle: &mut impl PrecompileHandle, netuids: SolidityBoundedVec>, dests: SolidityBoundedVec>, ConstU32<16>>, values: SolidityBoundedVec>, ConstU32<16>>, version_keys: SolidityBoundedVec>, ) -> EvmResult<()> { let netuids = Vec::::from(netuids); let dests = Vec::>>::from(dests); let values = Vec::>>::from(values); let version_keys = Vec::::from(version_keys); if netuids.len() != dests.len() || netuids.len() != values.len() || netuids.len() != version_keys.len() { return Err(revert("batch weight arrays must have equal outer lengths")); } let mut weights = Vec::with_capacity(netuids.len()); for (batch_dests, batch_values) in dests.into_iter().zip(values) { let batch_dests = Vec::::from(batch_dests); let batch_values = Vec::::from(batch_values); if batch_dests.len() != batch_values.len() { return Err(revert( "batch destination and value arrays must have equal lengths", )); } weights.push( batch_dests .into_iter() .zip(batch_values) .map(|(uid, value)| (codec::Compact(uid), codec::Compact(value))) .collect(), ); } dispatch_neuron( handle, pallet_subtensor::Call::::batch_set_weights { netuids: netuids .into_iter() .map(|netuid| codec::Compact(NetUid::from(netuid))) .collect(), weights, version_keys: version_keys.into_iter().map(codec::Compact).collect(), }, ) } #[precompile::public("batchCommitWeights(uint16[],bytes32[])")] fn batch_commit_weights( handle: &mut impl PrecompileHandle, netuids: SolidityBoundedVec>, commit_hashes: SolidityBoundedVec>, ) -> EvmResult<()> { let netuids = Vec::::from(netuids); let commit_hashes = Vec::::from(commit_hashes); if netuids.len() != commit_hashes.len() { return Err(revert( "batch netuid and commitment arrays must have equal lengths", )); } dispatch_neuron( handle, pallet_subtensor::Call::::batch_commit_weights { netuids: netuids .into_iter() .map(|netuid| codec::Compact(NetUid::from(netuid))) .collect(), commit_hashes, }, ) } #[precompile::public("batchRevealWeights(uint16,uint16[][],uint16[][],uint16[][],uint64[])")] fn batch_reveal_weights( handle: &mut impl PrecompileHandle, netuid: u16, uids_list: SolidityBoundedVec>, ConstU32<16>>, values_list: SolidityBoundedVec>, ConstU32<16>>, salts_list: SolidityBoundedVec>, ConstU32<16>>, version_keys: SolidityBoundedVec>, ) -> EvmResult<()> { let uids_list = Vec::>>::from(uids_list); let values_list = Vec::>>::from(values_list); let salts_list = Vec::>>::from(salts_list); let version_keys = Vec::::from(version_keys); if uids_list.len() != values_list.len() || uids_list.len() != salts_list.len() || uids_list.len() != version_keys.len() { return Err(revert("batch reveal arrays must have equal outer lengths")); } dispatch_neuron( handle, pallet_subtensor::Call::::batch_reveal_weights { netuid: netuid.into(), uids_list: uids_list.into_iter().map(Into::into).collect(), values_list: values_list.into_iter().map(Into::into).collect(), salts_list: salts_list.into_iter().map(Into::into).collect(), version_keys, }, ) } #[precompile::public("register(uint16,uint64,uint64,bytes,bytes32,bytes32)")] fn register( handle: &mut impl PrecompileHandle, netuid: u16, block_number: u64, nonce: u64, work: BoundedBytes>, hotkey: H256, coldkey: H256, ) -> EvmResult<()> { dispatch_neuron( handle, pallet_subtensor::Call::::register { netuid: netuid.into(), block_number, nonce, work: work.into(), hotkey: hotkey.0.into(), coldkey: coldkey.0.into(), }, ) } #[precompile::public("rootRegister(bytes32)")] fn root_register(handle: &mut impl PrecompileHandle, hotkey: H256) -> EvmResult<()> { dispatch_neuron( handle, pallet_subtensor::Call::::root_register { hotkey: hotkey.0.into(), }, ) } #[precompile::public("swapHotkey(bytes32,bytes32,bool,uint16)")] fn swap_hotkey( handle: &mut impl PrecompileHandle, hotkey: H256, new_hotkey: H256, has_netuid: bool, netuid: u16, ) -> EvmResult<()> { dispatch_neuron( handle, pallet_subtensor::Call::::swap_hotkey { hotkey: hotkey.0.into(), new_hotkey: new_hotkey.0.into(), netuid: has_netuid.then_some(NetUid::from(netuid)), }, ) } #[precompile::public("swapHotkeyV2(bytes32,bytes32,bool,uint16,bool)")] fn swap_hotkey_v2( handle: &mut impl PrecompileHandle, hotkey: H256, new_hotkey: H256, has_netuid: bool, netuid: u16, keep_stake: bool, ) -> EvmResult<()> { dispatch_neuron( handle, pallet_subtensor::Call::::swap_hotkey_v2 { hotkey: hotkey.0.into(), new_hotkey: new_hotkey.0.into(), netuid: has_netuid.then_some(NetUid::from(netuid)), keep_stake, }, ) } #[precompile::public("setChildren(bytes32,uint16,uint64[],bytes32[])")] fn set_children( handle: &mut impl PrecompileHandle, hotkey: H256, netuid: u16, proportions: SolidityBoundedVec>, children: SolidityBoundedVec>, ) -> EvmResult<()> { let proportions = Vec::::from(proportions); let children = Vec::::from(children); if proportions.len() != children.len() { return Err(revert( "child proportions and hotkeys must have equal length", )); } let children = proportions .into_iter() .zip(children) .map(|(proportion, child)| (proportion, child.0.into())) .collect(); dispatch_neuron( handle, pallet_subtensor::Call::::set_children { hotkey: hotkey.0.into(), netuid: netuid.into(), children, }, ) } #[precompile::public("setIdentity(string,string,string,string,string,string,string)")] #[allow(clippy::too_many_arguments)] fn set_identity( handle: &mut impl PrecompileHandle, name: BoundedString>, url: BoundedString>, github_repo: BoundedString>, image: BoundedString>, discord: BoundedString>, description: BoundedString>, additional: BoundedString>, ) -> EvmResult<()> { dispatch_neuron( handle, pallet_subtensor::Call::::set_identity { name: name.into(), url: url.into(), github_repo: github_repo.into(), image: image.into(), discord: discord.into(), description: description.into(), additional: additional.into(), }, ) } #[precompile::public("tryAssociateHotkey(bytes32)")] fn try_associate_hotkey(handle: &mut impl PrecompileHandle, hotkey: H256) -> EvmResult<()> { dispatch_neuron( handle, pallet_subtensor::Call::::try_associate_hotkey { hotkey: hotkey.0.into(), }, ) } #[precompile::public("associateEvmKey(uint16,address,uint64,bytes)")] fn associate_evm_key( handle: &mut impl PrecompileHandle, netuid: u16, evm_key: Address, block_number: u64, signature: BoundedBytes>, ) -> EvmResult<()> { let bytes = Vec::::from(signature); let signature: [u8; 65] = bytes .try_into() .map_err(|_| revert("ECDSA signature must be exactly 65 bytes"))?; dispatch_neuron( handle, pallet_subtensor::Call::::associate_evm_key { netuid: netuid.into(), evm_key: evm_key.0, block_number, signature: Signature::from_raw(signature), }, ) } #[precompile::public("announceColdkeySwap(bytes32)")] fn announce_coldkey_swap( handle: &mut impl PrecompileHandle, new_coldkey_hash: H256, ) -> EvmResult<()> { let new_coldkey_hash = codec::Decode::decode(&mut new_coldkey_hash.as_bytes()) .map_err(|_| revert("runtime hash is not compatible with bytes32"))?; dispatch_neuron( handle, pallet_subtensor::Call::::announce_coldkey_swap { new_coldkey_hash }, ) } #[precompile::public("executeAnnouncedColdkeySwap(bytes32)")] fn execute_announced_coldkey_swap( handle: &mut impl PrecompileHandle, new_coldkey: H256, ) -> EvmResult<()> { dispatch_neuron( handle, pallet_subtensor::Call::::swap_coldkey_announced { new_coldkey: new_coldkey.0.into(), }, ) } #[precompile::public("disputeColdkeySwap()")] fn dispute_coldkey_swap(handle: &mut impl PrecompileHandle) -> EvmResult<()> { dispatch_neuron(handle, pallet_subtensor::Call::::dispute_coldkey_swap {}) } #[precompile::public("clearColdkeySwapAnnouncement()")] fn clear_coldkey_swap_announcement(handle: &mut impl PrecompileHandle) -> EvmResult<()> { dispatch_neuron( handle, pallet_subtensor::Call::::clear_coldkey_swap_announcement {}, ) } #[precompile::public("getUid(uint16,bytes32)")] #[precompile::view] fn get_uid( handle: &mut impl PrecompileHandle, netuid: u16, hotkey: H256, ) -> EvmResult<(bool, u16)> { handle.record_db_reads::(1)?; Ok( match pallet_subtensor::Uids::::get( NetUid::from(netuid), R::AccountId::from(hotkey.0), ) { Some(uid) => (true, uid), None => (false, 0), }, ) } #[precompile::public("isNetworkMember(bytes32,uint16)")] #[precompile::view] fn is_network_member( handle: &mut impl PrecompileHandle, hotkey: H256, netuid: u16, ) -> EvmResult { handle.record_db_reads::(1)?; Ok(pallet_subtensor::IsNetworkMember::::get( R::AccountId::from(hotkey.0), NetUid::from(netuid), )) } #[precompile::public("getWeights(uint16,uint16)")] #[precompile::view] fn get_weights( handle: &mut impl PrecompileHandle, netuid: u16, uid: u16, ) -> EvmResult> { handle.record_db_reads::(1)?; Ok(pallet_subtensor::Weights::::get( NetUidStorageIndex::from(NetUid::from(netuid)), uid, )) } #[precompile::public("getBonds(uint16,uint16)")] #[precompile::view] fn get_bonds( handle: &mut impl PrecompileHandle, netuid: u16, uid: u16, ) -> EvmResult> { handle.record_db_reads::(1)?; Ok(pallet_subtensor::Bonds::::get( NetUidStorageIndex::from(NetUid::from(netuid)), uid, )) } #[precompile::public("getBlockAtRegistration(uint16,uint16)")] #[precompile::view] fn get_block_at_registration( handle: &mut impl PrecompileHandle, netuid: u16, uid: u16, ) -> EvmResult { handle.record_db_reads::(1)?; Ok(pallet_subtensor::BlockAtRegistration::::get( NetUid::from(netuid), uid, )) } #[precompile::public("getNeuronCertificate(uint16,bytes32)")] #[precompile::view] fn get_neuron_certificate( handle: &mut impl PrecompileHandle, netuid: u16, hotkey: H256, ) -> EvmResult<(bool, u8, UnboundedBytes)> { handle.record_db_reads::(1)?; Ok( match pallet_subtensor::NeuronCertificates::::get( NetUid::from(netuid), R::AccountId::from(hotkey.0), ) { Some(certificate) => ( true, certificate.algorithm, UnboundedBytes::from(certificate.public_key.into_inner()), ), None => (false, 0, UnboundedBytes::default()), }, ) } #[precompile::public("getPrometheus(uint16,bytes32)")] #[precompile::view] fn get_prometheus( handle: &mut impl PrecompileHandle, netuid: u16, hotkey: H256, ) -> EvmResult<(bool, u64, u32, u128, u16, u8)> { handle.record_db_reads::(1)?; Ok( match pallet_subtensor::Prometheus::::get( NetUid::from(netuid), R::AccountId::from(hotkey.0), ) { Some(info) => ( true, info.block, info.version, info.ip, info.port, info.ip_type, ), None => (false, 0, 0, 0, 0, 0), }, ) } #[precompile::public("getChainIdentity(bytes32)")] #[precompile::view] fn get_chain_identity( handle: &mut impl PrecompileHandle, coldkey: H256, ) -> EvmResult<( bool, UnboundedBytes, UnboundedBytes, UnboundedBytes, UnboundedBytes, UnboundedBytes, UnboundedBytes, UnboundedBytes, )> { handle.record_db_reads::(1)?; Ok( match pallet_subtensor::IdentitiesV2::::get(R::AccountId::from(coldkey.0)) { Some(identity) => ( true, identity.name.into(), identity.url.into(), identity.github_repo.into(), identity.image.into(), identity.discord.into(), identity.description.into(), identity.additional.into(), ), None => ( false, Default::default(), Default::default(), Default::default(), Default::default(), Default::default(), Default::default(), Default::default(), ), }, ) } #[precompile::public("getSubnetIdentity(uint16)")] #[precompile::view] fn get_subnet_identity( handle: &mut impl PrecompileHandle, netuid: u16, ) -> EvmResult<( bool, UnboundedBytes, UnboundedBytes, UnboundedBytes, UnboundedBytes, UnboundedBytes, UnboundedBytes, UnboundedBytes, UnboundedBytes, )> { handle.record_db_reads::(1)?; Ok( match pallet_subtensor::SubnetIdentitiesV3::::get(NetUid::from(netuid)) { Some(identity) => ( true, identity.subnet_name.into(), identity.github_repo.into(), identity.subnet_contact.into(), identity.subnet_url.into(), identity.discord.into(), identity.description.into(), identity.logo_url.into(), identity.additional.into(), ), None => ( false, Default::default(), Default::default(), Default::default(), Default::default(), Default::default(), Default::default(), Default::default(), Default::default(), ), }, ) } #[precompile::public("getLoadedEmission(uint16)")] #[precompile::view] fn get_loaded_emission( handle: &mut impl PrecompileHandle, netuid: u16, ) -> EvmResult<(bool, Vec<(H256, u64, u64)>)> { handle.record_db_reads::(1)?; Ok( match pallet_subtensor::LoadedEmission::::get(NetUid::from(netuid)) { Some(emission) => ( true, emission .into_iter() .map(|(hotkey, server, validator)| { (H256::from(hotkey.into()), server, validator) }) .collect(), ), None => (false, Vec::new()), }, ) } #[precompile::public("getTransactionKeyLastBlock(bytes32,uint16,uint16)")] #[precompile::view] fn get_transaction_key_last_block( handle: &mut impl PrecompileHandle, hotkey: H256, netuid: u16, transaction_key: u16, ) -> EvmResult { handle.record_db_reads::(1)?; Ok(pallet_subtensor::TransactionKeyLastBlock::::get(( R::AccountId::from(hotkey.0), NetUid::from(netuid), transaction_key, ))) } #[allow(deprecated)] #[precompile::public("getLegacyTransactionRateBlocks(bytes32)")] #[precompile::view] fn get_legacy_transaction_rate_blocks( handle: &mut impl PrecompileHandle, hotkey: H256, ) -> EvmResult<(u64, u64, u64)> { handle.record_db_reads::(3)?; let hotkey = R::AccountId::from(hotkey.0); Ok(( pallet_subtensor::LastTxBlock::::get(&hotkey), pallet_subtensor::LastTxBlockChildKeyTake::::get(&hotkey), pallet_subtensor::LastTxBlockDelegateTake::::get(hotkey), )) } #[precompile::public("getWeightCommit(uint16,bytes32,uint32)")] #[precompile::view] fn get_weight_commit( handle: &mut impl PrecompileHandle, netuid: u16, hotkey: H256, index: u32, ) -> EvmResult<(bool, H256, u64, u64)> { handle.record_db_reads::(1)?; let commits = pallet_subtensor::WeightCommits::::get( NetUidStorageIndex::from(NetUid::from(netuid)), R::AccountId::from(hotkey.0), ); Ok(commits .and_then(|commits| commits.get(index as usize).copied()) .map(|(hash, epoch, block, _)| (true, hash, epoch, block)) .unwrap_or((false, H256::zero(), 0, 0))) } #[precompile::public("getWeightCommitCount(uint16,bytes32)")] #[precompile::view] fn get_weight_commit_count( handle: &mut impl PrecompileHandle, netuid: u16, hotkey: H256, ) -> EvmResult { handle.record_db_reads::(1)?; Ok(pallet_subtensor::WeightCommits::::get( NetUidStorageIndex::from(NetUid::from(netuid)), R::AccountId::from(hotkey.0), ) .map(|commits| commits.len() as u32) .unwrap_or(0)) } #[precompile::public("getTimelockedWeightCommit(uint16,uint64,uint32)")] #[precompile::view] fn get_timelocked_weight_commit( handle: &mut impl PrecompileHandle, netuid: u16, epoch: u64, index: u32, ) -> EvmResult<(bool, H256, u64, H256, u32, u64)> { handle.record_db_reads::(1)?; let commits = pallet_subtensor::TimelockedWeightCommits::::get( NetUidStorageIndex::from(NetUid::from(netuid)), epoch, ); Ok(commits .get(index as usize) .map(|(who, block, ciphertext, round)| { ( true, H256::from(who.clone().into()), *block, H256::from(sp_io::hashing::keccak_256(ciphertext.as_slice())), ciphertext.len() as u32, *round, ) }) .unwrap_or((false, H256::zero(), 0, H256::zero(), 0, 0))) } #[precompile::public("getTimelockedWeightCommitCount(uint16,uint64)")] #[precompile::view] fn get_timelocked_weight_commit_count( handle: &mut impl PrecompileHandle, netuid: u16, epoch: u64, ) -> EvmResult { handle.record_db_reads::(1)?; Ok(pallet_subtensor::TimelockedWeightCommits::::get( NetUidStorageIndex::from(NetUid::from(netuid)), epoch, ) .len() as u32) } #[precompile::public("getLegacyTimelockedWeightCommit(uint8,uint16,uint64,uint32)")] #[precompile::view] fn get_legacy_timelocked_weight_commit( handle: &mut impl PrecompileHandle, version: u8, netuid: u16, epoch: u64, index: u32, ) -> EvmResult<(bool, H256, u64, H256, u32, u64)> { handle.record_db_reads::(1)?; let netuid = NetUidStorageIndex::from(NetUid::from(netuid)); match version { 1 => Ok(pallet_subtensor::CRV3WeightCommits::::get(netuid, epoch) .get(index as usize) .map(|(who, ciphertext, round)| { ( true, H256::from(who.clone().into()), 0, H256::from(sp_io::hashing::keccak_256(ciphertext.as_slice())), ciphertext.len() as u32, *round, ) }) .unwrap_or((false, H256::zero(), 0, H256::zero(), 0, 0))), 2 => Ok( pallet_subtensor::CRV3WeightCommitsV2::::get(netuid, epoch) .get(index as usize) .map(|(who, block, ciphertext, round)| { ( true, H256::from(who.clone().into()), *block, H256::from(sp_io::hashing::keccak_256(ciphertext.as_slice())), ciphertext.len() as u32, *round, ) }) .unwrap_or((false, H256::zero(), 0, H256::zero(), 0, 0)), ), _ => Err(revert("unsupported legacy weight-commit version")), } } #[precompile::public("getLegacyTimelockedWeightCommitCount(uint8,uint16,uint64)")] #[precompile::view] fn get_legacy_timelocked_weight_commit_count( handle: &mut impl PrecompileHandle, version: u8, netuid: u16, epoch: u64, ) -> EvmResult { handle.record_db_reads::(1)?; let netuid = NetUidStorageIndex::from(NetUid::from(netuid)); match version { 1 => Ok(pallet_subtensor::CRV3WeightCommits::::get(netuid, epoch).len() as u32), 2 => Ok(pallet_subtensor::CRV3WeightCommitsV2::::get(netuid, epoch).len() as u32), _ => Err(revert("unsupported legacy weight-commit version")), } } } fn dispatch_neuron( handle: &mut impl PrecompileHandle, call: pallet_subtensor::Call, ) -> EvmResult<()> where R: frame_system::Config + pallet_balances::Config + pallet_evm::Config + pallet_subtensor::Config + pallet_shield::Config + pallet_subtensor_proxy::Config + Send + Sync + scale_info::TypeInfo, R::AccountId: From<[u8; 32]> + Into<[u8; 32]>, ::RuntimeOrigin: AsSystemOriginSigner + Clone, ::RuntimeCall: From> + GetDispatchInfo + Dispatchable + IsSubType> + IsSubType> + IsSubType> + IsSubType>, ::AddressMapping: AddressMapping, { let caller = handle.caller_account_id::(); handle.try_dispatch_runtime_call::(call, RawOrigin::Signed(caller)) } #[cfg(test)] mod tests { #![allow(clippy::expect_used, clippy::indexing_slicing, clippy::unwrap_used)] use super::*; use crate::PrecompileExt; use crate::mock::{ AccountId, Runtime, System, addr_from_index, execute_precompile, mapped_account, new_test_ext, precompiles, selector_u32, }; use precompile_utils::solidity::encode_with_selector; use precompile_utils::testing::PrecompileTesterExt; use sp_core::{H160, H256, U256}; use sp_runtime::traits::Hash; use subtensor_runtime_common::{AlphaBalance, NetUid, NetUidStorageIndex, TaoBalance, Token}; const TEST_NETUID_U16: u16 = 1; const REGISTRATION_BURN: u64 = 1_000; const RESERVE: u64 = 1_000_000_000; const COLDKEY_BALANCE: u64 = 50_000; const TEMPO: u16 = 100; const REVEAL_PERIOD: u64 = 1; const VERSION_KEY: u64 = 0; const REGISTERED_UID: u16 = 0; const REVEAL_UIDS: [u16; 1] = [REGISTERED_UID]; const REVEAL_VALUES: [u16; 1] = [5]; const REVEAL_SALT: [u16; 1] = [9]; const SERVE_VERSION: u32 = 0; const SERVE_IP: u128 = 1; const SERVE_PORT: u16 = 2; const SERVE_IP_TYPE: u8 = 4; const SERVE_PROTOCOL: u8 = 0; const SERVE_PLACEHOLDER1: u8 = 8; const SERVE_PLACEHOLDER2: u8 = 9; fn add_balance_to_coldkey_account(coldkey: &sp_core::crypto::AccountId32, tao: TaoBalance) { let credit = pallet_subtensor::Pallet::::mint_tao(tao); let _ = pallet_subtensor::Pallet::::spend_tao(coldkey, credit, tao).unwrap(); } fn setup_registered_caller(caller: H160) -> (NetUid, AccountId) { let netuid = NetUid::from(TEST_NETUID_U16); let caller_account = mapped_account(caller); let caller_hotkey = H256::from_slice(caller_account.as_ref()); pallet_subtensor::Pallet::::init_new_network(netuid, TEMPO); pallet_subtensor::Pallet::::set_network_registration_allowed(netuid, true); pallet_subtensor::Pallet::::set_burn(netuid, REGISTRATION_BURN.into()); pallet_subtensor::Pallet::::set_max_allowed_uids(netuid, 4096); pallet_subtensor::Pallet::::set_weights_set_rate_limit(netuid, 0); pallet_subtensor::Pallet::::set_tempo_unchecked(netuid, TEMPO); pallet_subtensor::Pallet::::set_commit_reveal_weights_enabled(netuid, true); pallet_subtensor::Pallet::::set_reveal_period(netuid, REVEAL_PERIOD) .expect("reveal period setup should succeed"); pallet_subtensor::SubnetTAO::::insert(netuid, TaoBalance::from(RESERVE)); pallet_subtensor::SubnetAlphaIn::::insert(netuid, AlphaBalance::from(RESERVE)); add_balance_to_coldkey_account(&caller_account, COLDKEY_BALANCE.into()); precompiles::>() .prepare_test( caller, addr_from_index(NeuronPrecompile::::INDEX), encode_with_selector( selector_u32("burnedRegister(uint16,bytes32)"), (TEST_NETUID_U16, caller_hotkey), ), ) .execute_returns(()); let registered_uid = pallet_subtensor::Pallet::::get_uid_for_net_and_hotkey( netuid, &caller_account, ) .expect("caller should be registered on subnet"); assert_eq!(registered_uid, REGISTERED_UID); (netuid, caller_account) } fn reveal_commit_hash(caller_account: &AccountId, netuid: NetUid) -> H256 { ::Hashing::hash_of(&( caller_account.clone(), NetUidStorageIndex::from(netuid), REVEAL_UIDS.as_slice(), REVEAL_VALUES.as_slice(), REVEAL_SALT.as_slice(), VERSION_KEY, )) } #[test] fn neuron_precompile_burned_register_adds_a_new_uid_and_key() { new_test_ext().execute_with(|| { let netuid = NetUid::from(TEST_NETUID_U16); let caller = addr_from_index(0x1234); let caller_account = mapped_account(caller); let hotkey_account = AccountId::from([0x42; 32]); let hotkey = H256::from_slice(hotkey_account.as_ref()); pallet_subtensor::Pallet::::init_new_network(netuid, TEMPO); pallet_subtensor::Pallet::::set_network_registration_allowed(netuid, true); pallet_subtensor::Pallet::::set_burn(netuid, REGISTRATION_BURN.into()); pallet_subtensor::Pallet::::set_max_allowed_uids(netuid, 4096); pallet_subtensor::SubnetTAO::::insert(netuid, TaoBalance::from(RESERVE)); pallet_subtensor::SubnetAlphaIn::::insert(netuid, AlphaBalance::from(RESERVE)); add_balance_to_coldkey_account(&caller_account, COLDKEY_BALANCE.into()); let uid_before = pallet_subtensor::SubnetworkN::::get(netuid); let balance_before = pallet_subtensor::Pallet::::get_coldkey_balance(&caller_account).to_u64(); precompiles::>() .prepare_test( caller, addr_from_index(NeuronPrecompile::::INDEX), encode_with_selector( selector_u32("burnedRegister(uint16,bytes32)"), (TEST_NETUID_U16, hotkey), ), ) .execute_returns(()); let uid_after = pallet_subtensor::SubnetworkN::::get(netuid); let registered_hotkey = pallet_subtensor::Keys::::get(netuid, uid_before); let owner = pallet_subtensor::Owner::::get(&hotkey_account); let balance_after = pallet_subtensor::Pallet::::get_coldkey_balance(&caller_account).to_u64(); assert_eq!(uid_after, uid_before + 1); assert_eq!(registered_hotkey, hotkey_account); assert_eq!(owner, caller_account); assert!(balance_after < balance_before); }); } #[test] fn neuron_precompile_commit_weights_respects_stake_threshold_and_stores_commit() { new_test_ext().execute_with(|| { let caller = addr_from_index(0x2234); let (netuid, caller_account) = setup_registered_caller(caller); let commit_hash = reveal_commit_hash(&caller_account, netuid); let precompile_addr = addr_from_index(NeuronPrecompile::::INDEX); pallet_subtensor::Pallet::::set_stake_threshold(1); let rejected = execute_precompile( &precompiles::>(), precompile_addr, caller, encode_with_selector( selector_u32("commitWeights(uint16,bytes32)"), (TEST_NETUID_U16, commit_hash), ), U256::zero(), ) .expect("commit weights should route to neuron precompile"); assert!(rejected.is_err()); pallet_subtensor::Pallet::::set_stake_threshold(0); precompiles::>() .prepare_test( caller, precompile_addr, encode_with_selector( selector_u32("commitWeights(uint16,bytes32)"), (TEST_NETUID_U16, commit_hash), ), ) .execute_returns(()); let commits = pallet_subtensor::WeightCommits::::get( NetUidStorageIndex::from(netuid), &caller_account, ) .expect("weight commits should be stored after successful commit"); assert_eq!(commits.len(), 1); }); } #[test] fn neuron_precompile_reveal_weights_respects_stake_threshold_and_sets_weights() { new_test_ext().execute_with(|| { let caller = addr_from_index(0x3234); let (netuid, caller_account) = setup_registered_caller(caller); let commit_hash = reveal_commit_hash(&caller_account, netuid); let precompile_addr = addr_from_index(NeuronPrecompile::::INDEX); precompiles::>() .prepare_test( caller, precompile_addr, encode_with_selector( selector_u32("commitWeights(uint16,bytes32)"), (TEST_NETUID_U16, commit_hash), ), ) .execute_returns(()); let commits = pallet_subtensor::WeightCommits::::get( NetUidStorageIndex::from(netuid), &caller_account, ) .expect("weight commit should exist before reveal"); // CR-v2 tuple layout: (hash, commit_epoch, commit_block, _unused). let (_, commit_epoch, _, _) = commits .front() .copied() .expect("weight commit queue should contain the committed hash"); // Put the subnet into the exact epoch in which the commit is revealable: // `current_epoch == commit_epoch + reveal_period`. Pin `LastEpochBlock` and // `PendingEpochAt` so `should_run_epoch` is false and the look-ahead does // not advance past the reveal epoch. let reveal_epoch = commit_epoch.saturating_add(REVEAL_PERIOD); pallet_subtensor::SubnetEpochIndex::::insert(netuid, reveal_epoch); let cur_block = pallet_subtensor::Pallet::::get_current_block_as_u64(); pallet_subtensor::LastEpochBlock::::insert(netuid, cur_block); pallet_subtensor::PendingEpochAt::::insert(netuid, 0u64); pallet_subtensor::Pallet::::set_stake_threshold(1); let rejected = execute_precompile( &precompiles::>(), precompile_addr, caller, encode_with_selector( selector_u32("revealWeights(uint16,uint16[],uint16[],uint16[],uint64)"), ( TEST_NETUID_U16, REVEAL_UIDS.to_vec(), REVEAL_VALUES.to_vec(), REVEAL_SALT.to_vec(), VERSION_KEY, ), ), U256::zero(), ) .expect("reveal weights should route to neuron precompile"); assert!(rejected.is_err()); pallet_subtensor::Pallet::::set_stake_threshold(0); precompiles::>() .prepare_test( caller, precompile_addr, encode_with_selector( selector_u32("revealWeights(uint16,uint16[],uint16[],uint16[],uint64)"), ( TEST_NETUID_U16, REVEAL_UIDS.to_vec(), REVEAL_VALUES.to_vec(), REVEAL_SALT.to_vec(), VERSION_KEY, ), ), ) .execute_returns(()); assert!( pallet_subtensor::WeightCommits::::get( NetUidStorageIndex::from(netuid), &caller_account ) .is_none() ); let neuron_uid = pallet_subtensor::Pallet::::get_uid_for_net_and_hotkey( netuid, &caller_account, ) .expect("caller should remain registered after reveal"); let weights = pallet_subtensor::Weights::::get( NetUidStorageIndex::from(netuid), neuron_uid, ); assert_eq!(weights.len(), 1); assert_eq!(weights[0].0, neuron_uid); assert!(weights[0].1 > 0); }); } #[test] fn neuron_precompile_set_weights_sets_weights_when_commit_reveal_is_disabled() { new_test_ext().execute_with(|| { let caller = addr_from_index(0x4234); let (netuid, caller_account) = setup_registered_caller(caller); let precompile_addr = addr_from_index(NeuronPrecompile::::INDEX); pallet_subtensor::Pallet::::set_commit_reveal_weights_enabled(netuid, false); precompiles::>() .prepare_test( caller, precompile_addr, encode_with_selector( selector_u32("setWeights(uint16,uint16[],uint16[],uint64)"), ( TEST_NETUID_U16, vec![REGISTERED_UID], vec![2_u16], VERSION_KEY, ), ), ) .execute_returns(()); let neuron_uid = pallet_subtensor::Pallet::::get_uid_for_net_and_hotkey( netuid, &caller_account, ) .expect("caller should remain registered after setting weights"); let weights = pallet_subtensor::Weights::::get( NetUidStorageIndex::from(netuid), neuron_uid, ); assert_eq!(weights.len(), 1); assert_eq!(weights[0].0, neuron_uid); assert!(weights[0].1 > 0); }); } #[test] fn neuron_precompile_serve_axon_sets_axon_info() { new_test_ext().execute_with(|| { let caller = addr_from_index(0x5234); let (netuid, caller_account) = setup_registered_caller(caller); precompiles::>() .prepare_test( caller, addr_from_index(NeuronPrecompile::::INDEX), encode_with_selector( selector_u32( "serveAxon(uint16,uint32,uint128,uint16,uint8,uint8,uint8,uint8)", ), ( TEST_NETUID_U16, SERVE_VERSION, SERVE_IP, SERVE_PORT, SERVE_IP_TYPE, SERVE_PROTOCOL, SERVE_PLACEHOLDER1, SERVE_PLACEHOLDER2, ), ), ) .execute_returns(()); let axon = pallet_subtensor::Axons::::get(netuid, &caller_account) .expect("axon info should be stored"); assert!(axon.block > 0); assert_eq!(axon.version, SERVE_VERSION); assert_eq!(axon.ip, SERVE_IP); assert_eq!(axon.port, SERVE_PORT); assert_eq!(axon.ip_type, SERVE_IP_TYPE); assert_eq!(axon.protocol, SERVE_PROTOCOL); assert_eq!(axon.placeholder1, SERVE_PLACEHOLDER1); assert_eq!(axon.placeholder2, SERVE_PLACEHOLDER2); }); } #[test] fn neuron_precompile_dispatch_runs_subtensor_dispatch_extensions() { new_test_ext().execute_with(|| { let caller = addr_from_index(0x5A34); let (netuid, caller_account) = setup_registered_caller(caller); let new_coldkey_hash = ::Hashing::hash_of(&AccountId::new([0x99; 32])); pallet_subtensor::ColdkeySwapAnnouncements::::insert( &caller_account, (System::block_number(), new_coldkey_hash), ); let rejected = execute_precompile( &precompiles::>(), addr_from_index(NeuronPrecompile::::INDEX), caller, encode_with_selector( selector_u32("serveAxon(uint16,uint32,uint128,uint16,uint8,uint8,uint8,uint8)"), ( TEST_NETUID_U16, SERVE_VERSION, SERVE_IP, SERVE_PORT, SERVE_IP_TYPE, SERVE_PROTOCOL, SERVE_PLACEHOLDER1, SERVE_PLACEHOLDER2, ), ), U256::zero(), ) .expect("serve axon should route to neuron precompile"); assert!(rejected.is_err()); assert!( pallet_subtensor::Axons::::get(netuid, caller_account).is_none(), "dispatch extension rejection must happen before the call writes endpoint metadata" ); }); } #[test] fn neuron_precompile_serve_axon_tls_sets_axon_info_and_certificate() { new_test_ext().execute_with(|| { let caller = addr_from_index(0x6234); let (netuid, caller_account) = setup_registered_caller(caller); let certificate: Vec = (1u8..=65).collect(); precompiles::>() .prepare_test( caller, addr_from_index(NeuronPrecompile::::INDEX), encode_with_selector( selector_u32( "serveAxonTls(uint16,uint32,uint128,uint16,uint8,uint8,uint8,uint8,bytes)", ), ( TEST_NETUID_U16, SERVE_VERSION, SERVE_IP, SERVE_PORT, SERVE_IP_TYPE, SERVE_PROTOCOL, SERVE_PLACEHOLDER1, SERVE_PLACEHOLDER2, UnboundedBytes::from(certificate.clone()), ), ), ) .execute_returns(()); let axon = pallet_subtensor::Axons::::get(netuid, &caller_account) .expect("axon info should be stored"); assert!(axon.block > 0); assert_eq!(axon.version, SERVE_VERSION); assert_eq!(axon.ip, SERVE_IP); assert_eq!(axon.port, SERVE_PORT); assert_eq!(axon.ip_type, SERVE_IP_TYPE); assert_eq!(axon.protocol, SERVE_PROTOCOL); assert_eq!(axon.placeholder1, SERVE_PLACEHOLDER1); assert_eq!(axon.placeholder2, SERVE_PLACEHOLDER2); let stored_certificate = pallet_subtensor::NeuronCertificates::::get(netuid, caller_account) .expect("certificate should be stored"); assert_eq!( stored_certificate.public_key.into_inner(), certificate[1..].to_vec() ); }); } #[test] fn neuron_precompile_serve_prometheus_sets_prometheus_info() { new_test_ext().execute_with(|| { let caller = addr_from_index(0x7234); let (netuid, caller_account) = setup_registered_caller(caller); precompiles::>() .prepare_test( caller, addr_from_index(NeuronPrecompile::::INDEX), encode_with_selector( selector_u32("servePrometheus(uint16,uint32,uint128,uint16,uint8)"), ( TEST_NETUID_U16, SERVE_VERSION, SERVE_IP, SERVE_PORT, SERVE_IP_TYPE, ), ), ) .execute_returns(()); let prometheus = pallet_subtensor::Prometheus::::get(netuid, caller_account) .expect("prometheus info should be stored"); assert!(prometheus.block > 0); assert_eq!(prometheus.version, SERVE_VERSION); assert_eq!(prometheus.ip, SERVE_IP); assert_eq!(prometheus.port, SERVE_PORT); assert_eq!(prometheus.ip_type, SERVE_IP_TYPE); }); } #[test] fn neuron_state_views_return_typed_values_and_missing_state() { new_test_ext().execute_with(|| { let caller = addr_from_index(0x8234); let address = addr_from_index(NeuronPrecompile::::INDEX); let precompiles = precompiles::>(); let netuid = NetUid::from(TEST_NETUID_U16); let netuid_index = NetUidStorageIndex::from(netuid); let hotkey = AccountId::from([0x81; 32]); let hotkey_word = H256::from_slice(hotkey.as_ref()); let uid = 7_u16; let weights = vec![(1_u16, 2_u16), (3_u16, 4_u16)]; let bonds = vec![(5_u16, 6_u16)]; pallet_subtensor::Uids::::insert(netuid, &hotkey, uid); pallet_subtensor::IsNetworkMember::::insert(&hotkey, netuid, true); pallet_subtensor::Weights::::insert(netuid_index, uid, weights.clone()); pallet_subtensor::Bonds::::insert(netuid_index, uid, bonds.clone()); pallet_subtensor::BlockAtRegistration::::insert(netuid, uid, 91_u64); macro_rules! assert_view { ($signature:literal, $arguments:expr, $expected:expr) => { precompiles .prepare_test( caller, address, encode_with_selector(selector_u32($signature), $arguments), ) .with_static_call(true) .execute_returns($expected); }; } assert_view!( "getUid(uint16,bytes32)", (TEST_NETUID_U16, hotkey_word), (true, uid) ); assert_view!( "isNetworkMember(bytes32,uint16)", (hotkey_word, TEST_NETUID_U16), true ); assert_view!("getWeights(uint16,uint16)", (TEST_NETUID_U16, uid), weights); assert_view!("getBonds(uint16,uint16)", (TEST_NETUID_U16, uid), bonds); assert_view!( "getBlockAtRegistration(uint16,uint16)", (TEST_NETUID_U16, uid), 91_u64 ); assert_view!( "getNeuronCertificate(uint16,bytes32)", (TEST_NETUID_U16, hotkey_word), (false, 0_u8, UnboundedBytes::default()) ); assert_view!( "getPrometheus(uint16,bytes32)", (TEST_NETUID_U16, hotkey_word), (false, 0_u64, 0_u32, 0_u128, 0_u16, 0_u8) ); assert_view!( "getChainIdentity(bytes32)", (hotkey_word,), ( false, UnboundedBytes::default(), UnboundedBytes::default(), UnboundedBytes::default(), UnboundedBytes::default(), UnboundedBytes::default(), UnboundedBytes::default(), UnboundedBytes::default(), ) ); assert_view!( "getSubnetIdentity(uint16)", (TEST_NETUID_U16,), ( false, UnboundedBytes::default(), UnboundedBytes::default(), UnboundedBytes::default(), UnboundedBytes::default(), UnboundedBytes::default(), UnboundedBytes::default(), UnboundedBytes::default(), UnboundedBytes::default(), ) ); assert_view!( "getLoadedEmission(uint16)", (TEST_NETUID_U16,), (false, Vec::<(H256, u64, u64)>::new()) ); assert_view!( "getTransactionKeyLastBlock(bytes32,uint16,uint16)", (hotkey_word, TEST_NETUID_U16, 4_u16), 0_u64 ); assert_view!( "getLegacyTransactionRateBlocks(bytes32)", (hotkey_word,), (0_u64, 0_u64, 0_u64) ); assert_view!( "getWeightCommit(uint16,bytes32,uint32)", (TEST_NETUID_U16, hotkey_word, 0_u32), (false, H256::zero(), 0_u64, 0_u64) ); assert_view!( "getWeightCommitCount(uint16,bytes32)", (TEST_NETUID_U16, hotkey_word), 0_u32 ); assert_view!( "getTimelockedWeightCommit(uint16,uint64,uint32)", (TEST_NETUID_U16, 2_u64, 0_u32), (false, H256::zero(), 0_u64, H256::zero(), 0_u32, 0_u64) ); assert_view!( "getTimelockedWeightCommitCount(uint16,uint64)", (TEST_NETUID_U16, 2_u64), 0_u32 ); for version in [1_u8, 2_u8] { assert_view!( "getLegacyTimelockedWeightCommit(uint8,uint16,uint64,uint32)", (version, TEST_NETUID_U16, 2_u64, 0_u32), (false, H256::zero(), 0_u64, H256::zero(), 0_u32, 0_u64) ); assert_view!( "getLegacyTimelockedWeightCommitCount(uint8,uint16,uint64)", (version, TEST_NETUID_U16, 2_u64), 0_u32 ); } }); } }