1#![doc = include_str!("../README.md")]
11#![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
59pub(crate) type OnChargeTransactionOf<T> =
61	<T as pallet_transaction_payment::Config>::OnChargeTransaction;
62
63pub(crate) type BalanceOf<T> = <OnChargeTransactionOf<T> as OnChargeTransaction<T>>::Balance;
65
66pub(crate) type LiquidityInfoOf<T> =
68	<OnChargeTransactionOf<T> as OnChargeTransaction<T>>::LiquidityInfo;
69
70pub(crate) type CapacityOf<T> = <T as Config>::Capacity;
72
73pub(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#[derive(Encode, Decode, DefaultNoBound, TypeInfo)]
81pub enum InitialPayment<T: Config> {
82	#[default]
84	Free,
85	Token(LiquidityInfoOf<T>),
87	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	#[pallet::pallet]
139	pub struct Pallet<T>(_);
140
141	#[pallet::config]
142	pub trait Config: frame_system::Config + pallet_transaction_payment::Config {
143		#[allow(deprecated)]
145		type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
146
147		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		type Capacity: Replenishable + Nontransferable;
157
158		type WeightInfo: WeightInfo;
160
161		type CapacityCalls: GetStableWeight<<Self as Config>::RuntimeCall, Weight>;
163
164		type OnChargeCapacityTransaction: OnChargeCapacityTransaction<Self>;
166
167		#[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		BatchedCallAmountExceedsMaximum,
189	}
190
191	#[pallet::call]
192	impl<T: Config> Pallet<T> {
193		#[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		#[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	pub fn get_capacity_overhead_weight() -> Weight {
246		T::DbWeight::get().reads(2).saturating_add(T::DbWeight::get().writes(1))
247	}
248
249	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	pub fn compute_capacity_fee_details(
274		runtime_call: &<T as Config>::RuntimeCall,
275		overhead_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(overhead_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	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	pub fn weight_to_fee(weight: Weight) -> BalanceOf<T> {
315		let capped_weight = weight.min(T::BlockWeights::get().max_block);
318		T::WeightToFee::weight_to_fee(&capped_weight)
319	}
320}
321
322pub enum ChargeFrqTransactionPaymentError {
324	CallIsNotCapacityEligible,
326	InvalidMsaKey,
328	TargetCapacityNotFound,
330	BelowMinDeposit,
332}
333
334#[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	pub fn from(tip: BalanceOf<T>) -> Self {
362		Self(tip)
363	}
364
365	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	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	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	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	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	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#[derive(RuntimeDebugNoBound)]
530pub enum Val<T: Config> {
531	Charge { tip: BalanceOf<T>, who: T::AccountId, fee: BalanceOf<T> },
532	NoCharge,
533}
534
535#[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	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				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					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}