// This file is part of Substrate. // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. /// ! Traits and default implementation for paying transaction fees. use crate::Config; use core::marker::PhantomData; use sp_runtime::{ traits::{DispatchInfoOf, PostDispatchInfoOf, Saturating, Zero}, transaction_validity::InvalidTransaction, }; use frame_support::{ traits::{ fungible::{Balanced, Credit, Debt, Inspect}, tokens::Precision, Currency, ExistenceRequirement, Imbalance, OnUnbalanced, WithdrawReasons, }, unsigned::TransactionValidityError, }; type NegativeImbalanceOf = ::AccountId>>::NegativeImbalance; /// Handle withdrawing, refunding and depositing of transaction fees. pub trait OnChargeTransaction { /// The underlying integer type in which fees are calculated. type Balance: frame_support::traits::tokens::Balance; type LiquidityInfo: Default; /// Before the transaction is executed the payment of the transaction fees /// need to be secured. /// /// Note: The `fee` already includes the `tip`. fn withdraw_fee( who: &T::AccountId, call: &T::RuntimeCall, dispatch_info: &DispatchInfoOf, fee: Self::Balance, tip: Self::Balance, ) -> Result; /// After the transaction was executed the actual fee can be calculated. /// This function should refund any overpaid fees and optionally deposit /// the corrected amount. /// /// Note: The `fee` already includes the `tip`. fn correct_and_deposit_fee( who: &T::AccountId, dispatch_info: &DispatchInfoOf, post_info: &PostDispatchInfoOf, corrected_fee: Self::Balance, tip: Self::Balance, already_withdrawn: Self::LiquidityInfo, ) -> Result<(), TransactionValidityError>; } /// Implements transaction payment for a pallet implementing the [`frame_support::traits::fungible`] /// trait (eg. pallet_balances) using an unbalance handler (implementing /// [`OnUnbalanced`]). /// /// The unbalance handler is given 2 unbalanceds in [`OnUnbalanced::on_unbalanceds`]: `fee` and /// then `tip`. pub struct FungibleAdapter(PhantomData<(F, OU)>); impl OnChargeTransaction for FungibleAdapter where T: Config, F: Balanced, OU: OnUnbalanced>, { type LiquidityInfo = Option>; type Balance = ::AccountId>>::Balance; fn withdraw_fee( who: &::AccountId, _call: &::RuntimeCall, _dispatch_info: &DispatchInfoOf<::RuntimeCall>, fee: Self::Balance, _tip: Self::Balance, ) -> Result { if fee.is_zero() { return Ok(None) } match F::withdraw( who, fee, Precision::Exact, frame_support::traits::tokens::Preservation::Preserve, frame_support::traits::tokens::Fortitude::Polite, ) { Ok(imbalance) => Ok(Some(imbalance)), Err(_) => Err(InvalidTransaction::Payment.into()), } } fn correct_and_deposit_fee( who: &::AccountId, _dispatch_info: &DispatchInfoOf<::RuntimeCall>, _post_info: &PostDispatchInfoOf<::RuntimeCall>, corrected_fee: Self::Balance, tip: Self::Balance, already_withdrawn: Self::LiquidityInfo, ) -> Result<(), TransactionValidityError> { if let Some(paid) = already_withdrawn { // Calculate how much refund we should return let refund_amount = paid.peek().saturating_sub(corrected_fee); // refund to the the account that paid the fees if it exists. otherwise, don't refind // anything. let refund_imbalance = if F::total_balance(who) > F::Balance::zero() { F::deposit(who, refund_amount, Precision::BestEffort) .unwrap_or_else(|_| Debt::::zero()) } else { Debt::::zero() }; // merge the imbalance caused by paying the fees and refunding parts of it again. let adjusted_paid: Credit = paid .offset(refund_imbalance) .same() .map_err(|_| TransactionValidityError::Invalid(InvalidTransaction::Payment))?; // Call someone else to handle the imbalance (fee and tip separately) let (tip, fee) = adjusted_paid.split(tip); OU::on_unbalanceds(Some(fee).into_iter().chain(Some(tip))); } Ok(()) } } /// Implements the transaction payment for a pallet implementing the [`Currency`] /// trait (eg. the pallet_balances) using an unbalance handler (implementing /// [`OnUnbalanced`]). /// /// The unbalance handler is given 2 unbalanceds in [`OnUnbalanced::on_unbalanceds`]: `fee` and /// then `tip`. #[deprecated( note = "Please use the fungible trait and FungibleAdapter. This struct will be removed some time after March 2024." )] pub struct CurrencyAdapter(PhantomData<(C, OU)>); /// Default implementation for a Currency and an OnUnbalanced handler. /// /// The unbalance handler is given 2 unbalanceds in [`OnUnbalanced::on_unbalanceds`]: `fee` and /// then `tip`. #[allow(deprecated)] impl OnChargeTransaction for CurrencyAdapter where T: Config, C: Currency<::AccountId>, C::PositiveImbalance: Imbalance< ::AccountId>>::Balance, Opposite = C::NegativeImbalance, >, C::NegativeImbalance: Imbalance< ::AccountId>>::Balance, Opposite = C::PositiveImbalance, >, OU: OnUnbalanced>, { type LiquidityInfo = Option>; type Balance = ::AccountId>>::Balance; /// Withdraw the predicted fee from the transaction origin. /// /// Note: The `fee` already includes the `tip`. fn withdraw_fee( who: &T::AccountId, _call: &T::RuntimeCall, _info: &DispatchInfoOf, fee: Self::Balance, tip: Self::Balance, ) -> Result { if fee.is_zero() { return Ok(None) } let withdraw_reason = if tip.is_zero() { WithdrawReasons::TRANSACTION_PAYMENT } else { WithdrawReasons::TRANSACTION_PAYMENT | WithdrawReasons::TIP }; match C::withdraw(who, fee, withdraw_reason, ExistenceRequirement::KeepAlive) { Ok(imbalance) => Ok(Some(imbalance)), Err(_) => Err(InvalidTransaction::Payment.into()), } } /// Hand the fee and the tip over to the `[OnUnbalanced]` implementation. /// Since the predicted fee might have been too high, parts of the fee may /// be refunded. /// /// Note: The `corrected_fee` already includes the `tip`. fn correct_and_deposit_fee( who: &T::AccountId, _dispatch_info: &DispatchInfoOf, _post_info: &PostDispatchInfoOf, corrected_fee: Self::Balance, tip: Self::Balance, already_withdrawn: Self::LiquidityInfo, ) -> Result<(), TransactionValidityError> { if let Some(paid) = already_withdrawn { // Calculate how much refund we should return let refund_amount = paid.peek().saturating_sub(corrected_fee); // refund to the the account that paid the fees. If this fails, the // account might have dropped below the existential balance. In // that case we don't refund anything. let refund_imbalance = C::deposit_into_existing(who, refund_amount) .unwrap_or_else(|_| C::PositiveImbalance::zero()); // merge the imbalance caused by paying the fees and refunding parts of it again. let adjusted_paid = paid .offset(refund_imbalance) .same() .map_err(|_| TransactionValidityError::Invalid(InvalidTransaction::Payment))?; // Call someone else to handle the imbalance (fee and tip separately) let (tip, fee) = adjusted_paid.split(tip); OU::on_unbalanceds(Some(fee).into_iter().chain(Some(tip))); } Ok(()) } }