referrerpolicy=no-referrer-when-downgrade

pallet_transaction_payment/
payment.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
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/// ! Traits and default implementation for paying transaction fees.
19use 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
39/// Handle withdrawing, refunding and depositing of transaction fees.
40pub trait OnChargeTransaction<T: Config> {
41	/// The underlying integer type in which fees are calculated.
42	type Balance: frame_support::traits::tokens::Balance;
43
44	type LiquidityInfo: Default;
45
46	/// Before the transaction is executed the payment of the transaction fees
47	/// need to be secured.
48	///
49	/// Note: The `fee` already includes the `tip`.
50	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	/// Check if the predicted fee from the transaction origin can be withdrawn.
59	///
60	/// Note: The `fee` already includes the `tip`.
61	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	/// After the transaction was executed the actual fee can be calculated.
70	/// This function should refund any overpaid fees and optionally deposit
71	/// the corrected amount.
72	///
73	/// Note: The `fee` already includes the `tip`.
74	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
90/// Implements transaction payment for a pallet implementing the [`frame_support::traits::fungible`]
91/// trait (eg. pallet_balances) using an unbalance handler (implementing
92/// [`OnUnbalanced`]).
93///
94/// The unbalance handler is given 2 unbalanceds in [`OnUnbalanced::on_unbalanceds`]: `fee` and
95/// then `tip`.
96pub 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			// Calculate how much refund we should return
157			let refund_amount = paid.peek().saturating_sub(corrected_fee);
158			// Refund to the the account that paid the fees if it exists & refund is non-zero.
159			// Otherwise, don't refund anything.
160			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			// merge the imbalance caused by paying the fees and refunding parts of it again.
168			let adjusted_paid: Credit<T::AccountId, F> = paid
169				.offset(refund_imbalance)
170				.same()
171				.map_err(|_| TransactionValidityError::Invalid(InvalidTransaction::Payment))?;
172			// Call someone else to handle the imbalance (fee and tip separately)
173			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/// Implements the transaction payment for a pallet implementing the [`Currency`]
192/// trait (eg. the pallet_balances) using an unbalance handler (implementing
193/// [`OnUnbalanced`]).
194///
195/// The unbalance handler is given 2 unbalanceds in [`OnUnbalanced::on_unbalanceds`]: `fee` and
196/// then `tip`.
197#[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/// Default implementation for a Currency and an OnUnbalanced handler.
203///
204/// The unbalance handler is given 2 unbalanceds in [`OnUnbalanced::on_unbalanceds`]: `fee` and
205/// then `tip`.
206#[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	/// Withdraw the predicted fee from the transaction origin.
225	///
226	/// Note: The `fee` already includes the `tip`.
227	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	/// Check if the predicted fee from the transaction origin can be withdrawn.
251	///
252	/// Note: The `fee` already includes the `tip`.
253	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	/// Hand the fee and the tip over to the `[OnUnbalanced]` implementation.
278	/// Since the predicted fee might have been too high, parts of the fee may
279	/// be refunded.
280	///
281	/// Note: The `corrected_fee` already includes the `tip`.
282	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			// Calculate how much refund we should return
292			let refund_amount = paid.peek().saturating_sub(corrected_fee);
293			// refund to the the account that paid the fees. If this fails, the
294			// account might have dropped below the existential balance. In
295			// that case we don't refund anything.
296			let refund_imbalance = C::deposit_into_existing(who, refund_amount)
297				.unwrap_or_else(|_| C::PositiveImbalance::zero());
298			// merge the imbalance caused by paying the fees and refunding parts of it again.
299			let adjusted_paid = paid
300				.offset(refund_imbalance)
301				.same()
302				.map_err(|_| TransactionValidityError::Invalid(InvalidTransaction::Payment))?;
303			// Call someone else to handle the imbalance (fee and tip separately)
304			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}