1use super::*;
18use crate::Config;
19
20use codec::FullCodec;
21use core::{fmt::Debug, marker::PhantomData};
22use frame_support::{
23 traits::{
24 fungibles::{Balanced, Credit, Inspect},
25 tokens::{
26 Balance, ConversionToAssetBalance, Fortitude::Polite, Precision::Exact,
27 Preservation::Protect,
28 },
29 },
30 unsigned::TransactionValidityError,
31};
32use scale_info::TypeInfo;
33use sp_runtime::{
34 traits::{DispatchInfoOf, MaybeSerializeDeserialize, One, PostDispatchInfoOf},
35 transaction_validity::InvalidTransaction,
36};
37
38pub trait OnChargeAssetTransaction<T: Config> {
40 type Balance: Balance;
42 type AssetId: FullCodec
44 + DecodeWithMemTracking
45 + Clone
46 + MaybeSerializeDeserialize
47 + Debug
48 + Default
49 + Eq
50 + TypeInfo;
51 type LiquidityInfo;
53
54 fn withdraw_fee(
58 who: &T::AccountId,
59 call: &T::RuntimeCall,
60 dispatch_info: &DispatchInfoOf<T::RuntimeCall>,
61 asset_id: Self::AssetId,
62 fee: Self::Balance,
63 tip: Self::Balance,
64 ) -> Result<Self::LiquidityInfo, TransactionValidityError>;
65
66 fn can_withdraw_fee(
70 who: &T::AccountId,
71 call: &T::RuntimeCall,
72 dispatch_info: &DispatchInfoOf<T::RuntimeCall>,
73 asset_id: Self::AssetId,
74 fee: Self::Balance,
75 tip: Self::Balance,
76 ) -> Result<(), TransactionValidityError>;
77
78 fn correct_and_deposit_fee(
86 who: &T::AccountId,
87 dispatch_info: &DispatchInfoOf<T::RuntimeCall>,
88 post_info: &PostDispatchInfoOf<T::RuntimeCall>,
89 corrected_fee: Self::Balance,
90 tip: Self::Balance,
91 already_withdrawn: Self::LiquidityInfo,
92 ) -> Result<(AssetBalanceOf<T>, AssetBalanceOf<T>), TransactionValidityError>;
93}
94
95pub trait HandleCredit<AccountId, B: Balanced<AccountId>> {
97 fn handle_credit(credit: Credit<AccountId, B>);
101}
102
103impl<A, B: Balanced<A>> HandleCredit<A, B> for () {
106 fn handle_credit(_credit: Credit<A, B>) {}
107}
108
109pub struct FungiblesAdapter<CON, HC>(PhantomData<(CON, HC)>);
114
115impl<T, CON, HC> OnChargeAssetTransaction<T> for FungiblesAdapter<CON, HC>
118where
119 T: Config,
120 CON: ConversionToAssetBalance<BalanceOf<T>, AssetIdOf<T>, AssetBalanceOf<T>>,
121 HC: HandleCredit<T::AccountId, T::Fungibles>,
122 AssetIdOf<T>: FullCodec + Clone + MaybeSerializeDeserialize + Debug + Default + Eq + TypeInfo,
123{
124 type Balance = BalanceOf<T>;
125 type AssetId = AssetIdOf<T>;
126 type LiquidityInfo = Credit<T::AccountId, T::Fungibles>;
127
128 fn withdraw_fee(
132 who: &T::AccountId,
133 _call: &T::RuntimeCall,
134 _info: &DispatchInfoOf<T::RuntimeCall>,
135 asset_id: Self::AssetId,
136 fee: Self::Balance,
137 _tip: Self::Balance,
138 ) -> Result<Self::LiquidityInfo, TransactionValidityError> {
139 let min_converted_fee = if fee.is_zero() { Zero::zero() } else { One::one() };
143 let converted_fee = CON::to_asset_balance(fee, asset_id.clone())
144 .map_err(|_| TransactionValidityError::from(InvalidTransaction::Payment))?
145 .max(min_converted_fee);
146 let can_withdraw = <T::Fungibles as Inspect<T::AccountId>>::can_withdraw(
147 asset_id.clone(),
148 who,
149 converted_fee,
150 );
151 if can_withdraw != WithdrawConsequence::Success {
152 return Err(InvalidTransaction::Payment.into())
153 }
154 <T::Fungibles as Balanced<T::AccountId>>::withdraw(
155 asset_id,
156 who,
157 converted_fee,
158 Exact,
159 Protect,
160 Polite,
161 )
162 .map_err(|_| TransactionValidityError::from(InvalidTransaction::Payment))
163 }
164
165 fn can_withdraw_fee(
169 who: &T::AccountId,
170 _call: &T::RuntimeCall,
171 _info: &DispatchInfoOf<T::RuntimeCall>,
172 asset_id: Self::AssetId,
173 fee: Self::Balance,
174 _tip: Self::Balance,
175 ) -> Result<(), TransactionValidityError> {
176 let min_converted_fee = if fee.is_zero() { Zero::zero() } else { One::one() };
180 let converted_fee = CON::to_asset_balance(fee, asset_id.clone())
181 .map_err(|_| TransactionValidityError::from(InvalidTransaction::Payment))?
182 .max(min_converted_fee);
183 let can_withdraw =
184 <T::Fungibles as Inspect<T::AccountId>>::can_withdraw(asset_id, who, converted_fee);
185 if can_withdraw != WithdrawConsequence::Success {
186 return Err(InvalidTransaction::Payment.into())
187 }
188 Ok(())
189 }
190
191 fn correct_and_deposit_fee(
198 who: &T::AccountId,
199 _dispatch_info: &DispatchInfoOf<T::RuntimeCall>,
200 _post_info: &PostDispatchInfoOf<T::RuntimeCall>,
201 corrected_fee: Self::Balance,
202 tip: Self::Balance,
203 paid: Self::LiquidityInfo,
204 ) -> Result<(AssetBalanceOf<T>, AssetBalanceOf<T>), TransactionValidityError> {
205 let min_converted_fee = if corrected_fee.is_zero() { Zero::zero() } else { One::one() };
206 let converted_fee = CON::to_asset_balance(corrected_fee, paid.asset())
208 .map_err(|_| -> TransactionValidityError { InvalidTransaction::Payment.into() })?
209 .max(min_converted_fee);
210 let converted_tip = CON::to_asset_balance(tip, paid.asset())
211 .map_err(|_| -> TransactionValidityError { InvalidTransaction::Payment.into() })?;
212
213 let (final_fee, refund) = paid.split(converted_fee);
215 let _ = <T::Fungibles as Balanced<T::AccountId>>::resolve(who, refund);
218 HC::handle_credit(final_fee);
220 Ok((converted_fee, converted_tip))
221 }
222}