pallet_block_fees/
fees.rs

1use crate::Pallet as BlockFees;
2use frame_support::traits::fungible::{Inspect, Mutate};
3use frame_support::traits::tokens::WithdrawConsequence;
4use frame_support::traits::{Currency, ExistenceRequirement, Imbalance, WithdrawReasons};
5use parity_scale_codec::Encode;
6use sp_runtime::Saturating;
7use sp_runtime::traits::{DispatchInfoOf, PostDispatchInfoOf, Zero};
8use sp_runtime::transaction_validity::{InvalidTransaction, TransactionValidityError};
9use sp_std::marker::PhantomData;
10
11pub struct LiquidityInfo<Balance, NegativeImbalance> {
12    consensus_storage_fee: Balance,
13    imbalance: NegativeImbalance,
14}
15
16type AccountIdOf<T> = <T as frame_system::Config>::AccountId;
17type BalanceOf<C, T> = <C as Currency<AccountIdOf<T>>>::Balance;
18type NegativeImbalanceOf<C, T> = <C as Currency<AccountIdOf<T>>>::NegativeImbalance;
19
20/// Implementation of [`pallet_transaction_payment::OnChargeTransaction`] that charges transaction
21/// fees and distributes storage/compute fees and tip separately.
22pub struct OnChargeDomainTransaction<C>(PhantomData<C>);
23
24impl<T, C> pallet_transaction_payment::OnChargeTransaction<T> for OnChargeDomainTransaction<C>
25where
26    T: pallet_transaction_payment::Config + crate::Config<Balance = BalanceOf<C, T>>,
27    C: Currency<AccountIdOf<T>>
28        + Inspect<AccountIdOf<T>, Balance = BalanceOf<C, T>>
29        + Mutate<AccountIdOf<T>>,
30    C::PositiveImbalance: Imbalance<BalanceOf<C, T>, Opposite = C::NegativeImbalance>,
31{
32    type Balance = BalanceOf<C, T>;
33    type LiquidityInfo = Option<LiquidityInfo<BalanceOf<C, T>, NegativeImbalanceOf<C, T>>>;
34
35    fn withdraw_fee(
36        who: &AccountIdOf<T>,
37        call: &T::RuntimeCall,
38        _info: &DispatchInfoOf<T::RuntimeCall>,
39        fee: Self::Balance,
40        tip: Self::Balance,
41    ) -> Result<Self::LiquidityInfo, TransactionValidityError> {
42        if fee.is_zero() {
43            return Ok(None);
44        }
45
46        let withdraw_reason = if tip.is_zero() {
47            WithdrawReasons::TRANSACTION_PAYMENT
48        } else {
49            WithdrawReasons::TRANSACTION_PAYMENT | WithdrawReasons::TIP
50        };
51
52        let withdraw_result =
53            C::withdraw(who, fee, withdraw_reason, ExistenceRequirement::KeepAlive);
54        let imbalance = withdraw_result.map_err(|_error| InvalidTransaction::Payment)?;
55
56        // Separate consensus storage fee while we have access to the call data structure to calculate it.
57        let consensus_storage_fee = BlockFees::<T>::consensus_chain_byte_fee()
58            * Self::Balance::from(call.encoded_size() as u32);
59
60        Ok(Some(LiquidityInfo {
61            consensus_storage_fee,
62            imbalance,
63        }))
64    }
65
66    fn can_withdraw_fee(
67        who: &T::AccountId,
68        _call: &T::RuntimeCall,
69        _dispatch_info: &DispatchInfoOf<T::RuntimeCall>,
70        fee: Self::Balance,
71        _tip: Self::Balance,
72    ) -> Result<(), TransactionValidityError> {
73        if fee.is_zero() {
74            return Ok(());
75        }
76
77        match C::can_withdraw(who, fee) {
78            WithdrawConsequence::Success => Ok(()),
79            _ => Err(InvalidTransaction::Payment.into()),
80        }
81    }
82
83    fn correct_and_deposit_fee(
84        who: &AccountIdOf<T>,
85        _dispatch_info: &DispatchInfoOf<T::RuntimeCall>,
86        _post_info: &PostDispatchInfoOf<T::RuntimeCall>,
87        corrected_fee: Self::Balance,
88        _tip: Self::Balance,
89        liquidity_info: Self::LiquidityInfo,
90    ) -> Result<(), TransactionValidityError> {
91        if let Some(LiquidityInfo {
92            consensus_storage_fee,
93            imbalance,
94        }) = liquidity_info
95        {
96            // Calculate how much refund we should return
97            let refund_amount = imbalance.peek().saturating_sub(corrected_fee);
98            // Refund to the account that paid the fees. If this fails, the account might have
99            // dropped below the existential balance. In that case we don't refund anything.
100            let refund_imbalance = C::deposit_into_existing(who, refund_amount)
101                .unwrap_or_else(|_| C::PositiveImbalance::zero());
102            // Merge the imbalance caused by paying the fees and refunding parts of it again.
103            let adjusted_paid = imbalance
104                .offset(refund_imbalance)
105                .same()
106                .map_err(|_| TransactionValidityError::Invalid(InvalidTransaction::Payment))?;
107
108            // Split the paid consensus storage fee and the paid domain execution fee so that they can
109            // be distributed separately.
110            let (paid_consensus_storage_fee, paid_domain_fee) =
111                adjusted_paid.split(consensus_storage_fee);
112
113            BlockFees::<T>::note_consensus_storage_fee(paid_consensus_storage_fee.peek());
114            BlockFees::<T>::note_domain_execution_fee(paid_domain_fee.peek());
115        }
116        Ok(())
117    }
118
119    #[cfg(feature = "runtime-benchmarks")]
120    fn endow_account(who: &AccountIdOf<T>, amount: Self::Balance) {
121        C::set_balance(who, amount);
122    }
123
124    #[cfg(feature = "runtime-benchmarks")]
125    fn minimum_balance() -> Self::Balance {
126        <C as Currency<AccountIdOf<T>>>::minimum_balance()
127    }
128}