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 Currency, ExistenceRequirement, Imbalance, NoDrop, OnUnbalanced, SuppressedDrop,
28 WithdrawReasons,
29 },
30 unsigned::TransactionValidityError,
31};
32use scale_info::TypeInfo;
33use sp_runtime::{
34 traits::{CheckedSub, DispatchInfoOf, PostDispatchInfoOf, Saturating, Zero},
35 transaction_validity::InvalidTransaction,
36};
37
38type NegativeImbalanceOf<C, T> =
39 <C as Currency<<T as frame_system::Config>::AccountId>>::NegativeImbalance;
40
41pub trait OnChargeTransaction<T: Config>: TxCreditHold<T> {
43 type Balance: frame_support::traits::tokens::Balance;
45
46 type LiquidityInfo: Default;
47
48 fn withdraw_fee(
53 who: &T::AccountId,
54 call: &T::RuntimeCall,
55 dispatch_info: &DispatchInfoOf<T::RuntimeCall>,
56 fee_with_tip: Self::Balance,
57 tip: Self::Balance,
58 ) -> Result<Self::LiquidityInfo, TransactionValidityError>;
59
60 fn can_withdraw_fee(
62 who: &T::AccountId,
63 call: &T::RuntimeCall,
64 dispatch_info: &DispatchInfoOf<T::RuntimeCall>,
65 fee_with_tip: Self::Balance,
66 tip: Self::Balance,
67 ) -> Result<(), TransactionValidityError>;
68
69 fn correct_and_deposit_fee(
75 who: &T::AccountId,
76 dispatch_info: &DispatchInfoOf<T::RuntimeCall>,
77 post_info: &PostDispatchInfoOf<T::RuntimeCall>,
78 corrected_fee_with_tip: Self::Balance,
79 tip: Self::Balance,
80 liquidity_info: Self::LiquidityInfo,
81 ) -> Result<(), TransactionValidityError>;
82
83 #[cfg(feature = "runtime-benchmarks")]
84 fn endow_account(who: &T::AccountId, amount: Self::Balance);
85
86 #[cfg(feature = "runtime-benchmarks")]
87 fn minimum_balance() -> Self::Balance;
88}
89
90pub trait TxCreditHold<T: Config> {
95 type Credit: FullCodec + DecodeWithMemTracking + MaxEncodedLen + TypeInfo + SuppressedDrop;
105}
106
107pub struct FungibleAdapter<F, OU>(PhantomData<(F, OU)>);
114
115impl<T, F, OU> OnChargeTransaction<T> for FungibleAdapter<F, OU>
116where
117 T: Config,
118 T::OnChargeTransaction: TxCreditHold<T, Credit = NoDrop<Credit<T::AccountId, F>>>,
119 F: Balanced<T::AccountId> + 'static,
120 OU: OnUnbalanced<<Self::Credit as SuppressedDrop>::Inner>,
121{
122 type LiquidityInfo = Option<<Self::Credit as SuppressedDrop>::Inner>;
123 type Balance = <F as Inspect<<T as frame_system::Config>::AccountId>>::Balance;
124
125 fn withdraw_fee(
126 who: &<T>::AccountId,
127 _call: &<T>::RuntimeCall,
128 _dispatch_info: &DispatchInfoOf<<T>::RuntimeCall>,
129 fee_with_tip: Self::Balance,
130 tip: Self::Balance,
131 ) -> Result<Self::LiquidityInfo, TransactionValidityError> {
132 if fee_with_tip.is_zero() {
133 return Ok(None)
134 }
135
136 let credit = F::withdraw(
137 who,
138 fee_with_tip,
139 Precision::Exact,
140 frame_support::traits::tokens::Preservation::Preserve,
141 frame_support::traits::tokens::Fortitude::Polite,
142 )
143 .map_err(|_| InvalidTransaction::Payment)?;
144
145 let (tip_credit, inclusion_fee) = credit.split(tip);
146
147 <Pallet<T>>::deposit_txfee(inclusion_fee);
148
149 Ok(Some(tip_credit))
150 }
151
152 fn can_withdraw_fee(
153 who: &T::AccountId,
154 _call: &T::RuntimeCall,
155 _dispatch_info: &DispatchInfoOf<T::RuntimeCall>,
156 fee_with_tip: Self::Balance,
157 _tip: Self::Balance,
158 ) -> Result<(), TransactionValidityError> {
159 if fee_with_tip.is_zero() {
160 return Ok(())
161 }
162
163 match F::can_withdraw(who, fee_with_tip) {
164 WithdrawConsequence::Success => Ok(()),
165 _ => Err(InvalidTransaction::Payment.into()),
166 }
167 }
168
169 fn correct_and_deposit_fee(
170 who: &<T>::AccountId,
171 _dispatch_info: &DispatchInfoOf<<T>::RuntimeCall>,
172 _post_info: &PostDispatchInfoOf<<T>::RuntimeCall>,
173 corrected_fee_with_tip: Self::Balance,
174 tip: Self::Balance,
175 tip_credit: Self::LiquidityInfo,
176 ) -> Result<(), TransactionValidityError> {
177 let corrected_fee = corrected_fee_with_tip.saturating_sub(tip);
178
179 let remaining_credit = <TxPaymentCredit<T>>::take()
180 .map(|stored_credit| stored_credit.into_inner())
181 .unwrap_or_default();
182
183 if remaining_credit.peek() < corrected_fee {
187 log::error!(target: LOG_TARGET, "Not enough balance on hold to pay tx fees. This is a bug.");
188 }
189
190 let fee_credit = if frame_system::Pallet::<T>::account_exists(who) {
192 let (mut fee_credit, refund_credit) = remaining_credit.split(corrected_fee);
193 if !refund_credit.peek().is_zero() {
196 if let Err(not_refunded) = F::resolve(who, refund_credit) {
197 fee_credit.subsume(not_refunded);
198 }
199 }
200 fee_credit
201 } else {
202 remaining_credit
203 };
204
205 OU::on_unbalanceds(Some(fee_credit).into_iter().chain(tip_credit));
206
207 Ok(())
208 }
209
210 #[cfg(feature = "runtime-benchmarks")]
211 fn endow_account(who: &T::AccountId, amount: Self::Balance) {
212 let _ = F::deposit(who, amount, Precision::BestEffort);
213 }
214
215 #[cfg(feature = "runtime-benchmarks")]
216 fn minimum_balance() -> Self::Balance {
217 F::minimum_balance()
218 }
219}
220
221impl<T, F, OU> TxCreditHold<T> for FungibleAdapter<F, OU>
222where
223 T: Config,
224 F: Balanced<T::AccountId> + 'static,
225{
226 type Credit = NoDrop<Credit<<T as frame_system::Config>::AccountId, F>>;
227}
228
229#[deprecated(
236 note = "Please use the fungible trait and FungibleAdapter. This struct will be removed some time after March 2024."
237)]
238pub struct CurrencyAdapter<C, OU>(PhantomData<(C, OU)>);
239
240#[allow(deprecated)]
245impl<T, C, OU> OnChargeTransaction<T> for CurrencyAdapter<C, OU>
246where
247 T: Config,
248 C: Currency<<T as frame_system::Config>::AccountId>,
249 C::PositiveImbalance: Imbalance<
250 <C as Currency<<T as frame_system::Config>::AccountId>>::Balance,
251 Opposite = C::NegativeImbalance,
252 >,
253 C::NegativeImbalance: Imbalance<
254 <C as Currency<<T as frame_system::Config>::AccountId>>::Balance,
255 Opposite = C::PositiveImbalance,
256 >,
257 OU: OnUnbalanced<NegativeImbalanceOf<C, T>>,
258{
259 type LiquidityInfo = Option<NegativeImbalanceOf<C, T>>;
260 type Balance = <C as Currency<<T as frame_system::Config>::AccountId>>::Balance;
261
262 fn withdraw_fee(
266 who: &T::AccountId,
267 _call: &T::RuntimeCall,
268 _info: &DispatchInfoOf<T::RuntimeCall>,
269 fee_with_tip: Self::Balance,
270 tip: Self::Balance,
271 ) -> Result<Self::LiquidityInfo, TransactionValidityError> {
272 if fee_with_tip.is_zero() {
273 return Ok(None)
274 }
275
276 let withdraw_reason = if tip.is_zero() {
277 WithdrawReasons::TRANSACTION_PAYMENT
278 } else {
279 WithdrawReasons::TRANSACTION_PAYMENT | WithdrawReasons::TIP
280 };
281
282 match C::withdraw(who, fee_with_tip, withdraw_reason, ExistenceRequirement::KeepAlive) {
283 Ok(imbalance) => Ok(Some(imbalance)),
284 Err(_) => Err(InvalidTransaction::Payment.into()),
285 }
286 }
287
288 fn can_withdraw_fee(
292 who: &T::AccountId,
293 _call: &T::RuntimeCall,
294 _info: &DispatchInfoOf<T::RuntimeCall>,
295 fee_with_tip: Self::Balance,
296 tip: Self::Balance,
297 ) -> Result<(), TransactionValidityError> {
298 if fee_with_tip.is_zero() {
299 return Ok(())
300 }
301
302 let withdraw_reason = if tip.is_zero() {
303 WithdrawReasons::TRANSACTION_PAYMENT
304 } else {
305 WithdrawReasons::TRANSACTION_PAYMENT | WithdrawReasons::TIP
306 };
307
308 let new_balance = C::free_balance(who)
309 .checked_sub(&fee_with_tip)
310 .ok_or(InvalidTransaction::Payment)?;
311 C::ensure_can_withdraw(who, fee_with_tip, withdraw_reason, new_balance)
312 .map(|_| ())
313 .map_err(|_| InvalidTransaction::Payment.into())
314 }
315
316 fn correct_and_deposit_fee(
322 who: &T::AccountId,
323 _dispatch_info: &DispatchInfoOf<T::RuntimeCall>,
324 _post_info: &PostDispatchInfoOf<T::RuntimeCall>,
325 corrected_fee_with_tip: Self::Balance,
326 tip: Self::Balance,
327 already_withdrawn: Self::LiquidityInfo,
328 ) -> Result<(), TransactionValidityError> {
329 if let Some(paid) = already_withdrawn {
330 let refund_amount = paid.peek().saturating_sub(corrected_fee_with_tip);
332 let refund_imbalance = C::deposit_into_existing(who, refund_amount)
336 .unwrap_or_else(|_| C::PositiveImbalance::zero());
337 let adjusted_paid = paid
339 .offset(refund_imbalance)
340 .same()
341 .map_err(|_| TransactionValidityError::Invalid(InvalidTransaction::Payment))?;
342 let (tip, fee) = adjusted_paid.split(tip);
344 OU::on_unbalanceds(Some(fee).into_iter().chain(Some(tip)));
345 }
346 Ok(())
347 }
348
349 #[cfg(feature = "runtime-benchmarks")]
350 fn endow_account(who: &T::AccountId, amount: Self::Balance) {
351 let _ = C::deposit_creating(who, amount);
352 }
353
354 #[cfg(feature = "runtime-benchmarks")]
355 fn minimum_balance() -> Self::Balance {
356 C::minimum_balance()
357 }
358}
359
360#[allow(deprecated)]
361impl<T: Config, C, OU> TxCreditHold<T> for CurrencyAdapter<C, OU> {
362 type Credit = ();
363}