pallet_block_fees/
fees.rs

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