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.
19
use crate::Config;
20

            
21
use core::marker::PhantomData;
22
use sp_runtime::{
23
	traits::{DispatchInfoOf, PostDispatchInfoOf, Saturating, Zero},
24
	transaction_validity::InvalidTransaction,
25
};
26

            
27
use frame_support::{
28
	traits::{
29
		fungible::{Balanced, Credit, Debt, Inspect},
30
		tokens::Precision,
31
		Currency, ExistenceRequirement, Imbalance, OnUnbalanced, WithdrawReasons,
32
	},
33
	unsigned::TransactionValidityError,
34
};
35

            
36
type NegativeImbalanceOf<C, T> =
37
	<C as Currency<<T as frame_system::Config>::AccountId>>::NegativeImbalance;
38

            
39
/// Handle withdrawing, refunding and depositing of transaction fees.
40
pub 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
	/// After the transaction was executed the actual fee can be calculated.
59
	/// This function should refund any overpaid fees and optionally deposit
60
	/// the corrected amount.
61
	///
62
	/// Note: The `fee` already includes the `tip`.
63
	fn correct_and_deposit_fee(
64
		who: &T::AccountId,
65
		dispatch_info: &DispatchInfoOf<T::RuntimeCall>,
66
		post_info: &PostDispatchInfoOf<T::RuntimeCall>,
67
		corrected_fee: Self::Balance,
68
		tip: Self::Balance,
69
		already_withdrawn: Self::LiquidityInfo,
70
	) -> Result<(), TransactionValidityError>;
71
}
72

            
73
/// Implements transaction payment for a pallet implementing the [`frame_support::traits::fungible`]
74
/// trait (eg. pallet_balances) using an unbalance handler (implementing
75
/// [`OnUnbalanced`]).
76
///
77
/// The unbalance handler is given 2 unbalanceds in [`OnUnbalanced::on_unbalanceds`]: `fee` and
78
/// then `tip`.
79
pub struct FungibleAdapter<F, OU>(PhantomData<(F, OU)>);
80

            
81
impl<T, F, OU> OnChargeTransaction<T> for FungibleAdapter<F, OU>
82
where
83
	T: Config,
84
	F: Balanced<T::AccountId>,
85
	OU: OnUnbalanced<Credit<T::AccountId, F>>,
86
{
87
	type LiquidityInfo = Option<Credit<T::AccountId, F>>;
88
	type Balance = <F as Inspect<<T as frame_system::Config>::AccountId>>::Balance;
89

            
90
	fn withdraw_fee(
91
		who: &<T>::AccountId,
92
		_call: &<T>::RuntimeCall,
93
		_dispatch_info: &DispatchInfoOf<<T>::RuntimeCall>,
94
		fee: Self::Balance,
95
		_tip: Self::Balance,
96
	) -> Result<Self::LiquidityInfo, TransactionValidityError> {
97
		if fee.is_zero() {
98
			return Ok(None)
99
		}
100

            
101
		match F::withdraw(
102
			who,
103
			fee,
104
			Precision::Exact,
105
			frame_support::traits::tokens::Preservation::Preserve,
106
			frame_support::traits::tokens::Fortitude::Polite,
107
		) {
108
			Ok(imbalance) => Ok(Some(imbalance)),
109
			Err(_) => Err(InvalidTransaction::Payment.into()),
110
		}
111
	}
112

            
113
	fn correct_and_deposit_fee(
114
		who: &<T>::AccountId,
115
		_dispatch_info: &DispatchInfoOf<<T>::RuntimeCall>,
116
		_post_info: &PostDispatchInfoOf<<T>::RuntimeCall>,
117
		corrected_fee: Self::Balance,
118
		tip: Self::Balance,
119
		already_withdrawn: Self::LiquidityInfo,
120
	) -> Result<(), TransactionValidityError> {
121
		if let Some(paid) = already_withdrawn {
122
			// Calculate how much refund we should return
123
			let refund_amount = paid.peek().saturating_sub(corrected_fee);
124
			// refund to the the account that paid the fees if it exists. otherwise, don't refind
125
			// anything.
126
			let refund_imbalance = if F::total_balance(who) > F::Balance::zero() {
127
				F::deposit(who, refund_amount, Precision::BestEffort)
128
					.unwrap_or_else(|_| Debt::<T::AccountId, F>::zero())
129
			} else {
130
				Debt::<T::AccountId, F>::zero()
131
			};
132
			// merge the imbalance caused by paying the fees and refunding parts of it again.
133
			let adjusted_paid: Credit<T::AccountId, F> = paid
134
				.offset(refund_imbalance)
135
				.same()
136
				.map_err(|_| TransactionValidityError::Invalid(InvalidTransaction::Payment))?;
137
			// Call someone else to handle the imbalance (fee and tip separately)
138
			let (tip, fee) = adjusted_paid.split(tip);
139
			OU::on_unbalanceds(Some(fee).into_iter().chain(Some(tip)));
140
		}
141

            
142
		Ok(())
143
	}
144
}
145

            
146
/// Implements the transaction payment for a pallet implementing the [`Currency`]
147
/// trait (eg. the pallet_balances) using an unbalance handler (implementing
148
/// [`OnUnbalanced`]).
149
///
150
/// The unbalance handler is given 2 unbalanceds in [`OnUnbalanced::on_unbalanceds`]: `fee` and
151
/// then `tip`.
152
#[deprecated(
153
	note = "Please use the fungible trait and FungibleAdapter. This struct will be removed some time after March 2024."
