pallet_frequency_tx_payment/
lib.rs

1//! Allows transactions in alternative payment methods such as capacity
2//!
3//! ## Quick Links
4//! - [Configuration: `Config`](Config)
5//! - [Extrinsics: `Call`](Call)
6//! - [Runtime API: `CapacityTransactionPaymentRuntimeApi`](../pallet_frequency_tx_payment_runtime_api/trait.CapacityTransactionPaymentRuntimeApi.html)
7//! - [Custom RPC API: `CapacityPaymentApiServer`](../pallet_frequency_tx_payment_rpc/trait.CapacityPaymentApiServer.html)
8//! - [Event Enum: `Event`](Event)
9//! - [Error Enum: `Error`](Error)
10#![doc = include_str!("../README.md")]
11// Substrate macros are tripping the clippy::expect_used lint.
12#![allow(clippy::expect_used)]
13#![cfg_attr(not(feature = "std"), no_std)]
14
15use frame_support::{
16	dispatch::{DispatchInfo, GetDispatchInfo, PostDispatchInfo},
17	pallet_prelude::*,
18	traits::{IsSubType, IsType},
19	weights::{Weight, WeightToFee},
20	DefaultNoBound,
21};
22use frame_system::pallet_prelude::*;
23use pallet_transaction_payment::{FeeDetails, InclusionFee, OnChargeTransaction};
24use parity_scale_codec::{Decode, Encode};
25use scale_info::TypeInfo;
26use sp_runtime::{
27	traits::{
28		AsSystemOriginSigner, DispatchInfoOf, Dispatchable, PostDispatchInfoOf,
29		TransactionExtension, Zero,
30	},
31	transaction_validity::{
32		InvalidTransaction, TransactionSource, TransactionValidityError, ValidTransaction,
33	},
34	DispatchResult, FixedPointOperand, Saturating,
35};
36extern crate alloc;
37use alloc::{boxed::Box, vec, vec::Vec};
38use common_primitives::{
39	capacity::{Nontransferable, Replenishable},
40	msa::MsaKeyProvider,
41	node::UtilityProvider,
42};
43use core::ops::Mul;
44pub use pallet::*;
45use sp_runtime::Permill;
46pub use weights::*;
47
48mod payment;
49pub use payment::*;
50
51pub use types::GetStableWeight;
52pub mod types;
53
54pub mod capacity_stable_weights;
55
56use crate::types::GetAddKeyData;
57use capacity_stable_weights::CAPACITY_EXTRINSIC_BASE_WEIGHT;
58
59/// Type aliases used for interaction with `OnChargeTransaction`.
60pub(crate) type OnChargeTransactionOf<T> =
61	<T as pallet_transaction_payment::Config>::OnChargeTransaction;
62
63/// Balance type alias.
64pub(crate) type BalanceOf<T> = <OnChargeTransactionOf<T> as OnChargeTransaction<T>>::Balance;
65
66/// Liquidity info type alias (imbalances).
67pub(crate) type LiquidityInfoOf<T> =
68	<OnChargeTransactionOf<T> as OnChargeTransaction<T>>::LiquidityInfo;
69
70/// Capacity Balance type
71pub(crate) type CapacityOf<T> = <T as Config>::Capacity;
72
73/// Capacity Balance alias
74pub(crate) type CapacityBalanceOf<T> = <CapacityOf<T> as Nontransferable>::Balance;
75
76pub(crate) type ChargeCapacityBalanceOf<T> =
77	<<T as Config>::OnChargeCapacityTransaction as OnChargeCapacityTransaction<T>>::Balance;
78
79/// Used to pass the initial payment info from pre- to post-dispatch.
80#[derive(Encode, Decode, DefaultNoBound, TypeInfo)]
81pub enum InitialPayment<T: Config> {
82	/// No initial fee was paid.
83	#[default]
84	Free,
85	/// The initial fee was paid in the native currency.
86	Token(LiquidityInfoOf<T>),
87	/// The initial fee was paid in an asset.
88	Capacity,
89}
90
91#[cfg(feature = "std")]
92impl<T: Config> InitialPayment<T> {
93	pub fn is_free(&self) -> bool {
94		matches!(*self, InitialPayment::Free)
95	}
96
97	pub fn is_capacity(&self) -> bool {
98		matches!(*self, InitialPayment::Capacity)
99	}
100
101	pub fn is_token(&self) -> bool {
102		matches!(*self, InitialPayment::Token(_))
103	}
104}
105
106impl<T: Config> core::fmt::Debug for InitialPayment<T> {
107	#[cfg(feature = "std")]
108	fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
109		match *self {
110			InitialPayment::Free => write!(f, "Nothing"),
111			InitialPayment::Capacity => write!(f, "Token"),
112			InitialPayment::Token(_) => write!(f, "Imbalance"),
113		}
114	}
115
116	#[cfg(not(feature = "std"))]
117	fn fmt(&self, _: &mut core::fmt::Formatter) -> core::fmt::Result {
118		Ok(())
119	}
120}
121
122#[cfg(test)]
123mod tests;
124
125#[cfg(feature = "runtime-benchmarks")]
126mod benchmarking;
127
128pub mod weights;
129
130#[allow(dead_code)]
131#[frame_support::pallet]
132pub mod pallet {
133	use super::*;
134	use common_primitives::msa::{MessageSourceId, MsaKeyProvider};
135
136	// Simple declaration of the `Pallet` type. It is placeholder we use to implement traits and
137	// method.
138	#[pallet::pallet]
139	pub struct Pallet<T>(_);
140
141	#[pallet::config]
142	pub trait Config: frame_system::Config + pallet_transaction_payment::Config {
143		/// The overarching event type.
144		#[allow(deprecated)]
145		type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
146
147		/// The overarching call type.
148		type RuntimeCall: Parameter
149			+ Dispatchable<RuntimeOrigin = Self::RuntimeOrigin, PostInfo = PostDispatchInfo>
150			+ GetDispatchInfo
151			+ From<frame_system::Call<Self>>
152			+ IsSubType<Call<Self>>
153			+ IsType<<Self as frame_system::Config>::RuntimeCall>;
154
155		/// The type that replenishes and keeps capacity balances.
156		type Capacity: Replenishable + Nontransferable;
157
158		/// Weight information for extrinsics in this pallet.
159		type WeightInfo: WeightInfo;
160
161		/// The type that checks what transactions are capacity with their stable weights.
162		type CapacityCalls: GetStableWeight<<Self as Config>::RuntimeCall, Weight>;
163
164		/// Charge Capacity for transaction payments.
165		type OnChargeCapacityTransaction: OnChargeCapacityTransaction<Self>;
166
167		/// The maxmimum number of capacity calls that can be batched together.
168		#[pallet::constant]
169		type MaximumCapacityBatchLength: Get<u8>;
170
171		type BatchProvider: UtilityProvider<OriginFor<Self>, <Self as Config>::RuntimeCall>;
172
173		type MsaKeyProvider: MsaKeyProvider<AccountId = Self::AccountId>;
174		type MsaCallFilter: GetAddKeyData<
175			<Self as Config>::RuntimeCall,
176			Self::AccountId,
177			MessageSourceId,
178		>;
179	}
180
181	#[pallet::event]
182	#[pallet::generate_deposit(pub (super) fn deposit_event)]
183	pub enum Event<T: Config> {}
184
185	#[pallet::error]
186	pub enum Error<T> {
187		/// The maximum amount of requested batched calls was exceeded
188		BatchedCallAmountExceedsMaximum,
189	}
190
191	#[pallet::call]
192	impl<T: Config> Pallet<T> {
193		/// Dispatch the given call as a sub_type of pay_with_capacity. Calls dispatched in this
194		/// fashion, if allowed, will pay with Capacity.
195		#[pallet::call_index(0)]
196		#[pallet::weight({
197		let dispatch_info = call.get_dispatch_info();
198		(< T as Config >::WeightInfo::pay_with_capacity().saturating_add(dispatch_info.call_weight), dispatch_info.class)
199		})]
200		#[allow(clippy::useless_conversion)]
201		pub fn pay_with_capacity(
202			origin: OriginFor<T>,
203			call: Box<<T as Config>::RuntimeCall>,
204		) -> DispatchResultWithPostInfo {
205			ensure_signed(origin.clone())?;
206
207			call.dispatch(origin)
208		}
209
210		/// Dispatch the given call as a sub_type of pay_with_capacity_batch_all. Calls dispatched in this
211		/// fashion, if allowed, will pay with Capacity.
212		#[pallet::call_index(1)]
213		#[pallet::weight({
214		let dispatch_infos = calls.iter().map(|call| call.get_dispatch_info()).collect::<Vec<_>>();
215		let dispatch_weight = dispatch_infos.iter()
216				.map(|di| di.call_weight)
217				.fold(Weight::zero(), |total: Weight, weight: Weight| total.saturating_add(weight));
218		(< T as Config >::WeightInfo::pay_with_capacity_batch_all(calls.len() as u32).saturating_add(dispatch_weight), DispatchClass::Normal)
219		})]
220		#[allow(clippy::useless_conversion)]
221		pub fn pay_with_capacity_batch_all(
222			origin: OriginFor<T>,
223			calls: Vec<<T as Config>::RuntimeCall>,
224		) -> DispatchResultWithPostInfo {
225			ensure_signed(origin.clone())?;
226			ensure!(
227				calls.len() <= T::MaximumCapacityBatchLength::get().into(),
228				Error::<T>::BatchedCallAmountExceedsMaximum
229			);
230
231			T::BatchProvider::batch_all(origin, calls)
232		}
233	}
234}
235
236impl<T: Config> Pallet<T> {
237	/// The weight calculation is a temporary adjustment because overhead benchmarks do not account
238	/// for capacity calls.  We count reads and writes for a pay_with_capacity call,
239	/// then subtract one of each for regular transactions since overhead benchmarks account for these.
240	///   Storage: Msa PublicKeyToMsaId (r:1)
241	///   Storage: Capacity CapacityLedger(r:1, w:2)
242	///   Storage: Capacity CurrentEpoch(r:1) ? maybe cached in on_initialize
243	///   Storage: System Account(r:1)
244	///   Total (r: 4-1=3, w: 2-1=1)
245	pub fn get_capacity_overhead_weight() -> Weight {
246		T::DbWeight::get().reads(2).saturating_add(T::DbWeight::get().writes(1))
247	}
248
249	/// Compute the capacity fee for a transaction.
250	/// The fee is computed as the sum of the following:
251	/// - the weight fee, which is proportional to the weight of the transaction.
252	/// - the length fee, which is proportional to the length of the transaction;
253	/// - the base fee, which accounts for the overhead of an extrinsic.
254	///
255	/// NOTE: Changing CAPACITY_EXTRINSIC_BASE_WEIGHT will also change static capacity weights.
256	pub fn compute_capacity_fee(len: u32, extrinsic_weight: Weight) -> BalanceOf<T> {
257		let weight_fee = Self::weight_to_fee(extrinsic_weight);
258
259		let len_fee = Self::length_to_fee(len);
260		let base_fee = Self::weight_to_fee(CAPACITY_EXTRINSIC_BASE_WEIGHT);
261
262		base_fee.saturating_add(weight_fee).saturating_add(len_fee)
263	}
264
265	/// Compute the capacity fee details for a transaction.
266	/// # Arguments
267	/// * `runtime_call` - The runtime call to be dispatched.
268	/// * `weight` - The weight of the transaction.
269	/// * `len` - The length of the transaction.
270	///
271	/// # Returns
272	/// `FeeDetails` - The fee details for the transaction.
273	pub fn compute_capacity_fee_details(
274		runtime_call: &<T as Config>::RuntimeCall,
275		dispatch_weight: &Weight,
276		len: u32,
277	) -> FeeDetails<BalanceOf<T>> {
278		let calls = T::CapacityCalls::get_inner_calls(runtime_call)
279			.expect("A collection of calls is expected at minimum one.");
280
281		let mut calls_weight_sum = Weight::zero();
282		for inner_call in calls {
283			let call_weight = T::CapacityCalls::get_stable_weight(inner_call).unwrap_or_default();
284			calls_weight_sum = calls_weight_sum.saturating_add(call_weight);
285		}
286
287		let mut fees = FeeDetails { inclusion_fee: None, tip: Zero::zero() };
288		if !calls_weight_sum.is_zero() {
289			if let Some(weight) = calls_weight_sum.checked_add(dispatch_weight) {
290				let weight_fee = Self::weight_to_fee(weight);
291				let len_fee = Self::length_to_fee(len);
292				let base_fee = Self::weight_to_fee(CAPACITY_EXTRINSIC_BASE_WEIGHT);
293
294				let tip = Zero::zero();
295				fees = FeeDetails {
296					inclusion_fee: Some(InclusionFee {
297						base_fee,
298						len_fee,
299						adjusted_weight_fee: weight_fee,
300					}),
301					tip,
302				};
303			}
304		}
305		fees
306	}
307	/// Compute the length portion of a fee by invoking the configured `LengthToFee` impl.
308	pub fn length_to_fee(length: u32) -> BalanceOf<T> {
309		T::LengthToFee::weight_to_fee(&Weight::from_parts(length as u64, 0))
310	}
311
312	/// Compute the unadjusted portion of the weight fee by invoking the configured `WeightToFee`
313	/// impl. Note that the input `weight` is capped by the maximum block weight before computation.
314	pub fn weight_to_fee(weight: Weight) -> BalanceOf<T> {
315		// cap the weight to the maximum defined in runtime, otherwise it will be the
316		// `Bounded` maximum of its data type, which is not desired.
317		let capped_weight = weight.min(T::BlockWeights::get().max_block);
318		T::WeightToFee::weight_to_fee(&capped_weight)
319	}
320}
321
322/// Custom Transaction Validity Errors for ChargeFrqTransactionPayment
323pub enum ChargeFrqTransactionPaymentError {
324	/// The call is not eligible to be paid for with Capacity
325	CallIsNotCapacityEligible,
326	/// The account key is not associated with an MSA
327	InvalidMsaKey,
328	/// The Capacity Target does not exist
329	TargetCapacityNotFound,
330	/// The minimum balance required for keys used to pay with Capacity
331	BelowMinDeposit,
332}
333
334/// Require the transactor pay for themselves and maybe include a tip to gain additional priority
335/// in the queue.
336///
337/// # Transaction Validity
338///
339/// This extension sets the `priority` field of `TransactionValidity` depending on the amount
340/// of tip being paid per weight unit.
341///
342/// Operational transactions will receive an additional priority bump, so that they are normally
343/// considered before regular transactions.
344#[derive(Encode, Decode, DecodeWithMemTracking, Clone, Eq, PartialEq, TypeInfo)]
345#[scale_info(skip_type_params(T))]
346pub struct ChargeFrqTransactionPayment<T: Config>(#[codec(compact)] BalanceOf<T>);
347
348impl ChargeFrqTransactionPaymentError {
349	pub fn into(self) -> TransactionValidityError {
350		TransactionValidityError::from(InvalidTransaction::Custom(self as u8))
351	}
352}
353
354impl<T: Config> ChargeFrqTransactionPayment<T>
355where
356	BalanceOf<T>: Send + Sync + FixedPointOperand + IsType<ChargeCapacityBalanceOf<T>>,
357	<T as frame_system::Config>::RuntimeCall:
358		Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>>,
359{
360	/// Utility construct from tip.
361	pub fn from(tip: BalanceOf<T>) -> Self {
362		Self(tip)
363	}
364
365	/// Return the tip as being chosen by the transaction sender.
366	pub fn tip(&self, call: &<T as frame_system::Config>::RuntimeCall) -> BalanceOf<T> {
367		match call.is_sub_type() {
368			Some(Call::pay_with_capacity { .. }) |
369			Some(Call::pay_with_capacity_batch_all { .. }) => Zero::zero(),
370			_ => self.0,
371		}
372	}
373
374	// simulates fee calculation and withdrawal without applying any changes
375	fn dryrun_withdraw_fee(
376		&self,
377		who: &T::AccountId,
378		call: &<T as frame_system::Config>::RuntimeCall,
379		info: &DispatchInfoOf<<T as frame_system::Config>::RuntimeCall>,
380		len: usize,
381	) -> Result<BalanceOf<T>, TransactionValidityError> {
382		match call.is_sub_type() {
383			Some(Call::pay_with_capacity { call }) =>
384				self.dryrun_withdraw_capacity_fee(who, &vec![*call.clone()], len),
385
386			Some(Call::pay_with_capacity_batch_all { calls }) =>
387				self.dryrun_withdraw_capacity_fee(who, calls, len),
388
389			_ => self.dryrun_withdraw_token_fee(who, call, info, len, self.tip(call)),
390		}
391	}
392
393	fn dryrun_withdraw_capacity_fee(
394		&self,
395		who: &T::AccountId,
396		calls: &Vec<<T as Config>::RuntimeCall>,
397		len: usize,
398	) -> Result<BalanceOf<T>, TransactionValidityError> {
399		let mut calls_weight_sum = Weight::zero();
400		for call in calls {
401			let call_weight = T::CapacityCalls::get_stable_weight(call)
402				.ok_or(ChargeFrqTransactionPaymentError::CallIsNotCapacityEligible.into())?;
403			calls_weight_sum = calls_weight_sum.saturating_add(call_weight);
404		}
405		let fee = Pallet::<T>::compute_capacity_fee(len as u32, calls_weight_sum);
406		T::OnChargeCapacityTransaction::can_withdraw_fee(who, fee.into())?;
407		Ok(fee)
408	}
409
410	fn dryrun_withdraw_token_fee(
411		&self,
412		who: &T::AccountId,
413		call: &<T as frame_system::Config>::RuntimeCall,
414		info: &DispatchInfoOf<<T as frame_system::Config>::RuntimeCall>,
415		len: usize,
416		tip: BalanceOf<T>,
417	) -> Result<BalanceOf<T>, TransactionValidityError> {
418		let fee = pallet_transaction_payment::Pallet::<T>::compute_fee(len as u32, info, tip);
419		if fee.is_zero() {
420			return Ok(Default::default());
421		}
422		<<T as pallet_transaction_payment::Config>::OnChargeTransaction as OnChargeTransaction<
423			T,
424		>>::can_withdraw_fee(who, call, info, fee, tip)?;
425		Ok(fee)
426	}
427
428	/// Withdraws fee from either Capacity ledger or Token account.
429	fn withdraw_fee(
430		&self,
431		who: &T::AccountId,
432		call: &<T as frame_system::Config>::RuntimeCall,
433		info: &DispatchInfoOf<<T as frame_system::Config>::RuntimeCall>,
434		len: usize,
435	) -> Result<(BalanceOf<T>, InitialPayment<T>), TransactionValidityError> {
436		match call.is_sub_type() {
437			Some(Call::pay_with_capacity { call }) =>
438				self.withdraw_capacity_fee(who, &vec![*call.clone()], len),
439			Some(Call::pay_with_capacity_batch_all { calls }) =>
440				self.withdraw_capacity_fee(who, calls, len),
441			_ => self.withdraw_token_fee(who, call, info, len, self.tip(call)),
442		}
443	}
444
445	/// Withdraws the transaction fee paid in Capacity using a key associated to an MSA.
446	fn withdraw_capacity_fee(
447		&self,
448		key: &T::AccountId,
449		calls: &Vec<<T as Config>::RuntimeCall>,
450		len: usize,
451	) -> Result<(BalanceOf<T>, InitialPayment<T>), TransactionValidityError> {
452		let mut calls_weight_sum = Weight::zero();
453		let mut subsidized_calls_weight_sum = Weight::zero();
454
455		for call in calls {
456			let call_weight = T::CapacityCalls::get_stable_weight(call)
457				.ok_or(ChargeFrqTransactionPaymentError::CallIsNotCapacityEligible.into())?;
458			calls_weight_sum = calls_weight_sum.saturating_add(call_weight);
459
460			if self.call_is_adding_eligible_key_to_msa(call) {
461				subsidized_calls_weight_sum =
462					subsidized_calls_weight_sum.saturating_add(call_weight);
463			}
464		}
465		let capacity_fee = Pallet::<T>::compute_capacity_fee(len as u32, calls_weight_sum)
466			.saturating_sub(Self::subsidized_calls_reduction(len, subsidized_calls_weight_sum));
467		let fee = T::OnChargeCapacityTransaction::withdraw_fee(key, capacity_fee.into())?;
468
469		Ok((fee.into(), InitialPayment::Capacity))
470	}
471
472	// Give a 70% discount for eligible calls
473	fn subsidized_calls_reduction(len: usize, eligible_call_weight: Weight) -> BalanceOf<T> {
474		if eligible_call_weight.is_zero() {
475			0u32.into()
476		} else {
477			let reduction: Permill = Permill::from_percent(70u32);
478			reduction.mul(Pallet::<T>::compute_capacity_fee(len as u32, eligible_call_weight))
479		}
480	}
481
482	fn call_is_adding_eligible_key_to_msa(&self, call: &<T as Config>::RuntimeCall) -> bool {
483		if let Some((owner_account_id, new_account_id, msa_id)) =
484			T::MsaCallFilter::get_add_key_data(call)
485		{
486			return T::MsaKeyProvider::key_eligible_for_subsidized_addition(
487				owner_account_id,
488				new_account_id,
489				msa_id,
490			);
491		}
492		false
493	}
494
495	/// Withdraws transaction fee paid with tokens from an.
496	fn withdraw_token_fee(
497		&self,
498		who: &T::AccountId,
499		call: &<T as frame_system::Config>::RuntimeCall,
500		info: &DispatchInfoOf<<T as frame_system::Config>::RuntimeCall>,
501		len: usize,
502		tip: BalanceOf<T>,
503	) -> Result<(BalanceOf<T>, InitialPayment<T>), TransactionValidityError> {
504		let fee = pallet_transaction_payment::Pallet::<T>::compute_fee(len as u32, info, tip);
505		if fee.is_zero() {
506			return Ok((fee, InitialPayment::Free));
507		}
508
509		<OnChargeTransactionOf<T> as OnChargeTransaction<T>>::withdraw_fee(
510			who, call, info, fee, tip,
511		)
512		.map(|i| (fee, InitialPayment::Token(i)))
513		.map_err(|_| -> TransactionValidityError { InvalidTransaction::Payment.into() })
514	}
515}
516
517impl<T: Config> core::fmt::Debug for ChargeFrqTransactionPayment<T> {
518	#[cfg(feature = "std")]
519	fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
520		write!(f, "ChargeFrqTransactionPayment<{:?}>", self.0)
521	}
522	#[cfg(not(feature = "std"))]
523	fn fmt(&self, _: &mut core::fmt::Formatter) -> core::fmt::Result {
524		Ok(())
525	}
526}
527
528/// The info passed between the validate and prepare steps for the `ChargeFrqTransactionPayment` extension.
529#[derive(RuntimeDebugNoBound)]
530pub enum Val<T: Config> {
531	Charge { tip: BalanceOf<T>, who: T::AccountId, fee: BalanceOf<T> },
532	NoCharge,
533}
534
535/// The info passed between the prepare and post-dispatch steps for the `ChargeFrqTransactionPayment` extension.
536#[derive(RuntimeDebugNoBound)]
537pub enum Pre<T: Config> {
538	Charge {
539		tip: BalanceOf<T>,
540		who: T::AccountId,
541		initial_payment: InitialPayment<T>,
542		weight: Weight,
543	},
544	/// No charge was made, and the transaction is free.
545	NoCharge { refund: Weight },
546}
547
548impl<T: Config> TransactionExtension<<T as frame_system::Config>::RuntimeCall>
549	for ChargeFrqTransactionPayment<T>
550where
551	<T as frame_system::Config>::RuntimeCall:
552		IsSubType<Call<T>> + Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
553	BalanceOf<T>: Send
554		+ Sync
555		+ FixedPointOperand
556		+ From<u64>
557		+ IsType<ChargeCapacityBalanceOf<T>>
558		+ IsType<CapacityBalanceOf<T>>,
559	<T as frame_system::Config>::RuntimeOrigin: AsSystemOriginSigner<T::AccountId> + Clone,
560{
561	const IDENTIFIER: &'static str = "ChargeTransactionPayment";
562	type Implicit = ();
563	type Val = Val<T>;
564	type Pre = Pre<T>;
565
566	fn weight(&self, call: &<T as frame_system::Config>::RuntimeCall) -> Weight {
567		match call.is_sub_type() {
568			Some(Call::pay_with_capacity { .. }) |
569			Some(Call::pay_with_capacity_batch_all { .. }) =>
570				<T as Config>::WeightInfo::charge_tx_payment_capacity_based(),
571			_ => {
572				// For token-based calls, check if it's a free transaction
573				let info = call.get_dispatch_info();
574				if info.pays_fee == Pays::No {
575					<T as Config>::WeightInfo::charge_tx_payment_free()
576				} else {
577					<T as Config>::WeightInfo::charge_tx_payment_token_based()
578				}
579			},
580		}
581	}
582
583	fn validate(
584		&self,
585		origin: <T as frame_system::Config>::RuntimeOrigin,
586		call: &<T as frame_system::Config>::RuntimeCall,
587		info: &DispatchInfoOf<<T as frame_system::Config>::RuntimeCall>,
588		len: usize,
589		_self_implicit: Self::Implicit,
590		_inherited_implication: &impl Encode,
591		_source: TransactionSource,
592	) -> sp_runtime::traits::ValidateResult<Self::Val, <T as frame_system::Config>::RuntimeCall> {
593		let Some(who) = origin.as_system_origin_signer() else {
594			return Ok((
595				sp_runtime::transaction_validity::ValidTransaction::default(),
596				Val::NoCharge,
597				origin,
598			));
599		};
600		let fee = self.dryrun_withdraw_fee(who, call, info, len)?;
601		let tip = self.tip(call);
602		let priority = pallet_transaction_payment::ChargeTransactionPayment::<T>::get_priority(
603			info, len, tip, fee,
604		);
605		let val = Val::Charge { tip, who: who.clone(), fee };
606		let validity = ValidTransaction { priority, ..Default::default() };
607		Ok((validity, val, origin))
608	}
609
610	fn prepare(
611		self,
612		val: Self::Val,
613		_origin: &<T as frame_system::Config>::RuntimeOrigin,
614		call: &<T as frame_system::Config>::RuntimeCall,
615		info: &DispatchInfoOf<<T as frame_system::Config>::RuntimeCall>,
616		len: usize,
617	) -> Result<Self::Pre, TransactionValidityError> {
618		match val {
619			Val::Charge { tip, who, .. } => {
620				let (_fee, initial_payment) = self.withdraw_fee(&who, call, info, len)?;
621				Ok(Pre::Charge { tip, who, initial_payment, weight: self.weight(call) })
622			},
623			Val::NoCharge => Ok(Pre::NoCharge { refund: self.weight(call) }),
624		}
625	}
626
627	fn post_dispatch_details(
628		pre: Self::Pre,
629		info: &DispatchInfoOf<<T as frame_system::Config>::RuntimeCall>,
630		post_info: &PostDispatchInfoOf<<T as frame_system::Config>::RuntimeCall>,
631		len: usize,
632		result: &DispatchResult,
633	) -> Result<Weight, TransactionValidityError> {
634		match pre {
635			Pre::Charge { tip, who, initial_payment, .. } => match initial_payment {
636				InitialPayment::Token(already_withdrawn) => {
637					// post_dispatch_details eliminated the Option from the first param.
638					// TransactionExtension implementers are expected to customize Pre to separate signed from unsigned.
639					// https://github.com/paritytech/polkadot-sdk/pull/3685/files?#diff-be5f002cca427d36cd5322cc1af56544cce785482d69721b976aebf5821a78e3L875
640					let weight = pallet_transaction_payment::ChargeTransactionPayment::<T>::post_dispatch_details(
641                        pallet_transaction_payment::Pre::Charge { tip, who, imbalance: already_withdrawn },
642                        info,
643                        post_info,
644                        len,
645                        result,
646                    )?;
647					Ok(weight)
648				},
649				InitialPayment::Capacity => {
650					debug_assert!(tip.is_zero(), "tip should be zero for Capacity tx.");
651					Ok(Weight::zero())
652				},
653				InitialPayment::Free => {
654					debug_assert!(tip.is_zero(), "tip should be zero if initial fee was zero.");
655					Ok(Weight::zero())
656				},
657			},
658			Pre::NoCharge { refund } => Ok(refund),
659		}
660	}
661}