pallet_transaction_payment/
payment.rs1use crate::{Config, Pallet, TxPaymentCredit, LOG_TARGET};
20
21use codec::{DecodeWithMemTracking, FullCodec, MaxEncodedLen};
22use core::marker::PhantomData;
23use frame_support::{
24 traits::{
25 fungible::{Balanced, Credit, Inspect},
26 tokens::{Precision, WithdrawConsequence},
27 Imbalance, NoDrop, OnUnbalanced, SuppressedDrop,
28 },
29 unsigned::TransactionValidityError,
30};
31use scale_info::TypeInfo;
32use sp_runtime::{
33 traits::{DispatchInfoOf, PostDispatchInfoOf, Saturating, Zero},
34 transaction_validity::InvalidTransaction,
35};
36
37pub trait OnChargeTransaction<T: Config>: TxCreditHold<T> {
39 type Balance: frame_support::traits::tokens::Balance;
41
42 type LiquidityInfo: Default;
43
44 fn withdraw_fee(
49 who: &T::AccountId,
50 call: &T::RuntimeCall,
51 dispatch_info: &DispatchInfoOf<T::RuntimeCall>,
52 fee_with_tip: Self::Balance,
53 tip: Self::Balance,
54 ) -> Result<Self::LiquidityInfo, TransactionValidityError>;
55
56 fn can_withdraw_fee(
58 who: &T::AccountId,
59 call: &T::RuntimeCall,
60 dispatch_info: &DispatchInfoOf<T::RuntimeCall>,
61 fee_with_tip: Self::Balance,
62 tip: Self::Balance,
63 ) -> Result<(), TransactionValidityError>;
64
65 fn correct_and_deposit_fee(
71 who: &T::AccountId,
72 dispatch_info: &DispatchInfoOf<T::RuntimeCall>,
73 post_info: &PostDispatchInfoOf<T::RuntimeCall>,
74 corrected_fee_with_tip: Self::Balance,
75 tip: Self::Balance,
76 liquidity_info: Self::LiquidityInfo,
77 ) -> Result<(), TransactionValidityError>;
78
79 #[cfg(feature = "runtime-benchmarks")]
80 fn endow_account(who: &T::AccountId, amount: Self::Balance);
81
82 #[cfg(feature = "runtime-benchmarks")]
83 fn minimum_balance() -> Self::Balance;
84}
85
86pub trait TxCreditHold<T: Config> {
91 type Credit: FullCodec + DecodeWithMemTracking + MaxEncodedLen + TypeInfo + SuppressedDrop;
101}
102
103pub struct FungibleAdapter<F, OU>(PhantomData<(F, OU)>);
110
111impl<T, F, OU> OnChargeTransaction<T> for FungibleAdapter<F, OU>
112where
113 T: Config,
114 T::OnChargeTransaction: TxCreditHold<T, Credit = NoDrop<Credit<T::AccountId, F>>>,
115 F: Balanced<T::AccountId> + 'static,
116 OU: OnUnbalanced<<Self::Credit as SuppressedDrop>::Inner>,
117{
118 type LiquidityInfo = Option<<Self::Credit as SuppressedDrop>::Inner>;
119 type Balance = <F as Inspect<<T as frame_system::Config>::AccountId>>::Balance;
120
121 fn withdraw_fee(
122 who: &<T>::AccountId,
123 _call: &<T>::RuntimeCall,
124 _dispatch_info: &DispatchInfoOf<<T>::RuntimeCall>,
125 fee_with_tip: Self::Balance,
126 tip: Self::Balance,
127 ) -> Result<Self::LiquidityInfo, TransactionValidityError> {
128 if fee_with_tip.is_zero() {
129 return Ok(None);
130 }
131
132 let credit = F::withdraw(
133 who,
134 fee_with_tip,
135 Precision::Exact,
136 frame_support::traits::tokens::Preservation::Preserve,
137 frame_support::traits::tokens::Fortitude::Polite,
138 )
139 .map_err(|_| InvalidTransaction::Payment)?;
140
141 let (tip_credit, inclusion_fee) = credit.split(tip);
142
143 <Pallet<T>>::deposit_txfee(inclusion_fee);
144
145 Ok(Some(tip_credit))
146 }
147
148 fn can_withdraw_fee(
149 who: &T::AccountId,
150 _call: &T::RuntimeCall,
151 _dispatch_info: &DispatchInfoOf<T::RuntimeCall>,
152 fee_with_tip: Self::Balance,
153 _tip: Self::Balance,
154 ) -> Result<(), TransactionValidityError> {
155 if fee_with_tip.is_zero() {
156 return Ok(());
157 }
158
159 match F::can_withdraw(who, fee_with_tip) {
160 WithdrawConsequence::Success => Ok(()),
161 _ => Err(InvalidTransaction::Payment.into()),
162 }
163 }
164
165 fn correct_and_deposit_fee(
166 who: &<T>::AccountId,
167 _dispatch_info: &DispatchInfoOf<<T>::RuntimeCall>,
168 _post_info: &PostDispatchInfoOf<<T>::RuntimeCall>,
169 corrected_fee_with_tip: Self::Balance,
170 tip: Self::Balance,
171 tip_credit: Self::LiquidityInfo,
172 ) -> Result<(), TransactionValidityError> {
173 let corrected_fee = corrected_fee_with_tip.saturating_sub(tip);
174
175 let remaining_credit = <TxPaymentCredit<T>>::take()
176 .map(|stored_credit| stored_credit.into_inner())
177 .unwrap_or_default();
178
179 if remaining_credit.peek() < corrected_fee {
183 log::error!(target: LOG_TARGET, "Not enough balance on hold to pay tx fees. This is a bug.");
184 }
185
186 let fee_credit = if frame_system::Pallet::<T>::account_exists(who) {
188 let (mut fee_credit, refund_credit) = remaining_credit.split(corrected_fee);
189 if !refund_credit.peek().is_zero() {
192 if let Err(not_refunded) = F::resolve(who, refund_credit) {
193 fee_credit.subsume(not_refunded);
194 }
195 }
196 fee_credit
197 } else {
198 remaining_credit
199 };
200
201 OU::on_unbalanceds(Some(fee_credit).into_iter().chain(tip_credit));
202
203 Ok(())
204 }
205
206 #[cfg(feature = "runtime-benchmarks")]
207 fn endow_account(who: &T::AccountId, amount: Self::Balance) {
208 let _ = F::deposit(who, amount, Precision::BestEffort);
209 }
210
211 #[cfg(feature = "runtime-benchmarks")]
212 fn minimum_balance() -> Self::Balance {
213 F::minimum_balance()
214 }
215}
216
217impl<T, F, OU> TxCreditHold<T> for FungibleAdapter<F, OU>
218where
219 T: Config,
220 F: Balanced<T::AccountId> + 'static,
221{
222 type Credit = NoDrop<Credit<<T as frame_system::Config>::AccountId, F>>;
223}