use crate::{ Call, CheckColdkeySwap, CheckDelegateTake, CheckEvmKeyAssociation, CheckRateLimits, CheckServingEndpoints, CheckWeights, Config, Error, guards::applicable_call, }; use codec::{Decode, DecodeWithMemTracking, Encode}; use frame_support::{ dispatch::{DispatchExtension, DispatchInfo, PostDispatchInfo}, traits::{IsSubType, OriginTrait}, weights::Weight, }; use scale_info::TypeInfo; use sp_runtime::traits::{ DispatchInfoOf, Dispatchable, Implication, TransactionExtension, ValidateResult, }; use sp_runtime::{ impl_tx_ext_default, transaction_validity::{TransactionSource, TransactionValidityError}, }; use sp_std::marker::PhantomData; use subtensor_macros::freeze_struct; use subtensor_runtime_common::CustomTransactionError; type CallOf = ::RuntimeCall; type OriginOf = ::RuntimeOrigin; #[allow(deprecated)] impl From> for CustomTransactionError { fn from(error: Error) -> Self { match error { Error::::AmountTooLow | Error::::NotEnoughStakeToSetWeights => { Self::StakeAmountTooLow } Error::::SubnetNotExists => Self::SubnetNotExists, Error::::NotEnoughBalanceToStake => Self::BalanceTooLow, Error::::HotKeyAccountNotExists => Self::HotkeyAccountDoesntExist, Error::::NotEnoughStakeToWithdraw => Self::NotEnoughStakeToWithdraw, Error::::InsufficientLiquidity => Self::InsufficientLiquidity, Error::::SlippageTooHigh => Self::SlippageTooHigh, Error::::TransferDisallowed => Self::TransferDisallowed, Error::::HotKeyNotRegisteredInNetwork => Self::HotKeyNotRegisteredInNetwork, Error::::InvalidIpAddress => Self::InvalidIpAddress, Error::::ServingRateLimitExceeded => Self::ServingRateLimitExceeded, Error::::InvalidPort => Self::InvalidPort, Error::::NonAssociatedColdKey => Self::NonAssociatedColdKey, Error::::DelegateTakeTooLow => Self::DelegateTakeTooLow, Error::::DelegateTakeTooHigh => Self::DelegateTakeTooHigh, Error::::InputLengthsUnequal => Self::InputLengthsUnequal, Error::::NoWeightsCommitFound => Self::CommitNotFound, Error::::RevealTooEarly => Self::CommitBlockNotInRevealRange, Error::::InvalidRevealRound => Self::InvalidRevealRound, Error::::CommittingWeightsTooFast | Error::::SettingWeightsTooFast | Error::::NetworkTxRateLimitExceeded => Self::RateLimitExceeded, Error::::HotKeyNotRegisteredInSubNet => Self::UidNotFound, Error::::EvmKeyAssociateRateLimitExceeded => Self::EvmKeyAssociateRateLimitExceeded, Error::::ColdkeySwapAnnounced => Self::ColdkeyInSwapSchedule, Error::::ColdkeySwapDisputed => Self::ColdkeySwapDisputed, _ => Self::BadRequest, } } } #[freeze_struct("2e02eb32e5cb25d3")] #[derive(Default, Encode, Decode, DecodeWithMemTracking, Clone, Eq, PartialEq, TypeInfo)] pub struct SubtensorTransactionExtension(pub PhantomData); impl sp_std::fmt::Debug for SubtensorTransactionExtension { fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result { write!(f, "SubtensorTransactionExtension") } } impl SubtensorTransactionExtension { pub fn new() -> Self { Self(Default::default()) } fn check(origin: &OriginOf, call: &CallOf) -> Result<(), Error> where T: pallet_shield::Config, CallOf: Dispatchable> + IsSubType> + IsSubType>, OriginOf: OriginTrait, { let Some(who) = origin.as_signer() else { return Ok(()); }; CheckColdkeySwap::::check(who, call)?; if let Some(call) = applicable_call(call, CheckWeights::::applies_to) { CheckWeights::::check(who, call)?; } if let Some(call) = applicable_call(call, CheckRateLimits::::applies_to) { CheckRateLimits::::check(who, call)?; } if let Some(call) = applicable_call(call, CheckDelegateTake::::applies_to) { CheckDelegateTake::::check(who, call)?; } if let Some(call) = applicable_call(call, CheckServingEndpoints::::applies_to) { CheckServingEndpoints::::check(who, call)?; } if let Some(call) = applicable_call(call, CheckEvmKeyAssociation::::applies_to) { CheckEvmKeyAssociation::::check(who, call)?; } Ok(()) } } impl TransactionExtension> for SubtensorTransactionExtension where T: Config + pallet_shield::Config + Send + Sync + TypeInfo, CallOf: Dispatchable, Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType> + IsSubType>, OriginOf: Clone + OriginTrait, { const IDENTIFIER: &'static str = "SubtensorTransactionExtension"; type Implicit = (); type Val = (); type Pre = (); fn weight(&self, call: &CallOf) -> Weight { use DispatchExtension as DE; as DE>>::weight(call) .saturating_add( as DE>>::weight(call)) .saturating_add( as DE>>::weight(call)) .saturating_add( as DE>>::weight(call)) .saturating_add( as DE>>::weight(call)) .saturating_add( as DE>>::weight(call)) } fn validate( &self, origin: OriginOf, call: &CallOf, _info: &DispatchInfoOf>, _len: usize, _self_implicit: Self::Implicit, _inherited_implication: &impl Implication, _source: TransactionSource, ) -> ValidateResult> { Self::check(&origin, call) .map(|()| (Default::default(), (), origin)) .map_err(|error| TransactionValidityError::from(CustomTransactionError::from(error))) } impl_tx_ext_default!(CallOf; prepare); } #[cfg(test)] #[allow(clippy::unwrap_used)] mod tests { use super::SubtensorTransactionExtension; use crate::{ CheckColdkeySwap, CheckDelegateTake, CheckEvmKeyAssociation, CheckRateLimits, CheckServingEndpoints, CheckWeights, ColdkeySwapAnnouncements, ColdkeySwapDisputes, tests::mock::*, }; use frame_support::{ assert_ok, dispatch::{DispatchExtension, GetDispatchInfo, Pays}, }; use frame_system::RawOrigin; use sp_core::U256; use sp_runtime::{ traits::{DispatchInfoOf, Hash, TransactionExtension, TxBaseImplication}, transaction_validity::{TransactionSource, TransactionValidityError, ValidTransaction}, }; use subtensor_runtime_common::{CustomTransactionError, MechId, NetUid}; fn dispatch_info() -> sp_runtime::traits::DispatchInfoOf<::RuntimeCall> { DispatchInfoOf::<::RuntimeCall>::default() } fn validate_signed( signer: U256, call: &RuntimeCall, ) -> Result { SubtensorTransactionExtension::::new() .validate( RawOrigin::Signed(signer).into(), call, &dispatch_info(), 0, (), &TxBaseImplication(()), TransactionSource::External, ) .map(|(validity, _, _)| validity) } fn expected_transaction_extension_weight(call: &RuntimeCall) -> frame_support::weights::Weight { use DispatchExtension as DE; as DE>::weight(call) .saturating_add( as DE>::weight(call)) .saturating_add( as DE>::weight(call)) .saturating_add( as DE>::weight(call)) .saturating_add( as DE>::weight( call, )) .saturating_add( as DE>::weight( call, )) } #[test] fn validate_accepts_calls_allowed_by_dispatch_extensions() { new_test_ext(1).execute_with(|| { let call = RuntimeCall::System(frame_system::Call::remark { remark: vec![] }); assert_ok!(validate_signed(U256::from(1), &call)); }); } #[test] #[allow(deprecated)] fn validate_maps_dispatch_extension_errors_to_transaction_errors() { new_test_ext(1).execute_with(|| { let coldkey = U256::from(1); let call = RuntimeCall::System(frame_system::Call::remark { remark: vec![] }); let new_coldkey_hash = ::Hashing::hash_of(&U256::from(99)); ColdkeySwapAnnouncements::::insert( coldkey, (System::block_number(), new_coldkey_hash), ); let err = validate_signed(coldkey, &call).unwrap_err(); assert_eq!(err, CustomTransactionError::ColdkeyInSwapSchedule.into()); ColdkeySwapDisputes::::insert(coldkey, System::block_number()); let err = validate_signed(coldkey, &call).unwrap_err(); assert_eq!(err, CustomTransactionError::ColdkeySwapDisputed.into()); }); } #[test] fn pays_no_set_weights_validate_rejects_rate_limited_call() { new_test_ext(0).execute_with(|| { let netuid = NetUid::from(1); let hotkey = U256::from(1); let coldkey = U256::from(2); add_network_disable_commit_reveal(netuid, 1, 0); setup_reserves( netuid, 1_000_000_000_000_u64.into(), 1_000_000_000_000_u64.into(), ); register_ok_neuron(netuid, hotkey, coldkey, 0); SubtensorModule::set_stake_threshold(0); SubtensorModule::set_weights_set_rate_limit(netuid, 100); System::set_block_number(10_u64); let uid = SubtensorModule::get_uid_for_net_and_hotkey(netuid, &hotkey).unwrap(); let netuid_index = SubtensorModule::get_mechanism_storage_index(netuid, MechId::MAIN); SubtensorModule::set_last_update_for_uid( netuid_index, uid, SubtensorModule::get_current_block_as_u64(), ); let call = RuntimeCall::SubtensorModule(SubtensorCall::set_weights { netuid, dests: vec![uid], weights: vec![1], version_key: 0, }); assert_eq!(call.get_dispatch_info().pays_fee, Pays::No); let err = validate_signed(hotkey, &call).unwrap_err(); assert_eq!(err, CustomTransactionError::RateLimitExceeded.into()); }); } #[test] fn weight_matches_top_level_dispatch_extension_checks() { new_test_ext(1).execute_with(|| { let extension = SubtensorTransactionExtension::::new(); let calls = [ RuntimeCall::System(frame_system::Call::remark { remark: vec![] }), RuntimeCall::SubtensorModule(SubtensorCall::set_weights { netuid: NetUid::from(1), dests: vec![0], weights: vec![1], version_key: 0, }), RuntimeCall::SubtensorModule(SubtensorCall::register_network { hotkey: U256::from(9), }), ]; for call in calls { assert_eq!( TransactionExtension::weight(&extension, &call), expected_transaction_extension_weight(&call) ); } }); } }