pallet_handles/
handles_signed_extension.rs

1//! Substrate Signed Extension for validating requests to the handles pallet
2use crate::{Call, Config, Error, MSAIdToDisplayName, WeightInfo};
3use common_primitives::msa::MsaValidator;
4use core::marker::PhantomData;
5use frame_support::{
6	dispatch::DispatchInfo, ensure, pallet_prelude::ValidTransaction, traits::IsSubType,
7	unsigned::UnknownTransaction, RuntimeDebugNoBound,
8};
9use parity_scale_codec::{Decode, DecodeWithMemTracking, Encode};
10use scale_info::TypeInfo;
11use sp_runtime::{
12	traits::{
13		AsSystemOriginSigner, DispatchInfoOf, Dispatchable, PostDispatchInfoOf,
14		TransactionExtension,
15	},
16	transaction_validity::{
17		InvalidTransaction, TransactionSource, TransactionValidity, TransactionValidityError,
18	},
19	DispatchError, DispatchResult, ModuleError, Weight,
20};
21
22/// The TransactionExtension trait is implemented on CheckFreeExtrinsicUse to validate the request. The
23/// purpose of this is to ensure that the retire_handle extrinsic cannot be
24/// repeatedly called to flood the network.
25#[derive(Encode, Decode, DecodeWithMemTracking, Clone, Eq, PartialEq, TypeInfo)]
26#[scale_info(skip_type_params(T))]
27pub struct HandlesSignedExtension<T: Config + Send + Sync>(PhantomData<T>);
28
29impl<T: Config + Send + Sync> HandlesSignedExtension<T> {
30	/// Create new `TransactionExtension`.
31	pub fn new() -> Self {
32		Self(core::marker::PhantomData)
33	}
34
35	/// Validates the following criteria for the retire_handle() extrinsic:
36	///
37	/// * The delegator must already have a MSA id
38	/// * The MSA must already have a handle associated with it
39	///
40	/// Returns a `ValidTransaction` or wrapped `pallet::Error`
41	///
42	/// # Errors (as u8 wrapped by `InvalidTransaction::Custom`)
43	/// * [`Error::InvalidMessageSourceAccount`]
44	/// * [`Error::MSAHandleDoesNotExist`]
45	pub fn validate_retire_handle(delegator_key: &T::AccountId) -> TransactionValidity {
46		const TAG_PREFIX: &str = "HandlesRetireHandle";
47
48		// Validation: The delegator must already have a MSA id
49		let delegator_msa_id =
50			T::MsaInfoProvider::ensure_valid_msa_key(delegator_key).map_err(map_dispatch_error)?;
51		// Validation: The MSA must already have a handle associated with it
52		let handle_from_state = MSAIdToDisplayName::<T>::try_get(delegator_msa_id)
53			.map_err(|_| UnknownTransaction::CannotLookup)?;
54		let expiration = handle_from_state.1;
55		let current_block = frame_system::Pallet::<T>::block_number();
56
57		let is_past_min_lifetime = current_block >= expiration;
58		ensure!(
59			is_past_min_lifetime,
60			map_dispatch_error(DispatchError::Other(
61				Error::<T>::HandleWithinMortalityPeriod.into()
62			))
63		);
64
65		ValidTransaction::with_tag_prefix(TAG_PREFIX).build()
66	}
67}
68
69impl<T: Config + Send + Sync> core::fmt::Debug for HandlesSignedExtension<T> {
70	#[cfg(feature = "std")]
71	fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
72		write!(f, "HandlesSignedExtension<{:?}>", self.0)
73	}
74	#[cfg(not(feature = "std"))]
75	fn fmt(&self, _: &mut core::fmt::Formatter) -> core::fmt::Result {
76		Ok(())
77	}
78}
79
80/// Map a module DispatchError to an InvalidTransaction::Custom error
81pub fn map_dispatch_error(err: DispatchError) -> InvalidTransaction {
82	InvalidTransaction::Custom(match err {
83		DispatchError::Module(ModuleError { error, .. }) => error[0],
84		_ => 255u8,
85	})
86}
87
88/// The info passed between the validate and prepare steps for the `HandlesSignedExtension` extension.
89#[derive(RuntimeDebugNoBound)]
90pub enum Val {
91	/// Valid transaction, no weight refund.
92	Valid,
93	/// Weight refund for the transaction.
94	Refund(Weight),
95}
96
97/// The info passed between the prepare and post-dispatch steps for the `HandlesSignedExtension` extension.
98#[derive(RuntimeDebugNoBound)]
99pub enum Pre {
100	/// Valid transaction, no weight refund.
101	Valid,
102	/// Weight refund for the transaction.
103	Refund(Weight),
104}
105
106impl<T: Config + Send + Sync> TransactionExtension<T::RuntimeCall> for HandlesSignedExtension<T>
107where
108	T::RuntimeCall: Dispatchable<Info = DispatchInfo> + IsSubType<Call<T>>,
109	<T as frame_system::Config>::RuntimeOrigin: AsSystemOriginSigner<T::AccountId> + Clone,
110{
111	const IDENTIFIER: &'static str = "HandlesSignedExtension";
112	type Implicit = ();
113	type Val = Val;
114	type Pre = Pre;
115
116	fn weight(&self, call: &T::RuntimeCall) -> Weight {
117		if let Some(Call::retire_handle {}) = call.is_sub_type() {
118			T::WeightInfo::validate_retire_handle_benchmark()
119		} else {
120			Weight::zero()
121		}
122	}
123	/// Frequently called by the transaction queue to validate all free Handles extrinsics:
124	/// Returns a `ValidTransaction` or wrapped [`TransactionValidityError`]
125	/// * retire_handle
126	///
127	/// Validate functions for the above MUST prevent errors in the extrinsic logic to prevent spam.
128	///
129	/// Arguments:
130	/// who: AccountId calling the extrinsic
131	/// call: The pallet extrinsic being called
132	/// unused: _info, _len
133	///
134	fn validate(
135		&self,
136		origin: <T as frame_system::Config>::RuntimeOrigin,
137		call: &T::RuntimeCall,
138		_info: &DispatchInfoOf<T::RuntimeCall>,
139		_len: usize,
140		_self_implicit: Self::Implicit,
141		_inherited_implication: &impl parity_scale_codec::Encode,
142		_source: TransactionSource,
143	) -> sp_runtime::traits::ValidateResult<Self::Val, T::RuntimeCall> {
144		let Some(who) = origin.as_system_origin_signer() else {
145			return Ok((ValidTransaction::default(), Val::Refund(self.weight(call)), origin));
146		};
147		let validity = match call.is_sub_type() {
148			Some(Call::retire_handle {}) => Self::validate_retire_handle(who),
149			_ => Ok(Default::default()),
150		};
151		validity.map(|v| (v, Val::Valid, origin))
152	}
153
154	fn prepare(
155		self,
156		val: Self::Val,
157		_origin: &<T as frame_system::Config>::RuntimeOrigin,
158		_call: &T::RuntimeCall,
159		_info: &DispatchInfoOf<T::RuntimeCall>,
160		_len: usize,
161	) -> Result<Self::Pre, TransactionValidityError> {
162		match val {
163			Val::Valid => Ok(Pre::Valid),
164			Val::Refund(w) => Ok(Pre::Refund(w)),
165		}
166	}
167
168	fn post_dispatch_details(
169		pre: Self::Pre,
170		_info: &DispatchInfo,
171		_post_info: &PostDispatchInfoOf<T::RuntimeCall>,
172		_len: usize,
173		_result: &DispatchResult,
174	) -> Result<Weight, sp_runtime::transaction_validity::TransactionValidityError> {
175		match pre {
176			Pre::Valid => Ok(Weight::zero()),
177			Pre::Refund(w) => Ok(w),
178		}
179	}
180}