subspace_runtime/
fees.rs

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