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::{
27 Balance, DepositConsequence, Fortitude, Precision, Preservation, Provenance,
28 WithdrawConsequence,
29 },
30 Defensive, OnUnbalanced,
31 },
32 unsigned::TransactionValidityError,
33};
34use pallet_asset_conversion::{QuotePrice, SwapCredit};
35use sp_runtime::{
36 traits::{DispatchInfoOf, Get, PostDispatchInfoOf, Zero},
37 transaction_validity::InvalidTransaction,
38 Saturating,
39};
40
41pub trait OnChargeAssetTransaction<T: Config> {
43 type Balance: Balance;
45 type AssetId: AssetId;
47 type LiquidityInfo;
49
50 fn withdraw_fee(
54 who: &T::AccountId,
55 call: &T::RuntimeCall,
56 dispatch_info: &DispatchInfoOf<T::RuntimeCall>,
57 asset_id: Self::AssetId,
58 fee: Self::Balance,
59 tip: Self::Balance,
60 ) -> Result<Self::LiquidityInfo, TransactionValidityError>;
61
62 fn can_withdraw_fee(
66 who: &T::AccountId,
67 asset_id: Self::AssetId,
68 fee: Self::Balance,
69 ) -> Result<(), TransactionValidityError>;
70
71 fn correct_and_deposit_fee(
78 who: &T::AccountId,
79 dispatch_info: &DispatchInfoOf<T::RuntimeCall>,
80 post_info: &PostDispatchInfoOf<T::RuntimeCall>,
81 corrected_fee: Self::Balance,
82 tip: Self::Balance,
83 asset_id: Self::AssetId,
84 already_withdraw: Self::LiquidityInfo,
85 ) -> Result<BalanceOf<T>, TransactionValidityError>;
86}
87
88pub struct SwapAssetAdapter<A, F, S, OU>(PhantomData<(A, F, S, OU)>);
101
102impl<A, F, S, OU, T> OnChargeAssetTransaction<T> for SwapAssetAdapter<A, F, S, OU>
103where
104 A: Get<T::AssetId>,
105 F: fungibles::Balanced<T::AccountId, Balance = BalanceOf<T>, AssetId = T::AssetId>,
106 S: SwapCredit<
107 T::AccountId,
108 Balance = BalanceOf<T>,
109 AssetKind = T::AssetId,
110 Credit = fungibles::Credit<T::AccountId, F>,
111 > + QuotePrice<Balance = BalanceOf<T>, AssetKind = T::AssetId>,
112 OU: OnUnbalanced<fungibles::Credit<T::AccountId, F>>,
113 T: Config,
114{
115 type AssetId = T::AssetId;
116 type Balance = BalanceOf<T>;
117 type LiquidityInfo = (fungibles::Credit<T::AccountId, F>, BalanceOf<T>);
118
119 fn withdraw_fee(
120 who: &T::AccountId,
121 _call: &T::RuntimeCall,
122 _dispatch_info: &DispatchInfoOf<<T>::RuntimeCall>,
123 asset_id: Self::AssetId,
124 fee: Self::Balance,
125 _tip: Self::Balance,
126 ) -> Result<Self::LiquidityInfo, TransactionValidityError> {
127 if asset_id == A::get() {
128 let fee_credit = F::withdraw(
130 asset_id.clone(),
131 who,
132 fee,
133 Precision::Exact,
134 Preservation::Preserve,
135 Fortitude::Polite,
136 )
137 .map_err(|_| InvalidTransaction::Payment)?;
138
139 return Ok((fee_credit, fee));
140 }
141
142 let asset_fee =
144 S::quote_price_tokens_for_exact_tokens(asset_id.clone(), A::get(), fee, true)
145 .filter(|asset_fee| !asset_fee.is_zero())
146 .ok_or(InvalidTransaction::Payment)?;
147
148 let asset_fee_credit = F::withdraw(
150 asset_id.clone(),
151 who,
152 asset_fee,
153 Precision::Exact,
154 Preservation::Preserve,
155 Fortitude::Polite,
156 )
157 .map_err(|_| InvalidTransaction::Payment)?;
158
159 let (fee_credit, change) = match S::swap_tokens_for_exact_tokens(
160 vec![asset_id, A::get()],
161 asset_fee_credit,
162 fee,
163 ) {
164 Ok((fee_credit, change)) => (fee_credit, change),
165 Err((credit_in, _)) => {
166 defensive!("Fee swap should pass for the quoted amount");
167 let _ = F::resolve(who, credit_in).defensive_proof("Should resolve the credit");
168 return Err(InvalidTransaction::Payment.into());
169 },
170 };
171
172 ensure!(change.peek().is_zero(), InvalidTransaction::Payment);
174
175 Ok((fee_credit, asset_fee))
176 }
177
178 fn can_withdraw_fee(
184 who: &T::AccountId,
185 asset_id: Self::AssetId,
186 fee: BalanceOf<T>,
187 ) -> Result<(), TransactionValidityError> {
188 if asset_id == A::get() {
189 match F::can_withdraw(asset_id.clone(), who, fee) {
191 WithdrawConsequence::Success => return Ok(()),
192 _ => return Err(TransactionValidityError::from(InvalidTransaction::Payment)),
193 }
194 }
195
196 let asset_fee =
197 S::quote_price_tokens_for_exact_tokens(asset_id.clone(), A::get(), fee, true)
198 .filter(|asset_fee| !asset_fee.is_zero())
199 .ok_or(InvalidTransaction::Payment)?;
200
201 match F::can_withdraw(asset_id.clone(), who, asset_fee) {
203 WithdrawConsequence::Success => {},
204 _ => return Err(TransactionValidityError::from(InvalidTransaction::Payment)),
205 };
206
207 Ok(())
208 }
209
210 fn correct_and_deposit_fee(
211 who: &T::AccountId,
212 _dispatch_info: &DispatchInfoOf<<T>::RuntimeCall>,
213 _post_info: &PostDispatchInfoOf<<T>::RuntimeCall>,
214 corrected_fee: Self::Balance,
215 tip: Self::Balance,
216 asset_id: Self::AssetId,
217 already_withdrawn: Self::LiquidityInfo,
218 ) -> Result<BalanceOf<T>, TransactionValidityError> {
219 let (fee_paid, fee_asset_amount) = already_withdrawn;
222 let refund_amount = fee_paid.peek().saturating_sub(corrected_fee);
223
224 if refund_amount.is_zero() || F::total_balance(asset_id.clone(), who).is_zero() {
226 let (tip, fee) = fee_paid.split(tip);
227 OU::on_unbalanceds(Some(fee).into_iter().chain(Some(tip)));
228 return Ok(fee_asset_amount);
229 }
230
231 if asset_id == A::get() {
233 let (refund, adjusted_paid) = fee_paid.split(refund_amount);
234
235 let (fee_asset_amount, adjusted_paid) = match F::resolve(who, refund) {
236 Ok(_) => (adjusted_paid.peek(), adjusted_paid),
237 Err(refund) => {
238 adjusted_paid.merge(refund).map_or_else(
240 |(adjusted_paid, refund)| {
241 defensive!(
242 "`adjusted_paid` and `refund` are credits of the same asset.",
243 (adjusted_paid.asset(), refund.asset(), who)
244 );
245 (fee_asset_amount, adjusted_paid)
247 },
248 |fee_paid| (fee_paid.peek(), fee_paid),
249 )
250 },
251 };
252
253 let (tip, fee) = adjusted_paid.split(tip);
255 OU::on_unbalanceds(Some(fee).into_iter().chain(Some(tip)));
256 return Ok(fee_asset_amount);
257 }
258
259 let refund_asset_amount =
263 S::quote_price_exact_tokens_for_tokens(A::get(), asset_id.clone(), refund_amount, true)
264 .unwrap_or(Zero::zero());
266
267 if refund_asset_amount.is_zero() ||
270 !matches!(
271 F::can_deposit(asset_id.clone(), who, refund_asset_amount, Provenance::Extant),
272 DepositConsequence::Success
273 ) {
274 let (tip, fee) = fee_paid.split(tip);
275 OU::on_unbalanceds(Some(fee).into_iter().chain(Some(tip)));
276 return Ok(fee_asset_amount);
277 }
278
279 let (refund, adjusted_paid) = fee_paid.split(refund_amount);
282
283 let (fee_asset_amount, adjusted_paid) = match S::swap_exact_tokens_for_tokens(
284 vec![A::get(), asset_id],
285 refund,
286 Some(refund_asset_amount),
287 ) {
288 Ok(refund_asset) => match F::resolve(who, refund_asset) {
289 Ok(_) => (fee_asset_amount.saturating_sub(refund_asset_amount), adjusted_paid),
290 Err(refund_asset) => {
291 defensive!(
292 "Refund resolve should pass since `can_deposit` was checked",
293 (refund_asset.asset(), refund_asset.peek(), who)
294 );
295 (fee_asset_amount, adjusted_paid)
296 },
297 },
298 Err((refund, _)) => {
300 defensive!(
301 "Refund swap should pass for the quoted amount",
302 (refund.asset(), refund.peek(), refund_asset_amount, who)
303 );
304 adjusted_paid.merge(refund).map_or_else(
306 |(adjusted_paid, refund)| {
307 defensive!(
308 "`adjusted_paid` and `refund` are credits of the same asset.",
309 (adjusted_paid.asset(), refund.asset(), who)
310 );
311 (fee_asset_amount, adjusted_paid)
313 },
314 |fee_paid| (fee_asset_amount, fee_paid),
315 )
316 },
317 };
318
319 let (tip, fee) = adjusted_paid.split(tip);
321 OU::on_unbalanceds(Some(fee).into_iter().chain(Some(tip)));
322 return Ok(fee_asset_amount);
323 }
324}