1use alloc::boxed::Box;
20use core::{fmt::Debug, marker::PhantomData};
21use frame_support::{
22 defensive_assert,
23 traits::{
24 tokens::{
25 fungibles,
26 imbalance::{ImbalanceAccounting, UnsafeManualAccounting},
27 Fortitude::Polite,
28 Precision::Exact,
29 Preservation::Expendable,
30 Provenance::Minted,
31 },
32 Contains, Get,
33 },
34};
35use xcm::latest::prelude::*;
36use xcm_executor::{
37 traits::{ConvertLocation, Error as MatchError, MatchesFungibles, TransactAsset},
38 AssetsInHolding,
39};
40
41pub struct FungiblesTransferAdapter<Assets, Matcher, AccountIdConverter, AccountId>(
43 PhantomData<(Assets, Matcher, AccountIdConverter, AccountId)>,
44);
45impl<
46 Assets: fungibles::Mutate<AccountId>,
47 Matcher: MatchesFungibles<Assets::AssetId, Assets::Balance>,
48 AccountIdConverter: ConvertLocation<AccountId>,
49 AccountId: Eq + Clone + Debug, > TransactAsset for FungiblesTransferAdapter<Assets, Matcher, AccountIdConverter, AccountId>
52{
53 fn internal_transfer_asset(
54 what: &Asset,
55 from: &Location,
56 to: &Location,
57 _context: &XcmContext,
58 ) -> Result<Asset, XcmError> {
59 tracing::trace!(
60 target: "xcm::fungibles_adapter",
61 ?what, ?from, ?to,
62 "internal_transfer_asset"
63 );
64 let (asset_id, amount) = Matcher::matches_fungibles(what)?;
66 let source = AccountIdConverter::convert_location(from)
67 .ok_or(MatchError::AccountIdConversionFailed)?;
68 let dest = AccountIdConverter::convert_location(to)
69 .ok_or(MatchError::AccountIdConversionFailed)?;
70 Assets::transfer(asset_id.clone(), &source, &dest, amount, Expendable).map_err(|e| {
71 tracing::debug!(target: "xcm::fungibles_adapter", error = ?e, ?asset_id, ?source, ?dest, ?amount, "Failed internal transfer asset");
72 XcmError::FailedToTransactAsset(e.into())
73 })?;
74 Ok(what.clone())
75 }
76}
77
78#[derive(Copy, Clone, Eq, PartialEq)]
80pub enum MintLocation {
81 Local,
84 NonLocal,
87}
88
89pub trait AssetChecking<AssetId> {
98 fn asset_checking(asset: &AssetId) -> Option<MintLocation>;
102}
103
104pub struct NoChecking;
107impl<AssetId> AssetChecking<AssetId> for NoChecking {
108 fn asset_checking(_: &AssetId) -> Option<MintLocation> {
109 None
110 }
111}
112
113pub struct LocalMint<T>(core::marker::PhantomData<T>);
116impl<AssetId, T: Contains<AssetId>> AssetChecking<AssetId> for LocalMint<T> {
117 fn asset_checking(asset: &AssetId) -> Option<MintLocation> {
118 match T::contains(asset) {
119 true => Some(MintLocation::Local),
120 false => None,
121 }
122 }
123}
124
125pub struct NonLocalMint<T>(core::marker::PhantomData<T>);
128impl<AssetId, T: Contains<AssetId>> AssetChecking<AssetId> for NonLocalMint<T> {
129 fn asset_checking(asset: &AssetId) -> Option<MintLocation> {
130 match T::contains(asset) {
131 true => Some(MintLocation::NonLocal),
132 false => None,
133 }
134 }
135}
136
137pub struct DualMint<L, R>(core::marker::PhantomData<(L, R)>);
141impl<AssetId, L: Contains<AssetId>, R: Contains<AssetId>> AssetChecking<AssetId>
142 for DualMint<L, R>
143{
144 fn asset_checking(asset: &AssetId) -> Option<MintLocation> {
145 if L::contains(asset) {
146 Some(MintLocation::Local)
147 } else if R::contains(asset) {
148 Some(MintLocation::NonLocal)
149 } else {
150 None
151 }
152 }
153}
154
155pub struct FungiblesMutateAdapter<
156 Assets,
157 Matcher,
158 AccountIdConverter,
159 AccountId,
160 CheckAsset,
161 CheckingAccount,
162>(PhantomData<(Assets, Matcher, AccountIdConverter, AccountId, CheckAsset, CheckingAccount)>);
163
164impl<
165 Assets: fungibles::Mutate<AccountId>,
166 Matcher: MatchesFungibles<Assets::AssetId, Assets::Balance>,
167 AccountIdConverter: ConvertLocation<AccountId>,
168 AccountId: Eq + Clone + Debug, CheckAsset: AssetChecking<Assets::AssetId>,
171 CheckingAccount: Get<AccountId>,
172 >
173 FungiblesMutateAdapter<Assets, Matcher, AccountIdConverter, AccountId, CheckAsset, CheckingAccount>
174{
175 fn can_accrue_checked(asset_id: Assets::AssetId, amount: Assets::Balance) -> XcmResult {
176 let checking_account = CheckingAccount::get();
177 Assets::can_deposit(asset_id, &checking_account, amount, Minted)
178 .into_result()
179 .map_err(|error| {
180 tracing::debug!(
181 target: "xcm::fungibles_adapter", ?error, ?checking_account, ?amount,
182 "Failed to check if asset can be accrued"
183 );
184 XcmError::NotDepositable
185 })
186 }
187 fn can_reduce_checked(asset_id: Assets::AssetId, amount: Assets::Balance) -> XcmResult {
188 let checking_account = CheckingAccount::get();
189 Assets::can_withdraw(asset_id, &checking_account, amount)
190 .into_result(false)
191 .map_err(|error| {
192 tracing::debug!(
193 target: "xcm::fungibles_adapter", ?error, ?checking_account, ?amount,
194 "Failed to check if asset can be reduced"
195 );
196 XcmError::NotWithdrawable
197 })
198 .map(|_| ())
199 }
200 fn accrue_checked(asset_id: Assets::AssetId, amount: Assets::Balance) {
201 let checking_account = CheckingAccount::get();
202 let ok = Assets::mint_into(asset_id, &checking_account, amount).is_ok();
203 debug_assert!(ok, "`can_accrue_checked` must have returned `true` immediately prior; qed");
204 }
205 fn reduce_checked(asset_id: Assets::AssetId, amount: Assets::Balance) {
206 let checking_account = CheckingAccount::get();
207 let ok = Assets::burn_from(asset_id, &checking_account, amount, Expendable, Exact, Polite)
208 .is_ok();
209 debug_assert!(ok, "`can_reduce_checked` must have returned `true` immediately prior; qed");
210 }
211}
212
213impl<
214 Assets: fungibles::Inspect<AccountId, AssetId: 'static, Balance: 'static>
215 + fungibles::Mutate<AccountId>
216 + fungibles::Balanced<AccountId, OnDropCredit: 'static, OnDropDebt: 'static>
217 + 'static,
218 Matcher: MatchesFungibles<Assets::AssetId, Assets::Balance>,
219 AccountIdConverter: ConvertLocation<AccountId>,
220 AccountId: Eq + Clone + Debug, CheckAsset: AssetChecking<Assets::AssetId>,
223 CheckingAccount: Get<AccountId>,
224 > TransactAsset
225 for FungiblesMutateAdapter<
226 Assets,
227 Matcher,
228 AccountIdConverter,
229 AccountId,
230 CheckAsset,
231 CheckingAccount,
232 >
233where
234 fungibles::Imbalance<
235 <Assets as fungibles::Inspect<AccountId>>::AssetId,
236 <Assets as fungibles::Inspect<AccountId>>::Balance,
237 <Assets as fungibles::Balanced<AccountId>>::OnDropCredit,
238 <Assets as fungibles::Balanced<AccountId>>::OnDropDebt,
239 >: ImbalanceAccounting<u128>,
240{
241 fn can_check_in(origin: &Location, what: &Asset, _context: &XcmContext) -> XcmResult {
242 tracing::trace!(
243 target: "xcm::fungibles_adapter",
244 ?origin, ?what,
245 "can_check_in"
246 );
247 let (asset_id, amount) = Matcher::matches_fungibles(what)?;
249 match CheckAsset::asset_checking(&asset_id) {
250 Some(MintLocation::Local) => Self::can_reduce_checked(asset_id, amount),
252 Some(MintLocation::NonLocal) => Self::can_accrue_checked(asset_id, amount),
254 _ => Ok(()),
255 }
256 }
257
258 fn check_in(origin: &Location, what: &Asset, _context: &XcmContext) {
259 tracing::trace!(
260 target: "xcm::fungibles_adapter",
261 ?origin, ?what,
262 "check_in"
263 );
264 if let Ok((asset_id, amount)) = Matcher::matches_fungibles(what) {
265 match CheckAsset::asset_checking(&asset_id) {
266 Some(MintLocation::Local) => Self::reduce_checked(asset_id, amount),
268 Some(MintLocation::NonLocal) => Self::accrue_checked(asset_id, amount),
270 _ => (),
271 }
272 }
273 }
274
275 fn can_check_out(origin: &Location, what: &Asset, _context: &XcmContext) -> XcmResult {
276 tracing::trace!(
277 target: "xcm::fungibles_adapter",
278 ?origin, ?what,
279 "can_check_out"
280 );
281 let (asset_id, amount) = Matcher::matches_fungibles(what)?;
283 match CheckAsset::asset_checking(&asset_id) {
284 Some(MintLocation::Local) => Self::can_accrue_checked(asset_id, amount),
286 Some(MintLocation::NonLocal) => Self::can_reduce_checked(asset_id, amount),
288 _ => Ok(()),
289 }
290 }
291
292 fn check_out(dest: &Location, what: &Asset, _context: &XcmContext) {
293 tracing::trace!(
294 target: "xcm::fungibles_adapter",
295 ?dest, ?what,
296 "check_out"
297 );
298 if let Ok((asset_id, amount)) = Matcher::matches_fungibles(what) {
299 match CheckAsset::asset_checking(&asset_id) {
300 Some(MintLocation::Local) => Self::accrue_checked(asset_id, amount),
302 Some(MintLocation::NonLocal) => Self::reduce_checked(asset_id, amount),
304 _ => (),
305 }
306 }
307 }
308
309 fn deposit_asset(
310 mut what: AssetsInHolding,
311 who: &Location,
312 _context: Option<&XcmContext>,
313 ) -> Result<(), (AssetsInHolding, XcmError)> {
314 tracing::trace!(
315 target: "xcm::fungibles_adapter",
316 ?what, ?who,
317 "deposit_asset"
318 );
319 defensive_assert!(what.len() == 1, "Trying to deposit more than one asset!");
320 let maybe = what.fungible_assets_iter().next().and_then(|asset| {
322 Matcher::matches_fungibles(&asset)
323 .map(|(fungibles_id, amount)| (asset.id, fungibles_id, amount))
324 .ok()
325 });
326 let Some((asset_id, fungibles_id, amount)) = maybe else {
327 return Err((what, MatchError::AssetNotHandled.into()));
328 };
329 let Some(who) = AccountIdConverter::convert_location(who) else {
330 return Err((what, MatchError::AccountIdConversionFailed.into()));
331 };
332 let Some(imbalance) = what.fungible.remove(&asset_id) else {
333 return Err((what, MatchError::AssetNotHandled.into()));
334 };
335 let mut credit = fungibles::Credit::<AccountId, Assets>::zero(fungibles_id);
337 credit.saturating_subsume(imbalance);
338
339 Assets::resolve(&who, credit).map_err(|unspent| {
340 tracing::debug!(target: "xcm::fungibles_adapter", ?asset_id, ?who, ?amount, "Failed to deposit asset");
341 (
342 AssetsInHolding::new_from_fungible_credit(asset_id, Box::new(unspent)),
343 XcmError::FailedToTransactAsset("")
344 )
345 })?;
346 Ok(())
347 }
348
349 fn withdraw_asset(
350 what: &Asset,
351 who: &Location,
352 _maybe_context: Option<&XcmContext>,
353 ) -> Result<AssetsInHolding, XcmError> {
354 tracing::trace!(
355 target: "xcm::fungibles_adapter",
356 ?what, ?who,
357 "withdraw_asset"
358 );
359 let (asset_id, amount) = Matcher::matches_fungibles(what)?;
361 let who = AccountIdConverter::convert_location(who)
362 .ok_or(MatchError::AccountIdConversionFailed)?;
363 let credit = Assets::withdraw(asset_id, &who, amount, Exact, Expendable, Polite).map_err(|error| {
364 tracing::debug!(target: "xcm::fungibles_adapter", ?error, ?who, ?amount, "Failed to withdraw asset");
365 XcmError::FailedToTransactAsset(error.into())
366 })?;
367 Ok(AssetsInHolding::new_from_fungible_credit(what.id.clone(), Box::new(credit)))
368 }
369
370 fn mint_asset(what: &Asset, context: &XcmContext) -> Result<AssetsInHolding, XcmError> {
371 tracing::trace!(
372 target: "xcm::fungibles_adapter",
373 ?what, ?context,
374 "mint_asset",
375 );
376 let (asset_id, amount) = Matcher::matches_fungibles(what)?;
377 let credit = Assets::issue(asset_id, amount);
378 Ok(AssetsInHolding::new_from_fungible_credit(what.id.clone(), Box::new(credit)))
379 }
380}
381
382pub struct FungiblesAdapter<
383 Assets,
384 Matcher,
385 AccountIdConverter,
386 AccountId,
387 CheckAsset,
388 CheckingAccount,
389>(PhantomData<(Assets, Matcher, AccountIdConverter, AccountId, CheckAsset, CheckingAccount)>);
390impl<
391 Assets: fungibles::Inspect<AccountId, AssetId: 'static, Balance: 'static>
392 + fungibles::Mutate<AccountId>
393 + fungibles::Balanced<AccountId, OnDropCredit: 'static, OnDropDebt: 'static>
394 + 'static,
395 Matcher: MatchesFungibles<Assets::AssetId, Assets::Balance>,
396 AccountIdConverter: ConvertLocation<AccountId>,
397 AccountId: Eq + Clone + Debug, CheckAsset: AssetChecking<Assets::AssetId>,
400 CheckingAccount: Get<AccountId>,
401 > TransactAsset
402 for FungiblesAdapter<Assets, Matcher, AccountIdConverter, AccountId, CheckAsset, CheckingAccount>
403where
404 fungibles::Imbalance<
405 <Assets as fungibles::Inspect<AccountId>>::AssetId,
406 <Assets as fungibles::Inspect<AccountId>>::Balance,
407 <Assets as fungibles::Balanced<AccountId>>::OnDropCredit,
408 <Assets as fungibles::Balanced<AccountId>>::OnDropDebt,
409 >: ImbalanceAccounting<u128>,
410{
411 fn can_check_in(origin: &Location, what: &Asset, context: &XcmContext) -> XcmResult {
412 FungiblesMutateAdapter::<
413 Assets,
414 Matcher,
415 AccountIdConverter,
416 AccountId,
417 CheckAsset,
418 CheckingAccount,
419 >::can_check_in(origin, what, context)
420 }
421
422 fn check_in(origin: &Location, what: &Asset, context: &XcmContext) {
423 FungiblesMutateAdapter::<
424 Assets,
425 Matcher,
426 AccountIdConverter,
427 AccountId,
428 CheckAsset,
429 CheckingAccount,
430 >::check_in(origin, what, context)
431 }
432
433 fn can_check_out(dest: &Location, what: &Asset, context: &XcmContext) -> XcmResult {
434 FungiblesMutateAdapter::<
435 Assets,
436 Matcher,
437 AccountIdConverter,
438 AccountId,
439 CheckAsset,
440 CheckingAccount,
441 >::can_check_out(dest, what, context)
442 }
443
444 fn check_out(dest: &Location, what: &Asset, context: &XcmContext) {
445 FungiblesMutateAdapter::<
446 Assets,
447 Matcher,
448 AccountIdConverter,
449 AccountId,
450 CheckAsset,
451 CheckingAccount,
452 >::check_out(dest, what, context)
453 }
454
455 fn deposit_asset(
456 what: AssetsInHolding,
457 who: &Location,
458 context: Option<&XcmContext>,
459 ) -> Result<(), (AssetsInHolding, XcmError)> {
460 FungiblesMutateAdapter::<
461 Assets,
462 Matcher,
463 AccountIdConverter,
464 AccountId,
465 CheckAsset,
466 CheckingAccount,
467 >::deposit_asset(what, who, context)
468 }
469
470 fn withdraw_asset(
471 what: &Asset,
472 who: &Location,
473 context: Option<&XcmContext>,
474 ) -> Result<AssetsInHolding, XcmError> {
475 FungiblesMutateAdapter::<
476 Assets,
477 Matcher,
478 AccountIdConverter,
479 AccountId,
480 CheckAsset,
481 CheckingAccount,
482 >::withdraw_asset(what, who, context)
483 }
484
485 fn internal_transfer_asset(
486 what: &Asset,
487 from: &Location,
488 to: &Location,
489 context: &XcmContext,
490 ) -> Result<Asset, XcmError> {
491 FungiblesTransferAdapter::<Assets, Matcher, AccountIdConverter, AccountId>::internal_transfer_asset(
492 what, from, to, context
493 )
494 }
495
496 fn mint_asset(what: &Asset, context: &XcmContext) -> Result<AssetsInHolding, XcmError> {
497 FungiblesMutateAdapter::<
498 Assets,
499 Matcher,
500 AccountIdConverter,
501 AccountId,
502 CheckAsset,
503 CheckingAccount,
504 >::mint_asset(what, context)
505 }
506}