1use super::*;
18use crate::Config;
19
20use alloc::vec;
21use core::marker::PhantomData;
22use frame_support::{
23 defensive, ensure,
24 traits::{
25 fungibles,
26 tokens::{Balance, Fortitude, Precision, Preservation, WithdrawConsequence},
27 Defensive, OnUnbalanced, SameOrOther,
28 },
29 unsigned::TransactionValidityError,
30};
31use pallet_asset_conversion::{QuotePrice, SwapCredit};
32use sp_runtime::{
33 traits::{DispatchInfoOf, Get, PostDispatchInfoOf, Zero},
34 transaction_validity::InvalidTransaction,
35 Saturating,
36};
37
38pub trait OnChargeAssetTransaction<T: Config> {
40 type Balance: Balance;
42 type AssetId: AssetId;
44 type LiquidityInfo;
46
47 fn withdraw_fee(
51 who: &T::AccountId,
52 call: &T::RuntimeCall,
53 dispatch_info: &DispatchInfoOf<T::RuntimeCall>,
54 asset_id: Self::AssetId,
55 fee: Self::Balance,
56 tip: Self::Balance,
57 ) -> Result<Self::LiquidityInfo, TransactionValidityError>;
58
59 fn can_withdraw_fee(
63 who: &T::AccountId,
64 asset_id: Self::AssetId,
65 fee: Self::Balance,
66 ) -> Result<(), TransactionValidityError>;
67
68 fn correct_and_deposit_fee(
75 who: &T::AccountId,
76 dispatch_info: &DispatchInfoOf<T::RuntimeCall>,
77 post_info: &PostDispatchInfoOf<T::RuntimeCall>,
78 corrected_fee: Self::Balance,
79 tip: Self::Balance,
80 asset_id: Self::AssetId,
81 already_withdraw: Self::LiquidityInfo,
82 ) -> Result<BalanceOf<T>, TransactionValidityError>;
83}
84
85pub struct SwapAssetAdapter<A, F, S, OU>(PhantomData<(A, F, S, OU)>);
98
99impl<A, F, S, OU, T> OnChargeAssetTransaction<T> for SwapAssetAdapter<A, F, S, OU>
100where
101 A: Get<T::AssetId>,
102 F: fungibles::Balanced<T::AccountId, Balance = BalanceOf<T>, AssetId = T::AssetId>,
103 S: SwapCredit<
104 T::AccountId,
105 Balance = BalanceOf<T>,
106 AssetKind = T::AssetId,
107 Credit = fungibles::Credit<T::AccountId, F>,
108 > + QuotePrice<Balance = BalanceOf<T>, AssetKind = T::AssetId>,
109 OU: OnUnbalanced<fungibles::Credit<T::AccountId, F>>,
110 T: Config,
111{
112 type AssetId = T::AssetId;
113 type Balance = BalanceOf<T>;
114 type LiquidityInfo = (fungibles::Credit<T::AccountId, F>, BalanceOf<T>);
115
116 fn withdraw_fee(
117 who: &T::AccountId,
118 _call: &T::RuntimeCall,
119 _dispatch_info: &DispatchInfoOf<<T>::RuntimeCall>,
120 asset_id: Self::AssetId,
121 fee: Self::Balance,
122 _tip: Self::Balance,
123 ) -> Result<Self::LiquidityInfo, TransactionValidityError> {
124 if asset_id == A::get() {
125 let fee_credit = F::withdraw(
127 asset_id.clone(),
128 who,
129 fee,
130 Precision::Exact,
131 Preservation::Preserve,
132 Fortitude::Polite,
133 )
134 .map_err(|_| InvalidTransaction::Payment)?;
135
136 return Ok((fee_credit, fee));
137 }
138
139 let asset_fee =
141 S::quote_price_tokens_for_exact_tokens(asset_id.clone(), A::get(), fee, true)
142 .ok_or(InvalidTransaction::Payment)?;
143
144 let asset_fee_credit = F::withdraw(
146 asset_id.clone(),
147 who,
148 asset_fee,
149 Precision::Exact,
150 Preservation::Preserve,
151 Fortitude::Polite,
152 )
153 .map_err(|_| InvalidTransaction::Payment)?;
154
155 let (fee_credit, change) = match S::swap_tokens_for_exact_tokens(
156 vec![asset_id, A::get()],
157 asset_fee_credit,
158 fee,
159 ) {
160 Ok((fee_credit, change)) => (fee_credit, change),
161 Err((credit_in, _)) => {
162 defensive!("Fee swap should pass for the quoted amount");
163 let _ = F::resolve(who, credit_in).defensive_proof("Should resolve the credit");
164 return Err(InvalidTransaction::Payment.into())
165 },
166 };
167
168 ensure!(change.peek().is_zero(), InvalidTransaction::Payment);
170
171 Ok((fee_credit, asset_fee))
172 }
173
174 fn can_withdraw_fee(
180 who: &T::AccountId,
181 asset_id: Self::AssetId,
182 fee: BalanceOf<T>,
183 ) -> Result<(), TransactionValidityError> {
184 if asset_id == A::get() {
185 match F::can_withdraw(asset_id.clone(), who, fee) {
187 WithdrawConsequence::BalanceLow |
188 WithdrawConsequence::UnknownAsset |
189 WithdrawConsequence::Underflow |
190 WithdrawConsequence::Overflow |
191 WithdrawConsequence::Frozen =>
192 return Err(TransactionValidityError::from(InvalidTransaction::Payment)),
193 WithdrawConsequence::Success |
194 WithdrawConsequence::ReducedToZero(_) |
195 WithdrawConsequence::WouldDie => return Ok(()),
196 }
197 }
198
199 let asset_fee =
200 S::quote_price_tokens_for_exact_tokens(asset_id.clone(), A::get(), fee, true)
201 .ok_or(InvalidTransaction::Payment)?;
202
203 match F::can_withdraw(asset_id.clone(), who, asset_fee) {
205 WithdrawConsequence::BalanceLow |
206 WithdrawConsequence::UnknownAsset |
207 WithdrawConsequence::Underflow |
208 WithdrawConsequence::Overflow |
209 WithdrawConsequence::Frozen =>
210 return Err(TransactionValidityError::from(InvalidTransaction::Payment)),
211 WithdrawConsequence::Success |
212 WithdrawConsequence::ReducedToZero(_) |
213 WithdrawConsequence::WouldDie => {},
214 };
215
216 Ok(())
217 }
218
219 fn correct_and_deposit_fee(
220 who: &T::AccountId,
221 _dispatch_info: &DispatchInfoOf<<T>::RuntimeCall>,
222 _post_info: &PostDispatchInfoOf<<T>::RuntimeCall>,
223 corrected_fee: Self::Balance,
224 tip: Self::Balance,
225 asset_id: Self::AssetId,
226 already_withdrawn: Self::LiquidityInfo,
227 ) -> Result<BalanceOf<T>, TransactionValidityError> {
228 let (fee_paid, initial_asset_consumed) = already_withdrawn;
229 let refund_amount = fee_paid.peek().saturating_sub(corrected_fee);
230 let (fee_in_asset, adjusted_paid) = if refund_amount.is_zero() ||
231 F::total_balance(asset_id.clone(), who).is_zero()
232 {
233 (initial_asset_consumed, fee_paid)
235 } else if asset_id == A::get() {
236 let (refund, fee_paid) = fee_paid.split(refund_amount);
238 if let Err(refund) = F::resolve(who, refund) {
239 let fee_paid = fee_paid.merge(refund).map_err(|_| {
240 defensive!("`fee_paid` and `refund` are credits of the same asset.");
241 InvalidTransaction::Payment
242 })?;
243 (initial_asset_consumed, fee_paid)
244 } else {
245 (fee_paid.peek().saturating_sub(refund_amount), fee_paid)
246 }
247 } else {
248 let refund_asset_amount = S::quote_price_exact_tokens_for_tokens(
251 A::get(),
252 asset_id.clone(),
253 refund_amount,
254 true,
255 )
256 .unwrap_or(Zero::zero());
258
259 let debt = if refund_asset_amount.is_zero() {
260 fungibles::Debt::<T::AccountId, F>::zero(asset_id.clone())
261 } else {
262 match F::deposit(asset_id.clone(), &who, refund_asset_amount, Precision::BestEffort)
264 {
265 Ok(debt) => debt,
266 Err(_) => fungibles::Debt::<T::AccountId, F>::zero(asset_id.clone()),
268 }
269 };
270
271 if debt.peek().is_zero() {
272 (initial_asset_consumed, fee_paid)
274 } else {
275 let (refund, adjusted_paid) = fee_paid.split(refund_amount);
276 match S::swap_exact_tokens_for_tokens(
277 vec![A::get(), asset_id],
278 refund,
279 Some(refund_asset_amount),
280 ) {
281 Ok(refund_asset) => {
282 match refund_asset.offset(debt) {
283 Ok(SameOrOther::None) => {},
284 _ => {
287 defensive!("Debt should be equal to the refund credit");
288 return Err(InvalidTransaction::Payment.into())
289 },
290 };
291 (
292 initial_asset_consumed.saturating_sub(refund_asset_amount.into()),
293 adjusted_paid,
294 )
295 },
296 Err((refund, _)) => {
298 defensive!("Refund swap should pass for the quoted amount");
299 match F::settle(who, debt, Preservation::Expendable) {
300 Ok(dust) => ensure!(dust.peek().is_zero(), InvalidTransaction::Payment),
301 Err(_) => {
303 defensive!("Should settle the debt");
304 return Err(InvalidTransaction::Payment.into())
305 },
306 };
307 let adjusted_paid = adjusted_paid.merge(refund).map_err(|_| {
308 InvalidTransaction::Payment
311 })?;
312 (initial_asset_consumed, adjusted_paid)
313 },
314 }
315 }
316 };
317
318 let (tip, fee) = adjusted_paid.split(tip);
320 OU::on_unbalanceds(Some(fee).into_iter().chain(Some(tip)));
321 Ok(fee_in_asset)
322 }
323}