pallet_transaction_payment/
payment.rs1use crate::Config;
20
21use core::marker::PhantomData;
22use sp_runtime::{
23 traits::{CheckedSub, DispatchInfoOf, PostDispatchInfoOf, Saturating, Zero},
24 transaction_validity::InvalidTransaction,
25};
26
27use frame_support::{
28 traits::{
29 fungible::{Balanced, Credit, Debt, Inspect},
30 tokens::{Precision, WithdrawConsequence},
31 Currency, ExistenceRequirement, Imbalance, OnUnbalanced, WithdrawReasons,
32 },
33 unsigned::TransactionValidityError,
34};
35
36type NegativeImbalanceOf<C, T> =
37 <C as Currency<<T as frame_system::Config>::AccountId>>::NegativeImbalance;
38
39pub trait OnChargeTransaction<T: Config> {
41 type Balance: frame_support::traits::tokens::Balance;
43
44 type LiquidityInfo: Default;
45
46 fn withdraw_fee(
51 who: &T::AccountId,
52 call: &T::RuntimeCall,
53 dispatch_info: &DispatchInfoOf<T::RuntimeCall>,
54 fee: Self::Balance,
55 tip: Self::Balance,
56 ) -> Result<Self::LiquidityInfo, TransactionValidityError>;
57
58 fn can_withdraw_fee(
62 who: &T::AccountId,
63 call: &T::RuntimeCall,
64 dispatch_info: &DispatchInfoOf<T::RuntimeCall>,
65 fee: 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: Self::Balance,
79 tip: Self::Balance,
80 already_withdrawn: 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 struct FungibleAdapter<F, OU>(PhantomData<(F, OU)>);
97
98impl<T, F, OU> OnChargeTransaction<T> for FungibleAdapter<F, OU>
99where
100 T: Config,
101 F: Balanced<T::AccountId>,
102 OU: OnUnbalanced<Credit<T::AccountId, F>>,
103{
104 type LiquidityInfo = Option<Credit<T::AccountId, F>>;
105 type Balance = <F as Inspect<<T as frame_system::Config>::AccountId>>::Balance;
106
107 fn withdraw_fee(
108 who: &<T>::AccountId,
109 _call: &<T>::RuntimeCall,
110 _dispatch_info: &DispatchInfoOf<<T>::RuntimeCall>,
111 fee: Self::Balance,
112 _tip: Self::Balance,
113 ) -> Result<Self::LiquidityInfo, TransactionValidityError> {
114 if fee.is_zero() {
115 return Ok(None)
116 }
117
118 match F::withdraw(
119 who,
120 fee,
121 Precision::Exact,
122 frame_support::traits::tokens::Preservation::Preserve,
123 frame_support::traits::tokens::Fortitude::Polite,
124 ) {
125 Ok(imbalance) => Ok(Some(imbalance)),
126 Err(_) => Err(InvalidTransaction::Payment.into()),
127 }
128 }
129
130 fn can_withdraw_fee(
131 who: &T::AccountId,
132 _call: &T::RuntimeCall,
133 _dispatch_info: &DispatchInfoOf<T::RuntimeCall>,
134 fee: Self::Balance,
135 _tip: Self::Balance,
136 ) -> Result<(), TransactionValidityError> {
137 if fee.is_zero() {
138 return Ok(())
139 }
140
141 match F::can_withdraw(who, fee) {
142 WithdrawConsequence::Success => Ok(()),
143 _ => Err(InvalidTransaction::Payment.into()),
144 }
145 }
146
147 fn correct_and_deposit_fee(
148 who: &<T>::AccountId,
149 _dispatch_info: &DispatchInfoOf<<T>::RuntimeCall>,
150 _post_info: &PostDispatchInfoOf<<T>::RuntimeCall>,
151 corrected_fee: Self::Balance,
152 tip: Self::Balance,
153 already_withdrawn: Self::LiquidityInfo,
154 ) -> Result<(), TransactionValidityError> {
155 if let Some(paid) = already_withdrawn {
156 let refund_amount = paid.peek().saturating_sub(corrected_fee);
158 let refund_imbalance =
161 if refund_amount > Zero::zero() && F::total_balance(who) > F::Balance::zero() {
162 F::deposit(who, refund_amount, Precision::BestEffort)
163 .unwrap_or_else(|_| Debt::<T::AccountId, F>::zero())
164 } else {
165 Debt::<T::AccountId, F>::zero()
166 };
167 let adjusted_paid: Credit<T::AccountId, F> = paid
169 .offset(refund_imbalance)
170 .same()
171 .map_err(|_| TransactionValidityError::Invalid(InvalidTransaction::Payment))?;
172 let (tip, fee) = adjusted_paid.split(tip);
174 OU::on_unbalanceds(Some(fee).into_iter().chain(Some(tip)));
175 }
176
177 Ok(())
178 }
179
180 #[cfg(feature = "runtime-benchmarks")]
181 fn endow_account(who: &T::AccountId, amount: Self::Balance) {
182 let _ = F::deposit(who, amount, Precision::BestEffort);
183 }
184
185 #[cfg(feature = "runtime-benchmarks")]
186 fn minimum_balance() -> Self::Balance {
187 F::minimum_balance()
188 }
189}
190
191#[deprecated(
198 note = "Please use the fungible trait and FungibleAdapter. This struct will be removed some time after March 2024."
199)]
200pub struct CurrencyAdapter<C, OU>(PhantomData<(C, OU)>);
201
202#[allow(deprecated)]
207impl<T, C, OU> OnChargeTransaction<T> for CurrencyAdapter<C, OU>
208where
209 T: Config,
210 C: Currency<<T as frame_system::Config>::AccountId>,
211 C::PositiveImbalance: Imbalance<
212 <C as Currency<<T as frame_system::Config>::AccountId>>::Balance,
213 Opposite = C::NegativeImbalance,
214 >,
215 C::NegativeImbalance: Imbalance<
216 <C as Currency<<T as frame_system::Config>::AccountId>>::Balance,
217 Opposite = C::PositiveImbalance,
218 >,
219 OU: OnUnbalanced<NegativeImbalanceOf<C, T>>,
220{
221 type LiquidityInfo = Option<NegativeImbalanceOf<C, T>>;
222 type Balance = <C as Currency<<T as frame_system::Config>::AccountId>>::Balance;
223
224 fn withdraw_fee(
228 who: &T::AccountId,
229 _call: &T::RuntimeCall,
230 _info: &DispatchInfoOf<T::RuntimeCall>,
231 fee: Self::Balance,
232 tip: Self::Balance,
233 ) -> Result<Self::LiquidityInfo, TransactionValidityError> {
234 if fee.is_zero() {
235 return Ok(None)
236 }
237
238 let withdraw_reason = if tip.is_zero() {
239 WithdrawReasons::TRANSACTION_PAYMENT
240 } else {
241 WithdrawReasons::TRANSACTION_PAYMENT | WithdrawReasons::TIP
242 };
243
244 match C::withdraw(who, fee, withdraw_reason, ExistenceRequirement::KeepAlive) {
245 Ok(imbalance) => Ok(Some(imbalance)),
246 Err(_) => Err(InvalidTransaction::Payment.into()),
247 }
248 }
249
250 fn can_withdraw_fee(
254 who: &T::AccountId,
255 _call: &T::RuntimeCall,
256 _info: &DispatchInfoOf<T::RuntimeCall>,
257 fee: Self::Balance,
258 tip: Self::Balance,
259 ) -> Result<(), TransactionValidityError> {
260 if fee.is_zero() {
261 return Ok(())
262 }
263
264 let withdraw_reason = if tip.is_zero() {
265 WithdrawReasons::TRANSACTION_PAYMENT
266 } else {
267 WithdrawReasons::TRANSACTION_PAYMENT | WithdrawReasons::TIP
268 };
269
270 let new_balance =
271 C::free_balance(who).checked_sub(&fee).ok_or(InvalidTransaction::Payment)?;
272 C::ensure_can_withdraw(who, fee, withdraw_reason, new_balance)
273 .map(|_| ())
274 .map_err(|_| InvalidTransaction::Payment.into())
275 }
276
277 fn correct_and_deposit_fee(
283 who: &T::AccountId,
284 _dispatch_info: &DispatchInfoOf<T::RuntimeCall>,
285 _post_info: &PostDispatchInfoOf<T::RuntimeCall>,
286 corrected_fee: Self::Balance,
287 tip: Self::Balance,
288 already_withdrawn: Self::LiquidityInfo,
289 ) -> Result<(), TransactionValidityError> {
290 if let Some(paid) = already_withdrawn {
291 let refund_amount = paid.peek().saturating_sub(corrected_fee);
293 let refund_imbalance = C::deposit_into_existing(who, refund_amount)
297 .unwrap_or_else(|_| C::PositiveImbalance::zero());
298 let adjusted_paid = paid
300 .offset(refund_imbalance)
301 .same()
302 .map_err(|_| TransactionValidityError::Invalid(InvalidTransaction::Payment))?;
303 let (tip, fee) = adjusted_paid.split(tip);
305 OU::on_unbalanceds(Some(fee).into_iter().chain(Some(tip)));
306 }
307 Ok(())
308 }
309
310 #[cfg(feature = "runtime-benchmarks")]
311 fn endow_account(who: &T::AccountId, amount: Self::Balance) {
312 let _ = C::deposit_creating(who, amount);
313 }
314
315 #[cfg(feature = "runtime-benchmarks")]
316 fn minimum_balance() -> Self::Balance {
317 C::minimum_balance()
318 }
319}