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