Skip to main content

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