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
26pub struct OnChargeTransaction;
29
30impl pallet_transaction_payment::TxCreditHold<Runtime> for OnChargeTransaction {
33 type Credit = ();
34}
35
36impl pallet_transaction_payment::OnChargeTransaction<Runtime> for OnChargeTransaction {
37 type LiquidityInfo = Option<LiquidityInfo>;
38 type Balance = Balance;
39
40 fn withdraw_fee(
41 who: &AccountId,
42 call: &RuntimeCall,
43 _info: &DispatchInfoOf<RuntimeCall>,
44 fee: Self::Balance,
45 tip: Self::Balance,
46 ) -> Result<Self::LiquidityInfo, TransactionValidityError> {
47 if fee.is_zero() {
48 return Ok(None);
49 }
50
51 let withdraw_reason = if tip.is_zero() {
52 WithdrawReasons::TRANSACTION_PAYMENT
53 } else {
54 WithdrawReasons::TRANSACTION_PAYMENT | WithdrawReasons::TIP
55 };
56
57 let withdraw_result = <Balances as Currency<AccountId>>::withdraw(
58 who,
59 fee,
60 withdraw_reason,
61 ExistenceRequirement::KeepAlive,
62 );
63 let imbalance = withdraw_result.map_err(|_error| InvalidTransaction::Payment)?;
64
65 let storage_fee = TransactionByteFee::get()
67 * Balance::try_from(call.encoded_size())
68 .expect("Size of the call never exceeds balance units; qed");
69
70 Ok(Some(LiquidityInfo {
71 storage_fee,
72 imbalance,
73 }))
74 }
75
76 fn correct_and_deposit_fee(
77 who: &AccountId,
78 _dispatch_info: &DispatchInfoOf<RuntimeCall>,
79 _post_info: &PostDispatchInfoOf<RuntimeCall>,
80 corrected_fee: Self::Balance,
81 tip: Self::Balance,
82 liquidity_info: Self::LiquidityInfo,
83 ) -> Result<(), TransactionValidityError> {
84 if let Some(LiquidityInfo {
85 storage_fee,
86 imbalance,
87 }) = liquidity_info
88 {
89 let refund_amount = imbalance.peek().saturating_sub(corrected_fee);
91 let refund_imbalance = Balances::deposit_into_existing(who, refund_amount)
94 .unwrap_or_else(|_| <Balances as Currency<AccountId>>::PositiveImbalance::zero());
95 let adjusted_paid = imbalance
97 .offset(refund_imbalance)
98 .same()
99 .map_err(|_| TransactionValidityError::Invalid(InvalidTransaction::Payment))?;
100
101 let (tip, fee) = adjusted_paid.split(tip);
103 let (paid_storage_fee, paid_compute_fee) = fee.split(storage_fee);
105
106 TransactionFees::note_transaction_fees(
107 paid_storage_fee.peek(),
108 paid_compute_fee.peek(),
109 tip.peek(),
110 );
111 }
112 Ok(())
113 }
114
115 fn can_withdraw_fee(
116 who: &AccountId,
117 _call: &RuntimeCall,
118 _dispatch_info: &DispatchInfoOf<RuntimeCall>,
119 fee: Self::Balance,
120 _tip: Self::Balance,
121 ) -> Result<(), TransactionValidityError> {
122 if fee.is_zero() {
123 return Ok(());
124 }
125
126 match Balances::can_withdraw(who, fee) {
127 WithdrawConsequence::Success => Ok(()),
128 _ => Err(InvalidTransaction::Payment.into()),
129 }
130 }
131
132 #[cfg(feature = "runtime-benchmarks")]
133 fn endow_account(who: &AccountId, amount: Self::Balance) {
134 Balances::set_balance(who, amount);
135 }
136
137 #[cfg(feature = "runtime-benchmarks")]
138 fn minimum_balance() -> Self::Balance {
139 <Balances as Currency<AccountId>>::minimum_balance()
140 }
141}