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
24pub 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 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 let refund_amount = imbalance.peek().saturating_sub(corrected_fee);
83 let refund_imbalance = Balances::deposit_into_existing(who, refund_amount)
86 .unwrap_or_else(|_| <Balances as Currency<AccountId>>::PositiveImbalance::zero());
87 let adjusted_paid = imbalance
89 .offset(refund_imbalance)
90 .same()
91 .map_err(|_| TransactionValidityError::Invalid(InvalidTransaction::Payment))?;
92
93 let (tip, fee) = adjusted_paid.split(tip);
95 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}