referrerpolicy=no-referrer-when-downgrade

pallet_asset_tx_payment/
payment.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// SPDX-License-Identifier: Apache-2.0
3
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8// 	http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16///! Traits and default implementation for paying transaction fees in assets.
17use super::*;
18use crate::Config;
19
20use codec::FullCodec;
21use core::{fmt::Debug, marker::PhantomData};
22use frame_support::{
23	traits::{
24		fungibles::{Balanced, Credit, Inspect},
25		tokens::{
26			Balance, ConversionToAssetBalance, Fortitude::Polite, Precision::Exact,
27			Preservation::Protect,
28		},
29	},
30	unsigned::TransactionValidityError,
31};
32use scale_info::TypeInfo;
33use sp_runtime::{
34	traits::{DispatchInfoOf, MaybeSerializeDeserialize, One, PostDispatchInfoOf},
35	transaction_validity::InvalidTransaction,
36};
37
38/// Handle withdrawing, refunding and depositing of transaction fees.
39pub trait OnChargeAssetTransaction<T: Config> {
40	/// The underlying integer type in which fees are calculated.
41	type Balance: Balance;
42	/// The type used to identify the assets used for transaction payment.
43	type AssetId: FullCodec
44		+ DecodeWithMemTracking
45		+ Clone
46		+ MaybeSerializeDeserialize
47		+ Debug
48		+ Default
49		+ Eq
50		+ TypeInfo;
51	/// The type used to store the intermediate values between pre- and post-dispatch.
52	type LiquidityInfo;
53
54	/// Before the transaction is executed the payment of the transaction fees needs to be secured.
55	///
56	/// Note: The `fee` already includes the `tip`.
57	fn withdraw_fee(
58		who: &T::AccountId,
59		call: &T::RuntimeCall,
60		dispatch_info: &DispatchInfoOf<T::RuntimeCall>,
61		asset_id: Self::AssetId,
62		fee: Self::Balance,
63		tip: Self::Balance,
64	) -> Result<Self::LiquidityInfo, TransactionValidityError>;
65
66	/// Ensure payment of the transaction fees can be withdrawn.
67	///
68	/// Note: The `fee` already includes the `tip`.
69	fn can_withdraw_fee(
70		who: &T::AccountId,
71		call: &T::RuntimeCall,
72		dispatch_info: &DispatchInfoOf<T::RuntimeCall>,
73		asset_id: Self::AssetId,
74		fee: Self::Balance,
75		tip: Self::Balance,
76	) -> Result<(), TransactionValidityError>;
77
78	/// After the transaction was executed the actual fee can be calculated.
79	/// This function should refund any overpaid fees and optionally deposit
80	/// the corrected amount.
81	///
82	/// Note: The `fee` already includes the `tip`.
83	///
84	/// Returns the fee and tip in the asset used for payment as (fee, tip).
85	fn correct_and_deposit_fee(
86		who: &T::AccountId,
87		dispatch_info: &DispatchInfoOf<T::RuntimeCall>,
88		post_info: &PostDispatchInfoOf<T::RuntimeCall>,
89		corrected_fee: Self::Balance,
90		tip: Self::Balance,
91		already_withdrawn: Self::LiquidityInfo,
92	) -> Result<(AssetBalanceOf<T>, AssetBalanceOf<T>), TransactionValidityError>;
93}
94
95/// Allows specifying what to do with the withdrawn asset fees.
96pub trait HandleCredit<AccountId, B: Balanced<AccountId>> {
97	/// Implement to determine what to do with the withdrawn asset fees.
98	/// Default for `CreditOf` from the assets pallet is to burn and
99	/// decrease total issuance.
100	fn handle_credit(credit: Credit<AccountId, B>);
101}
102
103/// Default implementation that just drops the credit according to the `OnDrop` in the underlying
104/// imbalance type.
105impl<A, B: Balanced<A>> HandleCredit<A, B> for () {
106	fn handle_credit(_credit: Credit<A, B>) {}
107}
108
109/// Implements the asset transaction for a balance to asset converter (implementing
110/// [`ConversionToAssetBalance`]) and a credit handler (implementing [`HandleCredit`]).
111///
112/// The credit handler is given the complete fee in terms of the asset used for the transaction.
113pub struct FungiblesAdapter<CON, HC>(PhantomData<(CON, HC)>);
114
115/// Default implementation for a runtime instantiating this pallet, a balance to asset converter and
116/// a credit handler.
117impl<T, CON, HC> OnChargeAssetTransaction<T> for FungiblesAdapter<CON, HC>
118where
119	T: Config,
120	CON: ConversionToAssetBalance<BalanceOf<T>, AssetIdOf<T>, AssetBalanceOf<T>>,
121	HC: HandleCredit<T::AccountId, T::Fungibles>,
122	AssetIdOf<T>: FullCodec + Clone + MaybeSerializeDeserialize + Debug + Default + Eq + TypeInfo,
123{
124	type Balance = BalanceOf<T>;
125	type AssetId = AssetIdOf<T>;
126	type LiquidityInfo = Credit<T::AccountId, T::Fungibles>;
127
128	/// Withdraw the predicted fee from the transaction origin.
129	///
130	/// Note: The `fee` already includes the `tip`.
131	fn withdraw_fee(
132		who: &T::AccountId,
133		_call: &T::RuntimeCall,
134		_info: &DispatchInfoOf<T::RuntimeCall>,
135		asset_id: Self::AssetId,
136		fee: Self::Balance,
137		_tip: Self::Balance,
138	) -> Result<Self::LiquidityInfo, TransactionValidityError> {
139		// We don't know the precision of the underlying asset. Because the converted fee could be
140		// less than one (e.g. 0.5) but gets rounded down by integer division we introduce a minimum
141		// fee.
142		let min_converted_fee = if fee.is_zero() { Zero::zero() } else { One::one() };
143		let converted_fee = CON::to_asset_balance(fee, asset_id.clone())
144			.map_err(|_| TransactionValidityError::from(InvalidTransaction::Payment))?
145			.max(min_converted_fee);
146		let can_withdraw = <T::Fungibles as Inspect<T::AccountId>>::can_withdraw(
147			asset_id.clone(),
148			who,
149			converted_fee,
150		);
151		if can_withdraw != WithdrawConsequence::Success {
152			return Err(InvalidTransaction::Payment.into())
153		}
154		<T::Fungibles as Balanced<T::AccountId>>::withdraw(
155			asset_id,
156			who,
157			converted_fee,
158			Exact,
159			Protect,
160			Polite,
161		)
162		.map_err(|_| TransactionValidityError::from(InvalidTransaction::Payment))
163	}
164
165	/// Ensure payment of the transaction fees can be withdrawn.
166	///
167	/// Note: The `fee` already includes the `tip`.
168	fn can_withdraw_fee(
169		who: &T::AccountId,
170		_call: &T::RuntimeCall,
171		_info: &DispatchInfoOf<T::RuntimeCall>,
172		asset_id: Self::AssetId,
173		fee: Self::Balance,
174		_tip: Self::Balance,
175	) -> Result<(), TransactionValidityError> {
176		// We don't know the precision of the underlying asset. Because the converted fee could be
177		// less than one (e.g. 0.5) but gets rounded down by integer division we introduce a minimum
178		// fee.
179		let min_converted_fee = if fee.is_zero() { Zero::zero() } else { One::one() };
180		let converted_fee = CON::to_asset_balance(fee, asset_id.clone())
181			.map_err(|_| TransactionValidityError::from(InvalidTransaction::Payment))?
182			.max(min_converted_fee);
183		let can_withdraw =
184			<T::Fungibles as Inspect<T::AccountId>>::can_withdraw(asset_id, who, converted_fee);
185		if can_withdraw != WithdrawConsequence::Success {
186			return Err(InvalidTransaction::Payment.into())
187		}
188		Ok(())
189	}
190
191	/// Hand the fee and the tip over to the `[HandleCredit]` implementation.
192	/// Since the predicted fee might have been too high, parts of the fee may be refunded.
193	///
194	/// Note: The `corrected_fee` already includes the `tip`.
195	///
196	/// Returns the fee and tip in the asset used for payment as (fee, tip).
197	fn correct_and_deposit_fee(
198		who: &T::AccountId,
199		_dispatch_info: &DispatchInfoOf<T::RuntimeCall>,
200		_post_info: &PostDispatchInfoOf<T::RuntimeCall>,
201		corrected_fee: Self::Balance,
202		tip: Self::Balance,
203		paid: Self::LiquidityInfo,
204	) -> Result<(AssetBalanceOf<T>, AssetBalanceOf<T>), TransactionValidityError> {
205		let min_converted_fee = if corrected_fee.is_zero() { Zero::zero() } else { One::one() };
206		// Convert the corrected fee and tip into the asset used for payment.
207		let converted_fee = CON::to_asset_balance(corrected_fee, paid.asset())
208			.map_err(|_| -> TransactionValidityError { InvalidTransaction::Payment.into() })?
209			.max(min_converted_fee);
210		let converted_tip = CON::to_asset_balance(tip, paid.asset())
211			.map_err(|_| -> TransactionValidityError { InvalidTransaction::Payment.into() })?;
212
213		// Calculate how much refund we should return.
214		let (final_fee, refund) = paid.split(converted_fee);
215		// Refund to the account that paid the fees. If this fails, the account might have dropped
216		// below the existential balance. In that case we don't refund anything.
217		let _ = <T::Fungibles as Balanced<T::AccountId>>::resolve(who, refund);
218		// Handle the final fee, e.g. by transferring to the block author or burning.
219		HC::handle_credit(final_fee);
220		Ok((converted_fee, converted_tip))
221	}
222}