154
)]
155
pub struct CurrencyAdapter<C, OU>(PhantomData<(C, OU)>);
156

            
157
/// Default implementation for a Currency and an OnUnbalanced handler.
158
///
159
/// The unbalance handler is given 2 unbalanceds in [`OnUnbalanced::on_unbalanceds`]: `fee` and
160
/// then `tip`.
161
#[allow(deprecated)]
162
impl<T, C, OU> OnChargeTransaction<T> for CurrencyAdapter<C, OU>
163
where
164
	T: Config,
165
	C: Currency<<T as frame_system::Config>::AccountId>,
166
	C::PositiveImbalance: Imbalance<
167
		<C as Currency<<T as frame_system::Config>::AccountId>>::Balance,
168
		Opposite = C::NegativeImbalance,
169
	>,
170
	C::NegativeImbalance: Imbalance<
171
		<C as Currency<<T as frame_system::Config>::AccountId>>::Balance,
172
		Opposite = C::PositiveImbalance,
173
	>,
174
	OU: OnUnbalanced<NegativeImbalanceOf<C, T>>,
175
{
176
	type LiquidityInfo = Option<NegativeImbalanceOf<C, T>>;
177
	type Balance = <C as Currency<<T as frame_system::Config>::AccountId>>::Balance;
178

            
179
	/// Withdraw the predicted fee from the transaction origin.
180
	///
181
	/// Note: The `fee` already includes the `tip`.
182
	fn withdraw_fee(
183
		who: &T::AccountId,
184
		_call: &T::RuntimeCall,
185
		_info: &DispatchInfoOf<T::RuntimeCall>,
186
		fee: Self::Balance,
187
		tip: Self::Balance,
188
	) -> Result<Self::LiquidityInfo, TransactionValidityError> {
189
		if fee.is_zero() {
190
			return Ok(None)
191
		}
192

            
193
		let withdraw_reason = if tip.is_zero() {
194
			WithdrawReasons::TRANSACTION_PAYMENT
195
		} else {
196
			WithdrawReasons::TRANSACTION_PAYMENT | WithdrawReasons::TIP
197
		};
198

            
199
		match C::withdraw(who, fee, withdraw_reason, ExistenceRequirement::KeepAlive) {
200
			Ok(imbalance) => Ok(Some(imbalance)),
201
			Err(_) => Err(InvalidTransaction::Payment.into()),
202
		}
203
	}
204

            
205
	/// Hand the fee and the tip over to the `[OnUnbalanced]` implementation.
206
	/// Since the predicted fee might have been too high, parts of the fee may
207
	/// be refunded.
208
	///
209
	/// Note: The `corrected_fee` already includes the `tip`.
210
	fn correct_and_deposit_fee(
211
		who: &T::AccountId,
212
		_dispatch_info: &DispatchInfoOf<T::RuntimeCall>,
213
		_post_info: &PostDispatchInfoOf<T::RuntimeCall>,
214
		corrected_fee: Self::Balance,
215
		tip: Self::Balance,
216
		already_withdrawn: Self::LiquidityInfo,
217
	) -> Result<(), TransactionValidityError> {
218
		if let Some(paid) = already_withdrawn {
219
			// Calculate how much refund we should return
220
			let refund_amount = paid.peek().saturating_sub(corrected_fee);
221
			// refund to the the account that paid the fees. If this fails, the
222
			// account might have dropped below the existential balance. In
223
			// that case we don't refund anything.
224
			let refund_imbalance = C::deposit_into_existing(who, refund_amount)
225
				.unwrap_or_else(|_| C::PositiveImbalance::zero());
226
			// merge the imbalance caused by paying the fees and refunding parts of it again.
227
			let adjusted_paid = paid
228
				.offset(refund_imbalance)
229
				.same()
230
				.map_err(|_| TransactionValidityError::Invalid(InvalidTransaction::Payment))?;
231
			// Call someone else to handle the imbalance (fee and tip separately)
232
			let (tip, fee) = adjusted_paid.split(tip);
233
			OU::on_unbalanceds(Some(fee).into_iter().chain(Some(tip)));
234
		}
235
		Ok(())
236
	}
237
}