pezpallet_transaction_payment/
lib.rs

1// This file is part of Bizinikiwi.
2
3// Copyright (C) Parity Technologies (UK) Ltd. and Dijital Kurdistan Tech Institute
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! # Transaction Payment Pezpallet
19//!
20//! This pezpallet provides the basic logic needed to pay the absolute minimum amount needed for a
21//! transaction to be included. This includes:
22//!   - _base fee_: This is the minimum amount a user pays for a transaction. It is declared
23//! 	as a base _weight_ in the runtime and converted to a fee using `WeightToFee`.
24//!   - _weight fee_: A fee proportional to amount of weight a transaction consumes.
25//!   - _length fee_: A fee proportional to the encoded length of the transaction.
26//!   - _tip_: An optional tip. Tip increases the priority of the transaction, giving it a higher
27//!     chance to be included by the transaction queue.
28//!
29//! The base fee and adjusted weight and length fees constitute the _inclusion fee_, which is
30//! the minimum fee for a transaction to be included in a block.
31//!
32//! The formula of final fee:
33//!   ```ignore
34//!   inclusion_fee = base_fee + length_fee + [targeted_fee_adjustment * weight_fee];
35//!   final_fee = inclusion_fee + tip;
36//!   ```
37//!
38//!   - `targeted_fee_adjustment`: This is a multiplier that can tune the final fee based on
39//! 	the congestion of the network.
40//!
41//! Additionally, this pezpallet allows one to configure:
42//!   - The mapping between one unit of weight to one unit of fee via [`Config::WeightToFee`].
43//!   - A means of updating the fee for the next block, via defining a multiplier, based on the
44//!     final state of the chain at the end of the previous block. This can be configured via
45//!     [`Config::FeeMultiplierUpdate`]
46//!   - How the fees are paid via [`Config::OnChargeTransaction`].
47
48#![cfg_attr(not(feature = "std"), no_std)]
49
50use codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
51use scale_info::TypeInfo;
52
53pub use payment::*;
54use pezframe_support::{
55	dispatch::{
56		DispatchClass, DispatchInfo, DispatchResult, GetDispatchInfo, Pays, PostDispatchInfo,
57	},
58	pezpallet_prelude::TransactionSource,
59	traits::{Defensive, EstimateCallFee, Get, Imbalance, SuppressedDrop},
60	weights::{Weight, WeightToFee},
61	RuntimeDebugNoBound,
62};
63pub use pezpallet::*;
64use pezsp_runtime::{
65	traits::{
66		Convert, DispatchInfoOf, Dispatchable, One, PostDispatchInfoOf, SaturatedConversion,
67		Saturating, TransactionExtension, Zero,
68	},
69	transaction_validity::{TransactionPriority, TransactionValidityError, ValidTransaction},
70	FixedPointNumber, FixedU128, Perbill, Perquintill, RuntimeDebug,
71};
72pub use types::{FeeDetails, InclusionFee, RuntimeDispatchInfo};
73pub use weights::WeightInfo;
74
75#[cfg(test)]
76mod mock;
77#[cfg(test)]
78mod tests;
79
80#[cfg(feature = "runtime-benchmarks")]
81mod benchmarking;
82
83mod payment;
84mod types;
85pub mod weights;
86
87/// Fee multiplier.
88pub type Multiplier = FixedU128;
89
90type BalanceOf<T> = <<T as Config>::OnChargeTransaction as OnChargeTransaction<T>>::Balance;
91type CreditOf<T> = <StoredCreditOf<T> as SuppressedDrop>::Inner;
92type StoredCreditOf<T> = <<T as Config>::OnChargeTransaction as TxCreditHold<T>>::Credit;
93
94const LOG_TARGET: &str = "runtime::txpayment";
95
96/// A struct to update the weight multiplier per block. It implements `Convert<Multiplier,
97/// Multiplier>`, meaning that it can convert the previous multiplier to the next one. This should
98/// be called on `on_finalize` of a block, prior to potentially cleaning the weight data from the
99/// system pezpallet.
100///
101/// given:
102/// 	s = previous block weight
103/// 	s'= ideal block weight
104/// 	m = maximum block weight
105/// 		diff = (s - s')/m
106/// 		v = 0.00001
107/// 		t1 = (v * diff)
108/// 		t2 = (v * diff)^2 / 2
109/// 	then:
110/// 	next_multiplier = prev_multiplier * (1 + t1 + t2)
111///
112/// Where `(s', v)` must be given as the `Get` implementation of the `T` generic type. Moreover, `M`
113/// must provide the minimum allowed value for the multiplier. Note that a runtime should ensure
114/// with tests that the combination of this `M` and `V` is not such that the multiplier can drop to
115/// zero and never recover.
116///
117/// Note that `s'` is interpreted as a portion in the _normal transaction_ capacity of the block.
118/// For example, given `s' == 0.25` and `AvailableBlockRatio = 0.75`, then the target fullness is
119/// _0.25 of the normal capacity_ and _0.1875 of the entire block_.
120///
121/// Since block weight is multi-dimension, we use the scarcer resource, referred as limiting
122/// dimension, for calculation of fees. We determine the limiting dimension by comparing the
123/// dimensions using the ratio of `dimension_value / max_dimension_value` and selecting the largest
124/// ratio. For instance, if a block is 30% full based on `ref_time` and 25% full based on
125/// `proof_size`, we identify `ref_time` as the limiting dimension, indicating that the block is 30%
126/// full.
127///
128/// This implementation implies the bound:
129/// - `v ≤ p / k * (s − s')`
130/// - or, solving for `p`: `p >= v * k * (s - s')`
131///
132/// where `p` is the amount of change over `k` blocks.
133///
134/// Hence:
135/// - in a fully congested chain: `p >= v * k * (1 - s')`.
136/// - in an empty chain: `p >= v * k * (-s')`.
137///
138/// For example, when all blocks are full and there are 28800 blocks per day (default in
139/// `bizinikiwi-node`) and v == 0.00001, s' == 0.1875, we'd have:
140///
141/// p >= 0.00001 * 28800 * 0.8125
142/// p >= 0.234
143///
144/// Meaning that fees can change by around ~23% per day, given extreme congestion.
145///
146/// More info can be found at:
147/// <https://research.web3.foundation/Polkadot/overview/token-economics>
148pub struct TargetedFeeAdjustment<T, S, V, M, X>(core::marker::PhantomData<(T, S, V, M, X)>);
149
150/// Something that can convert the current multiplier to the next one.
151pub trait MultiplierUpdate: Convert<Multiplier, Multiplier> {
152	/// Minimum multiplier. Any outcome of the `convert` function should be at least this.
153	fn min() -> Multiplier;
154	/// Maximum multiplier. Any outcome of the `convert` function should be less or equal this.
155	fn max() -> Multiplier;
156	/// Target block saturation level
157	fn target() -> Perquintill;
158	/// Variability factor
159	fn variability() -> Multiplier;
160}
161
162impl MultiplierUpdate for () {
163	fn min() -> Multiplier {
164		Default::default()
165	}
166	fn max() -> Multiplier {
167		<Multiplier as pezsp_runtime::traits::Bounded>::max_value()
168	}
169	fn target() -> Perquintill {
170		Default::default()
171	}
172	fn variability() -> Multiplier {
173		Default::default()
174	}
175}
176
177impl<T, S, V, M, X> MultiplierUpdate for TargetedFeeAdjustment<T, S, V, M, X>
178where
179	T: pezframe_system::Config,
180	S: Get<Perquintill>,
181	V: Get<Multiplier>,
182	M: Get<Multiplier>,
183	X: Get<Multiplier>,
184{
185	fn min() -> Multiplier {
186		M::get()
187	}
188	fn max() -> Multiplier {
189		X::get()
190	}
191	fn target() -> Perquintill {
192		S::get()
193	}
194	fn variability() -> Multiplier {
195		V::get()
196	}
197}
198
199impl<T, S, V, M, X> Convert<Multiplier, Multiplier> for TargetedFeeAdjustment<T, S, V, M, X>
200where
201	T: pezframe_system::Config,
202	S: Get<Perquintill>,
203	V: Get<Multiplier>,
204	M: Get<Multiplier>,
205	X: Get<Multiplier>,
206{
207	fn convert(previous: Multiplier) -> Multiplier {
208		// Defensive only. The multiplier in storage should always be at most positive. Nonetheless
209		// we recover here in case of errors, because any value below this would be stale and can
210		// never change.
211		let min_multiplier = M::get();
212		let max_multiplier = X::get();
213		let previous = previous.max(min_multiplier);
214
215		let weights = T::BlockWeights::get();
216		// the computed ratio is only among the normal class.
217		let normal_max_weight =
218			weights.get(DispatchClass::Normal).max_total.unwrap_or(weights.max_block);
219		let current_block_weight = pezframe_system::Pezpallet::<T>::block_weight();
220		let normal_block_weight =
221			current_block_weight.get(DispatchClass::Normal).min(normal_max_weight);
222
223		// Normalize dimensions so they can be compared. Ensure (defensive) max weight is non-zero.
224		let normalized_ref_time = Perbill::from_rational(
225			normal_block_weight.ref_time(),
226			normal_max_weight.ref_time().max(1),
227		);
228		let normalized_proof_size = Perbill::from_rational(
229			normal_block_weight.proof_size(),
230			normal_max_weight.proof_size().max(1),
231		);
232
233		// Pick the limiting dimension. If the proof size is the limiting dimension, then the
234		// multiplier is adjusted by the proof size. Otherwise, it is adjusted by the ref time.
235		let (normal_limiting_dimension, max_limiting_dimension) =
236			if normalized_ref_time < normalized_proof_size {
237				(normal_block_weight.proof_size(), normal_max_weight.proof_size())
238			} else {
239				(normal_block_weight.ref_time(), normal_max_weight.ref_time())
240			};
241
242		let target_block_fullness = S::get();
243		let adjustment_variable = V::get();
244
245		let target_weight = (target_block_fullness * max_limiting_dimension) as u128;
246		let block_weight = normal_limiting_dimension as u128;
247
248		// determines if the first_term is positive
249		let positive = block_weight >= target_weight;
250		let diff_abs = block_weight.max(target_weight) - block_weight.min(target_weight);
251
252		// defensive only, a test case assures that the maximum weight diff can fit in Multiplier
253		// without any saturation.
254		let diff = Multiplier::saturating_from_rational(diff_abs, max_limiting_dimension.max(1));
255		let diff_squared = diff.saturating_mul(diff);
256
257		let v_squared_2 = adjustment_variable.saturating_mul(adjustment_variable)
258			/ Multiplier::saturating_from_integer(2);
259
260		let first_term = adjustment_variable.saturating_mul(diff);
261		let second_term = v_squared_2.saturating_mul(diff_squared);
262
263		if positive {
264			let excess = first_term.saturating_add(second_term).saturating_mul(previous);
265			previous.saturating_add(excess).clamp(min_multiplier, max_multiplier)
266		} else {
267			// Defensive-only: first_term > second_term. Safe subtraction.
268			let negative = first_term.saturating_sub(second_term).saturating_mul(previous);
269			previous.saturating_sub(negative).clamp(min_multiplier, max_multiplier)
270		}
271	}
272}
273
274/// A struct to make the fee multiplier a constant
275pub struct ConstFeeMultiplier<M: Get<Multiplier>>(core::marker::PhantomData<M>);
276
277impl<M: Get<Multiplier>> MultiplierUpdate for ConstFeeMultiplier<M> {
278	fn min() -> Multiplier {
279		M::get()
280	}
281	fn max() -> Multiplier {
282		M::get()
283	}
284	fn target() -> Perquintill {
285		Default::default()
286	}
287	fn variability() -> Multiplier {
288		Default::default()
289	}
290}
291
292impl<M> Convert<Multiplier, Multiplier> for ConstFeeMultiplier<M>
293where
294	M: Get<Multiplier>,
295{
296	fn convert(_previous: Multiplier) -> Multiplier {
297		Self::min()
298	}
299}
300
301/// Storage releases of the pezpallet.
302#[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
303pub enum Releases {
304	/// Original version of the pezpallet.
305	V1Ancient,
306	/// One that bumps the usage to FixedU128 from FixedI128.
307	V2,
308}
309
310impl Default for Releases {
311	fn default() -> Self {
312		Releases::V1Ancient
313	}
314}
315
316/// Default value for NextFeeMultiplier. This is used in genesis and is also used in
317/// NextFeeMultiplierOnEmpty() to provide a value when none exists in storage.
318const MULTIPLIER_DEFAULT_VALUE: Multiplier = Multiplier::from_u32(1);
319
320#[pezframe_support::pezpallet]
321pub mod pezpallet {
322	use pezframe_support::pezpallet_prelude::*;
323	use pezframe_system::pezpallet_prelude::*;
324
325	use super::*;
326
327	#[pezpallet::pezpallet]
328	pub struct Pezpallet<T>(_);
329
330	pub mod config_preludes {
331		use super::*;
332		use pezframe_support::derive_impl;
333
334		/// Default prelude sensible to be used in a testing environment.
335		pub struct TestDefaultConfig;
336
337		#[derive_impl(pezframe_system::config_preludes::TestDefaultConfig, no_aggregated_types)]
338		impl pezframe_system::DefaultConfig for TestDefaultConfig {}
339
340		#[pezframe_support::register_default_impl(TestDefaultConfig)]
341		impl DefaultConfig for TestDefaultConfig {
342			#[inject_runtime_type]
343			type RuntimeEvent = ();
344			type FeeMultiplierUpdate = ();
345			type OperationalFeeMultiplier = ();
346			type WeightInfo = ();
347		}
348	}
349
350	#[pezpallet::config(with_default)]
351	pub trait Config: pezframe_system::Config {
352		/// The overarching event type.
353		#[pezpallet::no_default_bounds]
354		#[allow(deprecated)]
355		type RuntimeEvent: From<Event<Self>>
356			+ IsType<<Self as pezframe_system::Config>::RuntimeEvent>;
357
358		/// Handler for withdrawing, refunding and depositing the transaction fee.
359		/// Transaction fees are withdrawn before the transaction is executed.
360		/// After the transaction was executed the transaction weight can be
361		/// adjusted, depending on the used resources by the transaction. If the
362		/// transaction weight is lower than expected, parts of the transaction fee
363		/// might be refunded. In the end the fees can be deposited.
364		#[pezpallet::no_default]
365		type OnChargeTransaction: OnChargeTransaction<Self>;
366
367		/// Convert a weight value into a deductible fee based on the currency type.
368		#[pezpallet::no_default]
369		type WeightToFee: WeightToFee<Balance = BalanceOf<Self>>;
370
371		/// Convert a length value into a deductible fee based on the currency type.
372		#[pezpallet::no_default]
373		type LengthToFee: WeightToFee<Balance = BalanceOf<Self>>;
374
375		/// Update the multiplier of the next block, based on the previous block's weight.
376		type FeeMultiplierUpdate: MultiplierUpdate;
377
378		/// A fee multiplier for `Operational` extrinsics to compute "virtual tip" to boost their
379		/// `priority`
380		///
381		/// This value is multiplied by the `final_fee` to obtain a "virtual tip" that is later
382		/// added to a tip component in regular `priority` calculations.
383		/// It means that a `Normal` transaction can front-run a similarly-sized `Operational`
384		/// extrinsic (with no tip), by including a tip value greater than the virtual tip.
385		///
386		/// ```rust,ignore
387		/// // For `Normal`
388		/// let priority = priority_calc(tip);
389		///
390		/// // For `Operational`
391		/// let virtual_tip = (inclusion_fee + tip) * OperationalFeeMultiplier;
392		/// let priority = priority_calc(tip + virtual_tip);
393		/// ```
394		///
395		/// Note that since we use `final_fee` the multiplier applies also to the regular `tip`
396		/// sent with the transaction. So, not only does the transaction get a priority bump based
397		/// on the `inclusion_fee`, but we also amplify the impact of tips applied to `Operational`
398		/// transactions.
399		#[pezpallet::constant]
400		type OperationalFeeMultiplier: Get<u8>;
401
402		/// The weight information of this pezpallet.
403		type WeightInfo: WeightInfo;
404	}
405
406	#[pezpallet::type_value]
407	pub fn NextFeeMultiplierOnEmpty() -> Multiplier {
408		MULTIPLIER_DEFAULT_VALUE
409	}
410
411	#[pezpallet::storage]
412	#[pezpallet::whitelist_storage]
413	pub type NextFeeMultiplier<T: Config> =
414		StorageValue<_, Multiplier, ValueQuery, NextFeeMultiplierOnEmpty>;
415
416	#[pezpallet::storage]
417	pub type StorageVersion<T: Config> = StorageValue<_, Releases, ValueQuery>;
418
419	/// The `OnChargeTransaction` stores the withdrawn tx fee here.
420	///
421	/// Use `withdraw_txfee` and `remaining_txfee` to access from outside the crate.
422	#[pezpallet::storage]
423	#[pezpallet::whitelist_storage]
424	pub(crate) type TxPaymentCredit<T: Config> = StorageValue<_, StoredCreditOf<T>>;
425
426	#[pezpallet::genesis_config]
427	pub struct GenesisConfig<T: Config> {
428		pub multiplier: Multiplier,
429		#[serde(skip)]
430		pub _config: core::marker::PhantomData<T>,
431	}
432
433	impl<T: Config> Default for GenesisConfig<T> {
434		fn default() -> Self {
435			Self { multiplier: MULTIPLIER_DEFAULT_VALUE, _config: Default::default() }
436		}
437	}
438
439	#[pezpallet::genesis_build]
440	impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
441		fn build(&self) {
442			StorageVersion::<T>::put(Releases::V2);
443			NextFeeMultiplier::<T>::put(self.multiplier);
444		}
445	}
446
447	#[pezpallet::event]
448	#[pezpallet::generate_deposit(pub(super) fn deposit_event)]
449	pub enum Event<T: Config> {
450		/// A transaction fee `actual_fee`, of which `tip` was added to the minimum inclusion fee,
451		/// has been paid by `who`.
452		TransactionFeePaid { who: T::AccountId, actual_fee: BalanceOf<T>, tip: BalanceOf<T> },
453	}
454
455	#[pezpallet::hooks]
456	impl<T: Config> Hooks<BlockNumberFor<T>> for Pezpallet<T> {
457		fn on_finalize(_: pezframe_system::pezpallet_prelude::BlockNumberFor<T>) {
458			NextFeeMultiplier::<T>::mutate(|fm| {
459				*fm = T::FeeMultiplierUpdate::convert(*fm);
460			});
461
462			// We generally expect the `OnChargeTransaction` implementation to delete this value
463			// after each transaction. To make sure it is never stored between blocks we
464			// delete the value here just in case.
465			TxPaymentCredit::<T>::take().map(|credit| {
466				log::error!(target: LOG_TARGET, "The `TxPaymentCredit` was stored between blocks. This is a bug.");
467				// Converting to inner makes sure that the drop implementation is called.
468				credit.into_inner()
469			});
470		}
471
472		#[cfg(feature = "std")]
473		fn integrity_test() {
474			// given weight == u64, we build multipliers from `diff` of two weight values, which can
475			// at most be maximum block weight. Make sure that this can fit in a multiplier without
476			// loss.
477			assert!(
478				<Multiplier as pezsp_runtime::traits::Bounded>::max_value()
479					>= Multiplier::checked_from_integer::<u128>(
480						T::BlockWeights::get().max_block.ref_time().try_into().unwrap()
481					)
482					.unwrap(),
483			);
484
485			let target = T::FeeMultiplierUpdate::target()
486				* T::BlockWeights::get().get(DispatchClass::Normal).max_total.expect(
487					"Setting `max_total` for `Normal` dispatch class is not compatible with \
488					`transaction-payment` pezpallet.",
489				);
490			// add 1 percent;
491			let addition = target / 100;
492			if addition == Weight::zero() {
493				// this is most likely because in a test setup we set everything to ()
494				// or to `ConstFeeMultiplier`.
495				return;
496			}
497
498			// This is the minimum value of the multiplier. Make sure that if we collapse to this
499			// value, we can recover with a reasonable amount of traffic. For this test we assert
500			// that if we collapse to minimum, the trend will be positive with a weight value which
501			// is 1% more than the target.
502			let min_value = T::FeeMultiplierUpdate::min();
503			let target = target + addition;
504
505			pezframe_system::Pezpallet::<T>::set_block_consumed_resources(target, 0);
506			let next = T::FeeMultiplierUpdate::convert(min_value);
507			assert!(
508				next > min_value,
509				"The minimum bound of the multiplier is too low. When \
510				block saturation is more than target by 1% and multiplier is minimal then \
511				the multiplier doesn't increase."
512			);
513		}
514	}
515}
516
517impl<T: Config> Pezpallet<T> {
518	/// Public function to access the next fee multiplier.
519	pub fn next_fee_multiplier() -> Multiplier {
520		NextFeeMultiplier::<T>::get()
521	}
522
523	/// Query the data that we know about the fee of a given `call`.
524	///
525	/// This pezpallet is not and cannot be aware of the internals of a signed extension, for
526	/// example a tip. It only interprets the extrinsic as some encoded value and accounts for its
527	/// weight and length, the runtime's extrinsic base weight, and the current fee multiplier.
528	///
529	/// All dispatchables must be annotated with weight and will have some fee info. This function
530	/// always returns.
531	pub fn query_info<Extrinsic: pezsp_runtime::traits::ExtrinsicLike + GetDispatchInfo>(
532		unchecked_extrinsic: Extrinsic,
533		len: u32,
534	) -> RuntimeDispatchInfo<BalanceOf<T>>
535	where
536		T::RuntimeCall: Dispatchable<Info = DispatchInfo>,
537	{
538		// NOTE: we can actually make it understand `ChargeTransactionPayment`, but would be some
539		// hassle for sure. We have to make it aware of the index of `ChargeTransactionPayment` in
540		// `Extra`. Alternatively, we could actually execute the tx's per-dispatch and record the
541		// balance of the sender before and after the pipeline.. but this is way too much hassle for
542		// a very very little potential gain in the future.
543		let dispatch_info = <Extrinsic as GetDispatchInfo>::get_dispatch_info(&unchecked_extrinsic);
544
545		let partial_fee = if unchecked_extrinsic.is_bare() {
546			// Bare extrinsics have no partial fee.
547			0u32.into()
548		} else {
549			Self::compute_fee(len, &dispatch_info, 0u32.into())
550		};
551
552		let DispatchInfo { class, .. } = dispatch_info;
553
554		RuntimeDispatchInfo { weight: dispatch_info.total_weight(), class, partial_fee }
555	}
556
557	/// Query the detailed fee of a given `call`.
558	pub fn query_fee_details<Extrinsic: pezsp_runtime::traits::ExtrinsicLike + GetDispatchInfo>(
559		unchecked_extrinsic: Extrinsic,
560		len: u32,
561	) -> FeeDetails<BalanceOf<T>>
562	where
563		T::RuntimeCall: Dispatchable<Info = DispatchInfo>,
564	{
565		let dispatch_info = <Extrinsic as GetDispatchInfo>::get_dispatch_info(&unchecked_extrinsic);
566
567		let tip = 0u32.into();
568
569		if unchecked_extrinsic.is_bare() {
570			// Bare extrinsics have no inclusion fee.
571			FeeDetails { inclusion_fee: None, tip }
572		} else {
573			Self::compute_fee_details(len, &dispatch_info, tip)
574		}
575	}
576
577	/// Query information of a dispatch class, weight, and fee of a given encoded `Call`.
578	pub fn query_call_info(call: T::RuntimeCall, len: u32) -> RuntimeDispatchInfo<BalanceOf<T>>
579	where
580		T::RuntimeCall: Dispatchable<Info = DispatchInfo> + GetDispatchInfo,
581	{
582		let dispatch_info = <T::RuntimeCall as GetDispatchInfo>::get_dispatch_info(&call);
583		let DispatchInfo { class, .. } = dispatch_info;
584
585		RuntimeDispatchInfo {
586			weight: dispatch_info.total_weight(),
587			class,
588			partial_fee: Self::compute_fee(len, &dispatch_info, 0u32.into()),
589		}
590	}
591
592	/// Query fee details of a given encoded `Call`.
593	pub fn query_call_fee_details(call: T::RuntimeCall, len: u32) -> FeeDetails<BalanceOf<T>>
594	where
595		T::RuntimeCall: Dispatchable<Info = DispatchInfo> + GetDispatchInfo,
596	{
597		let dispatch_info = <T::RuntimeCall as GetDispatchInfo>::get_dispatch_info(&call);
598		let tip = 0u32.into();
599
600		Self::compute_fee_details(len, &dispatch_info, tip)
601	}
602
603	/// Compute the final fee value (including tip) for a particular transaction.
604	pub fn compute_fee(
605		len: u32,
606		info: &DispatchInfoOf<T::RuntimeCall>,
607		tip: BalanceOf<T>,
608	) -> BalanceOf<T>
609	where
610		T::RuntimeCall: Dispatchable<Info = DispatchInfo>,
611	{
612		Self::compute_fee_details(len, info, tip).final_fee()
613	}
614
615	/// Compute the fee details for a particular transaction.
616	pub fn compute_fee_details(
617		len: u32,
618		info: &DispatchInfoOf<T::RuntimeCall>,
619		tip: BalanceOf<T>,
620	) -> FeeDetails<BalanceOf<T>>
621	where
622		T::RuntimeCall: Dispatchable<Info = DispatchInfo>,
623	{
624		Self::compute_fee_raw(len, info.total_weight(), tip, info.pays_fee, info.class)
625	}
626
627	/// Compute the actual post dispatch fee for a particular transaction.
628	///
629	/// Identical to `compute_fee` with the only difference that the post dispatch corrected
630	/// weight is used for the weight fee calculation.
631	pub fn compute_actual_fee(
632		len: u32,
633		info: &DispatchInfoOf<T::RuntimeCall>,
634		post_info: &PostDispatchInfoOf<T::RuntimeCall>,
635		tip: BalanceOf<T>,
636	) -> BalanceOf<T>
637	where
638		T::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
639	{
640		Self::compute_actual_fee_details(len, info, post_info, tip).final_fee()
641	}
642
643	/// Compute the actual post dispatch fee details for a particular transaction.
644	pub fn compute_actual_fee_details(
645		len: u32,
646		info: &DispatchInfoOf<T::RuntimeCall>,
647		post_info: &PostDispatchInfoOf<T::RuntimeCall>,
648		tip: BalanceOf<T>,
649	) -> FeeDetails<BalanceOf<T>>
650	where
651		T::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
652	{
653		Self::compute_fee_raw(
654			len,
655			post_info.calc_actual_weight(info),
656			tip,
657			post_info.pays_fee(info),
658			info.class,
659		)
660	}
661
662	fn compute_fee_raw(
663		len: u32,
664		weight: Weight,
665		tip: BalanceOf<T>,
666		pays_fee: Pays,
667		class: DispatchClass,
668	) -> FeeDetails<BalanceOf<T>> {
669		if pays_fee == Pays::Yes {
670			// the adjustable part of the fee.
671			let unadjusted_weight_fee = Self::weight_to_fee(weight);
672			let multiplier = NextFeeMultiplier::<T>::get();
673			// final adjusted weight fee.
674			let adjusted_weight_fee = multiplier.saturating_mul_int(unadjusted_weight_fee);
675
676			// length fee. this is adjusted via `LengthToFee`.
677			let len_fee = Self::length_to_fee(len);
678
679			let base_fee = Self::weight_to_fee(T::BlockWeights::get().get(class).base_extrinsic);
680			FeeDetails {
681				inclusion_fee: Some(InclusionFee { base_fee, len_fee, adjusted_weight_fee }),
682				tip,
683			}
684		} else {
685			FeeDetails { inclusion_fee: None, tip }
686		}
687	}
688
689	/// Compute the length portion of a fee by invoking the configured `LengthToFee` impl.
690	pub fn length_to_fee(length: u32) -> BalanceOf<T> {
691		T::LengthToFee::weight_to_fee(&Weight::from_parts(length as u64, 0))
692	}
693
694	/// Compute the unadjusted portion of the weight fee by invoking the configured `WeightToFee`
695	/// impl. Note that the input `weight` is capped by the maximum block weight before computation.
696	pub fn weight_to_fee(weight: Weight) -> BalanceOf<T> {
697		// cap the weight to the maximum defined in runtime, otherwise it will be the
698		// `Bounded` maximum of its data type, which is not desired.
699		let capped_weight = weight.min(T::BlockWeights::get().max_block);
700		T::WeightToFee::weight_to_fee(&capped_weight)
701	}
702
703	/// Deposit the [`Event::TransactionFeePaid`] event.
704	pub fn deposit_fee_paid_event(who: T::AccountId, actual_fee: BalanceOf<T>, tip: BalanceOf<T>) {
705		Self::deposit_event(Event::TransactionFeePaid { who, actual_fee, tip });
706	}
707
708	/// Withdraw `amount` from the currents transaction's fees.
709	///
710	/// If enough balance is available a credit of size `amount` is returned.
711	///
712	/// # Warning
713	///
714	/// Do **not** use this to pay for Weight fees. Use only to pay for storage fees
715	/// that can be rolled back by a storage transaction.
716	///
717	/// # Note
718	///
719	/// This is only useful if a pezpallet knows that the pre-dispatch weight was vastly
720	/// overestimated. Pallets need to make sure to leave enough balance to pay for the
721	/// transaction fees. They can do that by first drawing as much as they need and then
722	/// at the end of the transaction (when they know the post dispatch fee) return an error
723	/// in case not enough is left. The error will automatically roll back all the storage
724	/// changes done by the pezpallet including the balance drawn by calling this function.
725	pub fn withdraw_txfee<Balance>(amount: Balance) -> Option<CreditOf<T>>
726	where
727		CreditOf<T>: Imbalance<Balance>,
728		Balance: PartialOrd,
729	{
730		<TxPaymentCredit<T>>::mutate(|credit| {
731			let credit = SuppressedDrop::as_mut(credit.as_mut()?);
732			if amount > credit.peek() {
733				return None;
734			}
735			Some(credit.extract(amount))
736		})
737	}
738
739	/// Deposit some additional balance.
740	pub fn deposit_txfee<Balance>(deposit: CreditOf<T>)
741	where
742		CreditOf<T>: Imbalance<Balance>,
743	{
744		<TxPaymentCredit<T>>::mutate(|credit| {
745			if let Some(credit) = credit.as_mut().map(SuppressedDrop::as_mut) {
746				credit.subsume(deposit);
747			} else {
748				*credit = Some(SuppressedDrop::new(deposit))
749			}
750		});
751	}
752
753	/// Return how much balance is currently available to pay for the transaction.
754	///
755	/// Does **not** include the tip.
756	///
757	/// If noone calls `charge_from_txfee` it is the same as the pre dispatch fee.
758	pub fn remaining_txfee<Balance>() -> Balance
759	where
760		CreditOf<T>: Imbalance<Balance>,
761		Balance: Default,
762	{
763		<TxPaymentCredit<T>>::get()
764			.map(|c| SuppressedDrop::as_ref(&c).peek())
765			.unwrap_or_default()
766	}
767}
768
769impl<T> Convert<Weight, BalanceOf<T>> for Pezpallet<T>
770where
771	T: Config,
772{
773	/// Compute the fee for the specified weight.
774	///
775	/// This fee is already adjusted by the per block fee adjustment factor and is therefore the
776	/// share that the weight contributes to the overall fee of a transaction. It is mainly
777	/// for informational purposes and not used in the actual fee calculation.
778	fn convert(weight: Weight) -> BalanceOf<T> {
779		NextFeeMultiplier::<T>::get().saturating_mul_int(Self::weight_to_fee(weight))
780	}
781}
782
783/// Require the transactor pay for themselves and maybe include a tip to gain additional priority
784/// in the queue.
785///
786/// # Transaction Validity
787///
788/// This extension sets the `priority` field of `TransactionValidity` depending on the amount
789/// of tip being paid per weight unit.
790///
791/// Operational transactions will receive an additional priority bump, so that they are normally
792/// considered before regular transactions.
793#[derive(Encode, Decode, DecodeWithMemTracking, Clone, Eq, PartialEq, TypeInfo)]
794#[scale_info(skip_type_params(T))]
795pub struct ChargeTransactionPayment<T: Config>(#[codec(compact)] BalanceOf<T>);
796
797impl<T: Config> ChargeTransactionPayment<T>
798where
799	T::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
800	BalanceOf<T>: Send + Sync,
801{
802	/// utility constructor. Used only in client/factory code.
803	pub fn from(fee: BalanceOf<T>) -> Self {
804		Self(fee)
805	}
806
807	/// Returns the tip as being chosen by the transaction sender.
808	pub fn tip(&self) -> BalanceOf<T> {
809		self.0
810	}
811
812	fn withdraw_fee(
813		&self,
814		who: &T::AccountId,
815		call: &T::RuntimeCall,
816		info: &DispatchInfoOf<T::RuntimeCall>,
817		fee_with_tip: BalanceOf<T>,
818	) -> Result<
819		(
820			BalanceOf<T>,
821			<<T as Config>::OnChargeTransaction as OnChargeTransaction<T>>::LiquidityInfo,
822		),
823		TransactionValidityError,
824	> {
825		let tip = self.0;
826
827		<<T as Config>::OnChargeTransaction as OnChargeTransaction<T>>::withdraw_fee(
828			who,
829			call,
830			info,
831			fee_with_tip,
832			tip,
833		)
834		.map(|liquidity_info| (fee_with_tip, liquidity_info))
835	}
836
837	fn can_withdraw_fee(
838		&self,
839		who: &T::AccountId,
840		call: &T::RuntimeCall,
841		info: &DispatchInfoOf<T::RuntimeCall>,
842		len: usize,
843	) -> Result<BalanceOf<T>, TransactionValidityError> {
844		let tip = self.0;
845		let fee_with_tip = Pezpallet::<T>::compute_fee(len as u32, info, tip);
846
847		<<T as Config>::OnChargeTransaction as OnChargeTransaction<T>>::can_withdraw_fee(
848			who,
849			call,
850			info,
851			fee_with_tip,
852			tip,
853		)?;
854		Ok(fee_with_tip)
855	}
856
857	/// Get an appropriate priority for a transaction with the given `DispatchInfo`, encoded length
858	/// and user-included tip.
859	///
860	/// The priority is based on the amount of `tip` the user is willing to pay per unit of either
861	/// `weight` or `length`, depending which one is more limiting. For `Operational` extrinsics
862	/// we add a "virtual tip" to the calculations.
863	///
864	/// The formula should simply be `tip / bounded_{weight|length}`, but since we are using
865	/// integer division, we have no guarantees it's going to give results in any reasonable
866	/// range (might simply end up being zero). Hence we use a scaling factor:
867	/// `tip * (max_block_{weight|length} / bounded_{weight|length})`, since given current
868	/// state of-the-art blockchains, number of per-block transactions is expected to be in a
869	/// range reasonable enough to not saturate the `Balance` type while multiplying by the tip.
870	pub fn get_priority(
871		info: &DispatchInfoOf<T::RuntimeCall>,
872		len: usize,
873		tip: BalanceOf<T>,
874		final_fee_with_tip: BalanceOf<T>,
875	) -> TransactionPriority {
876		// Calculate how many such extrinsics we could fit into an empty block and take the
877		// limiting factor.
878		let max_block_weight = T::BlockWeights::get().max_block;
879		let max_block_length = *T::BlockLength::get().max.get(info.class) as u64;
880
881		// bounded_weight is used as a divisor later so we keep it non-zero.
882		let bounded_weight =
883			info.total_weight().max(Weight::from_parts(1, 1)).min(max_block_weight);
884		let bounded_length = (len as u64).clamp(1, max_block_length);
885
886		// returns the scarce resource, i.e. the one that is limiting the number of transactions.
887		let max_tx_per_block_weight = max_block_weight
888			.checked_div_per_component(&bounded_weight)
889			.defensive_proof("bounded_weight is non-zero; qed")
890			.unwrap_or(1);
891		let max_tx_per_block_length = max_block_length / bounded_length;
892		// Given our current knowledge this value is going to be in a reasonable range - i.e.
893		// less than 10^9 (2^30), so multiplying by the `tip` value is unlikely to overflow the
894		// balance type. We still use saturating ops obviously, but the point is to end up with some
895		// `priority` distribution instead of having all transactions saturate the priority.
896		let max_tx_per_block = max_tx_per_block_length
897			.min(max_tx_per_block_weight)
898			.saturated_into::<BalanceOf<T>>();
899		let max_reward = |val: BalanceOf<T>| val.saturating_mul(max_tx_per_block);
900
901		// To distribute no-tip transactions a little bit, we increase the tip value by one.
902		// This means that given two transactions without a tip, smaller one will be preferred.
903		let tip = tip.saturating_add(One::one());
904		let scaled_tip = max_reward(tip);
905
906		match info.class {
907			DispatchClass::Normal => {
908				// For normal class we simply take the `tip_per_weight`.
909				scaled_tip
910			},
911			DispatchClass::Mandatory => {
912				// Mandatory extrinsics should be prohibited (e.g. by the [`CheckWeight`]
913				// extensions), but just to be safe let's return the same priority as `Normal` here.
914				scaled_tip
915			},
916			DispatchClass::Operational => {
917				// A "virtual tip" value added to an `Operational` extrinsic.
918				// This value should be kept high enough to allow `Operational` extrinsics
919				// to get in even during congestion period, but at the same time low
920				// enough to prevent a possible spam attack by sending invalid operational
921				// extrinsics which push away regular transactions from the pool.
922				let fee_multiplier = T::OperationalFeeMultiplier::get().saturated_into();
923				let virtual_tip = final_fee_with_tip.saturating_mul(fee_multiplier);
924				let scaled_virtual_tip = max_reward(virtual_tip);
925
926				scaled_tip.saturating_add(scaled_virtual_tip)
927			},
928		}
929		.saturated_into::<TransactionPriority>()
930	}
931}
932
933impl<T: Config> core::fmt::Debug for ChargeTransactionPayment<T> {
934	#[cfg(feature = "std")]
935	fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
936		write!(f, "ChargeTransactionPayment<{:?}>", self.0)
937	}
938	#[cfg(not(feature = "std"))]
939	fn fmt(&self, _: &mut core::fmt::Formatter) -> core::fmt::Result {
940		Ok(())
941	}
942}
943
944/// The info passed between the validate and prepare steps for the `ChargeAssetTxPayment` extension.
945#[derive(RuntimeDebugNoBound)]
946pub enum Val<T: Config> {
947	Charge {
948		tip: BalanceOf<T>,
949		// who paid the fee
950		who: T::AccountId,
951		// transaction fee
952		fee_with_tip: BalanceOf<T>,
953	},
954	NoCharge,
955}
956
957/// The info passed between the prepare and post-dispatch steps for the `ChargeAssetTxPayment`
958/// extension.
959pub enum Pre<T: Config> {
960	Charge {
961		tip: BalanceOf<T>,
962		// who paid the fee
963		who: T::AccountId,
964		// implementation defined type that is passed into the post charge function
965		liquidity_info:
966			<<T as Config>::OnChargeTransaction as OnChargeTransaction<T>>::LiquidityInfo,
967	},
968	NoCharge {
969		// weight initially estimated by the extension, to be refunded
970		refund: Weight,
971	},
972}
973
974impl<T: Config> core::fmt::Debug for Pre<T> {
975	#[cfg(feature = "std")]
976	fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
977		match self {
978			Pre::Charge { tip, who, liquidity_info: _ } => {
979				write!(f, "Charge {{ tip: {:?}, who: {:?}, imbalance: <stripped> }}", tip, who)
980			},
981			Pre::NoCharge { refund } => write!(f, "NoCharge {{ refund: {:?} }}", refund),
982		}
983	}
984
985	#[cfg(not(feature = "std"))]
986	fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
987		f.write_str("<wasm:stripped>")
988	}
989}
990
991impl<T: Config> TransactionExtension<T::RuntimeCall> for ChargeTransactionPayment<T>
992where
993	T::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
994{
995	const IDENTIFIER: &'static str = "ChargeTransactionPayment";
996	type Implicit = ();
997	type Val = Val<T>;
998	type Pre = Pre<T>;
999
1000	fn weight(&self, _: &T::RuntimeCall) -> Weight {
1001		T::WeightInfo::charge_transaction_payment()
1002	}
1003
1004	fn validate(
1005		&self,
1006		origin: <T::RuntimeCall as Dispatchable>::RuntimeOrigin,
1007		call: &T::RuntimeCall,
1008		info: &DispatchInfoOf<T::RuntimeCall>,
1009		len: usize,
1010		_: (),
1011		_implication: &impl Encode,
1012		_source: TransactionSource,
1013	) -> Result<
1014		(ValidTransaction, Self::Val, <T::RuntimeCall as Dispatchable>::RuntimeOrigin),
1015		TransactionValidityError,
1016	> {
1017		let Ok(who) = pezframe_system::ensure_signed(origin.clone()) else {
1018			return Ok((ValidTransaction::default(), Val::NoCharge, origin));
1019		};
1020		let fee_with_tip = self.can_withdraw_fee(&who, call, info, len)?;
1021		let tip = self.0;
1022		Ok((
1023			ValidTransaction {
1024				priority: Self::get_priority(info, len, tip, fee_with_tip),
1025				..Default::default()
1026			},
1027			Val::Charge { tip: self.0, who, fee_with_tip },
1028			origin,
1029		))
1030	}
1031
1032	fn prepare(
1033		self,
1034		val: Self::Val,
1035		_origin: &<T::RuntimeCall as Dispatchable>::RuntimeOrigin,
1036		call: &T::RuntimeCall,
1037		info: &DispatchInfoOf<T::RuntimeCall>,
1038		_len: usize,
1039	) -> Result<Self::Pre, TransactionValidityError> {
1040		match val {
1041			Val::Charge { tip, who, fee_with_tip } => {
1042				// Mutating call to `withdraw_fee` to actually charge for the transaction.
1043				let (_fee_with_tip, liquidity_info) =
1044					self.withdraw_fee(&who, call, info, fee_with_tip)?;
1045				Ok(Pre::Charge { tip, who, liquidity_info })
1046			},
1047			Val::NoCharge => Ok(Pre::NoCharge { refund: self.weight(call) }),
1048		}
1049	}
1050
1051	fn post_dispatch_details(
1052		pre: Self::Pre,
1053		info: &DispatchInfoOf<T::RuntimeCall>,
1054		post_info: &PostDispatchInfoOf<T::RuntimeCall>,
1055		len: usize,
1056		_result: &DispatchResult,
1057	) -> Result<Weight, TransactionValidityError> {
1058		let (tip, who, liquidity_info) = match pre {
1059			Pre::Charge { tip, who, liquidity_info } => (tip, who, liquidity_info),
1060			Pre::NoCharge { refund } => {
1061				// No-op: Refund everything
1062				return Ok(refund);
1063			},
1064		};
1065		let actual_fee_with_tip =
1066			Pezpallet::<T>::compute_actual_fee(len as u32, info, &post_info, tip);
1067		T::OnChargeTransaction::correct_and_deposit_fee(
1068			&who,
1069			info,
1070			&post_info,
1071			actual_fee_with_tip,
1072			tip,
1073			liquidity_info,
1074		)?;
1075		Pezpallet::<T>::deposit_event(Event::<T>::TransactionFeePaid {
1076			who,
1077			actual_fee: actual_fee_with_tip,
1078			tip,
1079		});
1080		Ok(Weight::zero())
1081	}
1082}
1083
1084impl<T: Config, AnyCall: GetDispatchInfo + Encode> EstimateCallFee<AnyCall, BalanceOf<T>>
1085	for Pezpallet<T>
1086where
1087	T::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
1088{
1089	fn estimate_call_fee(call: &AnyCall, post_info: PostDispatchInfo) -> BalanceOf<T> {
1090		let len = call.encoded_size() as u32;
1091		let info = call.get_dispatch_info();
1092		Self::compute_actual_fee(len, &info, &post_info, Zero::zero())
1093	}
1094}