subspace_runtime/
fees.rs

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